2 # -*- coding: utf-8 -*-
5 Defines objects to generate various kind of 1d, 2d and 3d LUTs in various file
13 import OpenImageIO as oiio
15 from aces_ocio.process import Process
17 __author__ = 'ACES Developers'
18 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
20 __maintainer__ = 'ACES Developers'
21 __email__ = 'aces@oscars.org'
22 __status__ = 'Production'
24 __all__ = ['generate_1d_LUT_image',
26 'generate_1d_LUT_from_image',
27 'generate_3d_LUT_image',
28 'generate_3d_LUT_from_image',
31 'generate_1d_LUT_from_CTL',
33 'generate_3d_LUT_from_CTL',
37 def generate_1d_LUT_image(ramp_1d_path,
47 Parameter description.
52 Return value description.
55 # print('Generate 1d LUT image - %s' % ramp1dPath)
58 format = os.path.splitext(ramp_1d_path)[1]
59 ramp = oiio.ImageOutput.create(ramp_1d_path)
62 spec = oiio.ImageSpec()
63 spec.set_format(oiio.FLOAT)
64 # spec.format.basetype = oiio.FLOAT
65 spec.width = resolution
69 ramp.open(ramp_1d_path, spec, oiio.Create)
71 data = array.array('f',
72 '\0' * spec.width * spec.height * spec.nchannels * 4)
73 for i in range(resolution):
74 value = float(i) / (resolution - 1) * (
75 max_value - min_value) + min_value
76 data[i * spec.nchannels + 0] = value
77 data[i * spec.nchannels + 1] = value
78 data[i * spec.nchannels + 2] = value
80 ramp.write_image(spec.format, data)
84 def write_SPI_1d(filename, from_min, from_max, data, entries, channels):
88 Credit to *Alex Fry* for the original single channel version of the spi1d
94 Parameter description.
99 Return value description.
102 f = file(filename, 'w')
103 f.write('Version 1\n')
104 f.write('From %f %f\n' % (from_min, from_max))
105 f.write('Length %d\n' % entries)
106 f.write('Components %d\n' % (min(3, channels)))
108 for i in range(0, entries):
110 for j in range(0, min(3, channels)):
111 entry = '%s %s' % (entry, data[i * channels + j])
112 f.write(' %s\n' % entry)
117 def generate_1d_LUT_from_image(ramp_1d_path,
127 Parameter description.
132 Return value description.
135 if output_path is None:
136 output_path = ramp_1d_path + '.spi1d'
139 ramp = oiio.ImageInput.open(ramp_1d_path)
143 type = spec.format.basetype
146 channels = spec.nchannels
149 # Force data to be read as float. The Python API doesn't handle
150 # half-floats well yet.
152 data = ramp.read_image(type)
154 write_SPI_1d(output_path, min_value, max_value, data, width, channels)
157 def generate_3d_LUT_image(ramp_3d_path, resolution=32):
164 Parameter description.
169 Return value description.
172 args = ['--generate',
176 str(resolution * resolution),
179 lut_extract = Process(description='generate a 3d LUT image',
182 lut_extract.execute()
185 def generate_3d_LUT_from_image(ramp_3d_path, output_path=None, resolution=32):
192 Parameter description.
197 Return value description.
200 if output_path is None:
201 output_path = ramp_3d_path + '.spi3d'
207 str(resolution * resolution),
212 lut_extract = Process(description='extract a 3d LUT',
215 lut_extract.execute()
218 def apply_CTL_to_image(input_image,
224 aces_CTL_directory=None):
231 Parameter description.
236 Return value description.
239 if len(ctl_paths) > 0:
241 if aces_CTL_directory != None:
242 if os.path.split(aces_CTL_directory)[1] != 'utilities':
243 ctl_module_path = '%s/utilities' % aces_CTL_directory
245 ctl_module_path = aces_CTL_directory
246 ctlenv['CTL_MODULE_PATH'] = ctl_module_path
249 for ctl in ctl_paths:
250 args += ['-ctl', ctl]
252 # args += ['-verbose']
253 args += ['-input_scale', str(input_scale)]
254 args += ['-output_scale', str(output_scale)]
255 args += ['-global_param1', 'aIn', '1.0']
256 for key, value in global_params.iteritems():
257 args += ['-global_param1', key, str(value)]
258 args += [input_image]
259 args += [output_image]
261 # print('args : %s' % args)
263 ctlp = Process(description='a ctlrender process',
265 args=args, env=ctlenv)
270 def convert_bit_depth(input_image, output_image, depth):
277 Parameter description.
282 Return value description.
290 convert = Process(description='convert image bit depth',
296 def generate_1d_LUT_from_CTL(lut_path,
299 identity_LUT_bit_depth='half',
304 aces_CTL_directory=None,
313 Parameter description.
318 Return value description.
324 lut_path_base = os.path.splitext(lut_path)[0]
326 identity_LUT_image_float = lut_path_base + '.float.tiff'
327 generate_1d_LUT_image(identity_LUT_image_float,
332 if identity_LUT_bit_depth != 'half':
333 identity_LUT_image = lut_path_base + '.uint16.tiff'
334 convert_bit_depth(identity_LUT_image_float,
336 identity_LUT_bit_depth)
338 identity_LUT_image = identity_LUT_image_float
340 transformed_LUT_image = lut_path_base + '.transformed.exr'
341 apply_CTL_to_image(identity_LUT_image,
342 transformed_LUT_image,
349 generate_1d_LUT_from_image(transformed_LUT_image,
355 os.remove(identity_LUT_image)
356 if identity_LUT_image != identity_LUT_image_float:
357 os.remove(identity_LUT_image_float)
358 os.remove(transformed_LUT_image)
361 def correct_LUT_image(transformed_LUT_image,
370 Parameter description.
375 Return value description.
379 transformed = oiio.ImageInput.open(transformed_LUT_image)
382 transformed_spec = transformed.spec()
383 type = transformed_spec.format.basetype
384 width = transformed_spec.width
385 height = transformed_spec.height
386 channels = transformed_spec.nchannels
389 if width != lut_resolution * lut_resolution or height != lut_resolution:
390 print(('Correcting image as resolution is off. '
391 'Found %d x %d. Expected %d x %d') % (
394 lut_resolution * lut_resolution,
396 print('Generating %s' % corrected_LUT_image)
399 # We're going to generate a new correct image
402 # Get the source data
403 # Force data to be read as float. The Python API doesn't handle
404 # half-floats well yet.
406 source_data = transformed.read_image(type)
408 format = os.path.splitext(corrected_LUT_image)[1]
409 correct = oiio.ImageOutput.create(corrected_LUT_image)
412 correct_spec = oiio.ImageSpec()
413 correct_spec.set_format(oiio.FLOAT)
414 correct_spec.width = height
415 correct_spec.height = width
416 correct_spec.nchannels = channels
418 correct.open(corrected_LUT_image, correct_spec, oiio.Create)
420 dest_data = array.array('f',
421 ('\0' * correct_spec.width *
422 correct_spec.height *
423 correct_spec.nchannels * 4))
424 for j in range(0, correct_spec.height):
425 for i in range(0, correct_spec.width):
426 for c in range(0, correct_spec.nchannels):
428 dest_data[(correct_spec.nchannels *
429 correct_spec.width * j +
430 correct_spec.nchannels * i + c)] = (
431 source_data[correct_spec.nchannels *
432 correct_spec.width * j +
433 correct_spec.nchannels * i + c])
435 correct.write_image(correct_spec.format, dest_data)
438 # shutil.copy(transformedLUTImage, correctedLUTImage)
439 corrected_LUT_image = transformed_LUT_image
443 return corrected_LUT_image
446 def generate_3d_LUT_from_CTL(lut_path,
449 identity_LUT_bit_depth='half',
454 aces_CTL_directory=None):
461 Parameter description.
466 Return value description.
472 lut_path_base = os.path.splitext(lut_path)[0]
474 identity_LUT_image_float = lut_path_base + '.float.tiff'
475 generate_3d_LUT_image(identity_LUT_image_float, lut_resolution)
477 if identity_LUT_bit_depth != 'half':
478 identity_LUT_image = (lut_path_base +
480 identity_LUT_bit_depth +
482 convert_bit_depth(identity_LUT_image_float,
484 identity_LUT_bit_depth)
486 identity_LUT_image = identity_LUT_image_float
488 transformed_LUT_image = lut_path_base + '.transformed.exr'
489 apply_CTL_to_image(identity_LUT_image,
490 transformed_LUT_image,
497 corrected_LUT_image = lut_path_base + '.correct.exr'
498 corrected_LUT_image = correct_LUT_image(transformed_LUT_image,
502 generate_3d_LUT_from_image(corrected_LUT_image, lut_path, lut_resolution)
505 os.remove(identity_LUT_image)
506 if identity_LUT_image != identity_LUT_image_float:
507 os.remove(identity_LUT_image_float)
508 os.remove(transformed_LUT_image)
509 if corrected_LUT_image != transformed_LUT_image:
510 os.remove(corrected_LUT_image)
511 # os.remove(correctedLUTImage)
521 Parameter description.
526 Return value description.
531 p = optparse.OptionParser(
532 description='A utility to generate LUTs from CTL',
535 usage='%prog [options]')
537 p.add_option('--lut', '-l', type='string', default='')
538 p.add_option('--ctl', '-c', type='string', action='append')
539 p.add_option('--lut_resolution_1d', '', type='int', default=1024)
540 p.add_option('--lut_resolution_3d', '', type='int', default=33)
541 p.add_option('--ctlReleasePath', '-r', type='string', default='')
542 p.add_option('--bitDepth', '-b', type='string', default='float')
543 p.add_option('--keepTempImages', '', action='store_true')
544 p.add_option('--minValue', '', type='float', default=0.0)
545 p.add_option('--maxValue', '', type='float', default=1.0)
546 p.add_option('--inputScale', '', type='float', default=1.0)
547 p.add_option('--outputScale', '', type='float', default=1.0)
548 p.add_option('--ctlRenderParam', '-p', type='string', nargs=2,
551 p.add_option('--generate1d', '', action='store_true')
552 p.add_option('--generate3d', '', action='store_true')
554 options, arguments = p.parse_args()
561 lut_resolution_1d = options.lut_resolution_1d
562 lut_resolution_3d = options.lut_resolution_3d
563 min_value = options.minValue
564 max_value = options.maxValue
565 input_scale = options.inputScale
566 output_scale = options.outputScale
567 ctl_release_path = options.ctlReleasePath
568 generate_1d = options.generate1d is True
569 generate_3d = options.generate3d is True
570 bitdepth = options.bitDepth
571 cleanup = not options.keepTempImages
574 if options.ctlRenderParam != None:
575 for param in options.ctlRenderParam:
576 params[param[0]] = float(param[1])
579 args_start = sys.argv.index('--') + 1
580 args = sys.argv[args_start:]
582 args_start = len(sys.argv) + 1
585 # print('command line : \n%s\n' % ' '.join(sys.argv))
591 print('1D LUT generation options')
593 print('3D LUT generation options')
595 print('lut : %s' % lut)
596 print('ctls : %s' % ctls)
597 print('lut res 1d : %s' % lut_resolution_1d)
598 print('lut res 3d : %s' % lut_resolution_3d)
599 print('min value : %s' % min_value)
600 print('max value : %s' % max_value)
601 print('input scale : %s' % input_scale)
602 print('output scale : %s' % output_scale)
603 print('ctl render params : %s' % params)
604 print('ctl release path : %s' % ctl_release_path)
605 print('bit depth of input : %s' % bitdepth)
606 print('cleanup temp images : %s' % cleanup)
609 generate_1d_LUT_from_CTL(lut,
622 generate_3d_LUT_from_CTL(lut,
632 print(('\n\nNo LUT generated. '
633 'You must choose either 1D or 3D LUT generation\n\n'))
636 if __name__ == '__main__':