2 # -*- coding: utf-8 -*-
5 Defines objects to generate various kind of 1d, 2d and 3d LUTs in various file
9 from __future__ import division
15 import OpenImageIO as oiio
17 from aces_ocio.process import Process
19 __author__ = 'ACES Developers'
20 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
22 __maintainer__ = 'ACES Developers'
23 __email__ = 'aces@oscars.org'
24 __status__ = 'Production'
26 __all__ = ['generate_1d_LUT_image',
28 'generate_1d_LUT_from_image',
29 'generate_3d_LUT_image',
30 'generate_3d_LUT_from_image',
33 'generate_1d_LUT_from_CTL',
35 'generate_3d_LUT_from_CTL',
39 def generate_1d_LUT_image(ramp_1d_path,
49 Parameter description.
54 Return value description.
57 ramp = oiio.ImageOutput.create(ramp_1d_path)
59 spec = oiio.ImageSpec()
60 spec.set_format(oiio.FLOAT)
61 # spec.format.basetype = oiio.FLOAT
62 spec.width = resolution
66 ramp.open(ramp_1d_path, spec, oiio.Create)
68 data = array.array('f',
69 '\0' * spec.width * spec.height * spec.nchannels * 4)
70 for i in range(resolution):
71 value = float(i) / (resolution - 1) * (
72 max_value - min_value) + min_value
73 data[i * spec.nchannels + 0] = value
74 data[i * spec.nchannels + 1] = value
75 data[i * spec.nchannels + 2] = value
77 ramp.write_image(spec.format, data)
81 def write_SPI_1d(filename,
91 Credit to *Alex Fry* for the original single channel version of the spi1d
97 Parameter description.
102 Return value description.
105 # May want to use fewer components than there are channels in the data
106 # Most commonly used for single channel LUTs
107 components = min(3, components, channels)
109 with open(filename, 'w') as fp:
110 fp.write('Version 1\n')
111 fp.write('From %f %f\n' % (from_min, from_max))
112 fp.write('Length %d\n' % entries)
113 fp.write('Components %d\n' % components)
115 for i in range(0, entries):
117 for j in range(0, components):
118 entry = '%s %s' % (entry, data[i * channels + j])
119 fp.write(' %s\n' % entry)
123 def generate_1d_LUT_from_image(ramp_1d_path,
134 Parameter description.
139 Return value description.
142 if output_path is None:
143 output_path = '%s.%s' % (ramp_1d_path, 'spi1d')
145 ramp = oiio.ImageInput.open(ramp_1d_path)
147 ramp_spec = ramp.spec()
148 ramp_width = ramp_spec.width
149 ramp_channels = ramp_spec.nchannels
151 # Forcibly read data as float, the Python API doesn't handle half-float
154 ramp_data = ramp.read_image(type)
156 write_SPI_1d(output_path, min_value, max_value,
157 ramp_data, ramp_width, ramp_channels, channels)
160 def generate_3d_LUT_image(ramp_3d_path, resolution=32):
167 Parameter description.
172 Return value description.
175 args = ['--generate',
179 str(resolution * resolution),
182 lut_extract = Process(description='generate a 3d LUT image',
185 lut_extract.execute()
188 def generate_3d_LUT_from_image(ramp_3d_path, output_path=None, resolution=32):
195 Parameter description.
200 Return value description.
203 if output_path is None:
204 output_path = '%s.%s' % (ramp_3d_path, 'spi1d')
210 str(resolution * resolution),
215 lut_extract = Process(description='extract a 3d LUT',
218 lut_extract.execute()
221 def apply_CTL_to_image(input_image,
227 aces_ctl_directory=None):
234 Parameter description.
239 Return value description.
242 if ctl_paths is None:
244 if global_params is None:
247 if len(ctl_paths) > 0:
249 if aces_ctl_directory is not None:
250 if os.path.split(aces_ctl_directory)[1] != 'utilities':
251 ctl_module_path = os.path.join(aces_ctl_directory, 'utilities')
253 ctl_module_path = aces_ctl_directory
254 ctlenv['CTL_MODULE_PATH'] = ctl_module_path
257 for ctl in ctl_paths:
258 args += ['-ctl', ctl]
260 args += ['-input_scale', str(input_scale)]
261 args += ['-output_scale', str(output_scale)]
262 args += ['-global_param1', 'aIn', '1.0']
263 for key, value in global_params.iteritems():
264 args += ['-global_param1', key, str(value)]
265 args += [input_image]
266 args += [output_image]
268 ctlp = Process(description='a ctlrender process',
270 args=args, env=ctlenv)
275 def convert_bit_depth(input_image, output_image, depth):
282 Parameter description.
287 Return value description.
295 convert = Process(description='convert image bit depth',
301 def generate_1d_LUT_from_CTL(lut_path,
304 identity_LUT_bit_depth='half',
309 aces_ctl_directory=None,
319 Parameter description.
324 Return value description.
327 if global_params is None:
330 lut_path_base = os.path.splitext(lut_path)[0]
332 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
333 generate_1d_LUT_image(identity_LUT_image_float,
338 if identity_LUT_bit_depth not in ['half', 'float']:
339 identity_LUT_image = '%s.%s.%s' % (lut_path_base, 'uint16', 'tiff')
340 convert_bit_depth(identity_LUT_image_float,
342 identity_LUT_bit_depth)
344 identity_LUT_image = identity_LUT_image_float
346 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
347 apply_CTL_to_image(identity_LUT_image,
348 transformed_LUT_image,
355 generate_1d_LUT_from_image(transformed_LUT_image,
362 os.remove(identity_LUT_image)
363 if identity_LUT_image != identity_LUT_image_float:
364 os.remove(identity_LUT_image_float)
365 os.remove(transformed_LUT_image)
368 def correct_LUT_image(transformed_LUT_image,
377 Parameter description.
382 Return value description.
385 transformed = oiio.ImageInput.open(transformed_LUT_image)
387 transformed_spec = transformed.spec()
388 width = transformed_spec.width
389 height = transformed_spec.height
390 channels = transformed_spec.nchannels
392 if width != lut_resolution * lut_resolution or height != lut_resolution:
393 print(('Correcting image as resolution is off. '
394 'Found %d x %d. Expected %d x %d') % (
397 lut_resolution * lut_resolution,
399 print('Generating %s' % corrected_LUT_image)
401 # Forcibly read data as float, the Python API doesn't handle half-float
404 source_data = transformed.read_image(type)
406 correct = oiio.ImageOutput.create(corrected_LUT_image)
408 correct_spec = oiio.ImageSpec()
409 correct_spec.set_format(oiio.FLOAT)
410 correct_spec.width = height
411 correct_spec.height = width
412 correct_spec.nchannels = channels
414 correct.open(corrected_LUT_image, correct_spec, oiio.Create)
416 dest_data = array.array('f',
417 ('\0' * correct_spec.width *
418 correct_spec.height *
419 correct_spec.nchannels * 4))
420 for j in range(0, correct_spec.height):
421 for i in range(0, correct_spec.width):
422 for c in range(0, correct_spec.nchannels):
423 dest_data[(correct_spec.nchannels *
424 correct_spec.width * j +
425 correct_spec.nchannels * i + c)] = (
426 source_data[correct_spec.nchannels *
427 correct_spec.width * j +
428 correct_spec.nchannels * i + c])
430 correct.write_image(correct_spec.format, dest_data)
433 # shutil.copy(transformedLUTImage, correctedLUTImage)
434 corrected_LUT_image = transformed_LUT_image
438 return corrected_LUT_image
441 def generate_3d_LUT_from_CTL(lut_path,
444 identity_LUT_bit_depth='half',
449 aces_ctl_directory=None):
456 Parameter description.
461 Return value description.
464 if global_params is None:
467 lut_path_base = os.path.splitext(lut_path)[0]
469 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
470 generate_3d_LUT_image(identity_LUT_image_float, lut_resolution)
472 if identity_LUT_bit_depth not in ['half', 'float']:
473 identity_LUT_image = '%s.%s.%s' % (lut_path_base,
474 identity_LUT_bit_depth,
476 convert_bit_depth(identity_LUT_image_float,
478 identity_LUT_bit_depth)
480 identity_LUT_image = identity_LUT_image_float
482 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
483 apply_CTL_to_image(identity_LUT_image,
484 transformed_LUT_image,
491 corrected_LUT_image = '%s.%s.%s' % (lut_path_base, 'correct', 'exr')
492 corrected_LUT_image = correct_LUT_image(transformed_LUT_image,
496 generate_3d_LUT_from_image(corrected_LUT_image, lut_path, lut_resolution)
499 os.remove(identity_LUT_image)
500 if identity_LUT_image != identity_LUT_image_float:
501 os.remove(identity_LUT_image_float)
502 os.remove(transformed_LUT_image)
503 if corrected_LUT_image != transformed_LUT_image:
504 os.remove(corrected_LUT_image)
514 Parameter description.
519 Return value description.
524 p = optparse.OptionParser(
525 description='A utility to generate LUTs from CTL',
528 usage='%prog [options]')
530 p.add_option('--lut', '-l', type='string', default='')
531 p.add_option('--ctl', '-c', type='string', action='append')
532 p.add_option('--lutResolution1d', '', type='int', default=1024)
533 p.add_option('--lutResolution3d', '', type='int', default=33)
534 p.add_option('--ctlReleasePath', '-r', type='string', default='')
535 p.add_option('--bitDepth', '-b', type='string', default='float')
536 p.add_option('--keepTempImages', '', action='store_true')
537 p.add_option('--minValue', '', type='float', default=0)
538 p.add_option('--maxValue', '', type='float', default=1)
539 p.add_option('--inputScale', '', type='float', default=1)
540 p.add_option('--outputScale', '', type='float', default=1)
541 p.add_option('--ctlRenderParam', '-p', type='string', nargs=2,
544 p.add_option('--generate1d', '', action='store_true')
545 p.add_option('--generate3d', '', action='store_true')
547 options, arguments = p.parse_args()
551 lut_resolution_1d = options.lutResolution1d
552 lut_resolution_3d = options.lutResolution3d
553 min_value = options.minValue
554 max_value = options.maxValue
555 input_scale = options.inputScale
556 output_scale = options.outputScale
557 ctl_release_path = options.ctlReleasePath
558 generate_1d = options.generate1d is True
559 generate_3d = options.generate3d is True
560 bit_depth = options.bitDepth
561 cleanup = not options.keepTempImages
564 if options.ctlRenderParam is not None:
565 for param in options.ctlRenderParam:
566 params[param[0]] = float(param[1])
569 args_start = sys.argv.index('--') + 1
570 args = sys.argv[args_start:]
572 args_start = len(sys.argv) + 1
576 print('1D LUT generation options')
578 print('3D LUT generation options')
580 print('lut : %s' % lut)
581 print('ctls : %s' % ctls)
582 print('lut res 1d : %s' % lut_resolution_1d)
583 print('lut res 3d : %s' % lut_resolution_3d)
584 print('min value : %s' % min_value)
585 print('max value : %s' % max_value)
586 print('input scale : %s' % input_scale)
587 print('output scale : %s' % output_scale)
588 print('ctl render params : %s' % params)
589 print('ctl release path : %s' % ctl_release_path)
590 print('bit depth of input : %s' % bit_depth)
591 print('cleanup temp images : %s' % cleanup)
594 generate_1d_LUT_from_CTL(lut,
607 generate_3d_LUT_from_CTL(lut,
617 print(('\n\nNo LUT generated. '
618 'You must choose either 1D or 3D LUT generation\n\n'))
621 if __name__ == '__main__':