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, from_min, from_max, data, entries, channels):
85 Credit to *Alex Fry* for the original single channel version of the spi1d
91 Parameter description.
96 Return value description.
99 with open(filename, 'w') as fp:
100 fp.write('Version 1\n')
101 fp.write('From %f %f\n' % (from_min, from_max))
102 fp.write('Length %d\n' % entries)
103 fp.write('Components %d\n' % (min(3, channels)))
105 for i in range(0, entries):
107 for j in range(0, min(3, channels)):
108 entry = '%s %s' % (entry, data[i * channels + j])
109 fp.write(' %s\n' % entry)
113 def generate_1d_LUT_from_image(ramp_1d_path,
123 Parameter description.
128 Return value description.
131 if output_path is None:
132 output_path = '%s.%s' % (ramp_1d_path, 'spi1d')
134 ramp = oiio.ImageInput.open(ramp_1d_path)
138 channels = spec.nchannels
140 # Forcibly read data as float, the Python API doesn't handle half-float
143 data = ramp.read_image(type)
145 write_SPI_1d(output_path, min_value, max_value, data, width, channels)
148 def generate_3d_LUT_image(ramp_3d_path, resolution=32):
155 Parameter description.
160 Return value description.
163 args = ['--generate',
167 str(resolution * resolution),
170 lut_extract = Process(description='generate a 3d LUT image',
173 lut_extract.execute()
176 def generate_3d_LUT_from_image(ramp_3d_path, output_path=None, resolution=32):
183 Parameter description.
188 Return value description.
191 if output_path is None:
192 output_path = '%s.%s' % (ramp_3d_path, 'spi1d')
198 str(resolution * resolution),
203 lut_extract = Process(description='extract a 3d LUT',
206 lut_extract.execute()
209 def apply_CTL_to_image(input_image,
215 aces_ctl_directory=None):
222 Parameter description.
227 Return value description.
230 if ctl_paths is None:
232 if global_params is None:
235 if len(ctl_paths) > 0:
237 if aces_ctl_directory is not None:
238 if os.path.split(aces_ctl_directory)[1] != 'utilities':
239 ctl_module_path = os.path.join(aces_ctl_directory, 'utilities')
241 ctl_module_path = aces_ctl_directory
242 ctlenv['CTL_MODULE_PATH'] = ctl_module_path
245 for ctl in ctl_paths:
246 args += ['-ctl', ctl]
248 args += ['-input_scale', str(input_scale)]
249 args += ['-output_scale', str(output_scale)]
250 args += ['-global_param1', 'aIn', '1.0']
251 for key, value in global_params.iteritems():
252 args += ['-global_param1', key, str(value)]
253 args += [input_image]
254 args += [output_image]
256 ctlp = Process(description='a ctlrender process',
258 args=args, env=ctlenv)
263 def convert_bit_depth(input_image, output_image, depth):
270 Parameter description.
275 Return value description.
283 convert = Process(description='convert image bit depth',
289 def generate_1d_LUT_from_CTL(lut_path,
292 identity_LUT_bit_depth='half',
297 aces_ctl_directory=None,
306 Parameter description.
311 Return value description.
314 if global_params is None:
317 lut_path_base = os.path.splitext(lut_path)[0]
319 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
320 generate_1d_LUT_image(identity_LUT_image_float,
325 if identity_LUT_bit_depth != 'half':
326 identity_LUT_image = '%s.%s.%s' % (lut_path_base, 'uint16', 'tiff')
327 convert_bit_depth(identity_LUT_image_float,
329 identity_LUT_bit_depth)
331 identity_LUT_image = identity_LUT_image_float
333 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
334 apply_CTL_to_image(identity_LUT_image,
335 transformed_LUT_image,
342 generate_1d_LUT_from_image(transformed_LUT_image,
348 os.remove(identity_LUT_image)
349 if identity_LUT_image != identity_LUT_image_float:
350 os.remove(identity_LUT_image_float)
351 os.remove(transformed_LUT_image)
354 def correct_LUT_image(transformed_LUT_image,
363 Parameter description.
368 Return value description.
371 transformed = oiio.ImageInput.open(transformed_LUT_image)
373 transformed_spec = transformed.spec()
374 width = transformed_spec.width
375 height = transformed_spec.height
376 channels = transformed_spec.nchannels
378 if width != lut_resolution * lut_resolution or height != lut_resolution:
379 print(('Correcting image as resolution is off. '
380 'Found %d x %d. Expected %d x %d') % (
383 lut_resolution * lut_resolution,
385 print('Generating %s' % corrected_LUT_image)
387 # Forcibly read data as float, the Python API doesn't handle half-float
390 source_data = transformed.read_image(type)
392 correct = oiio.ImageOutput.create(corrected_LUT_image)
394 correct_spec = oiio.ImageSpec()
395 correct_spec.set_format(oiio.FLOAT)
396 correct_spec.width = height
397 correct_spec.height = width
398 correct_spec.nchannels = channels
400 correct.open(corrected_LUT_image, correct_spec, oiio.Create)
402 dest_data = array.array('f',
403 ('\0' * correct_spec.width *
404 correct_spec.height *
405 correct_spec.nchannels * 4))
406 for j in range(0, correct_spec.height):
407 for i in range(0, correct_spec.width):
408 for c in range(0, correct_spec.nchannels):
409 dest_data[(correct_spec.nchannels *
410 correct_spec.width * j +
411 correct_spec.nchannels * i + c)] = (
412 source_data[correct_spec.nchannels *
413 correct_spec.width * j +
414 correct_spec.nchannels * i + c])
416 correct.write_image(correct_spec.format, dest_data)
419 # shutil.copy(transformedLUTImage, correctedLUTImage)
420 corrected_LUT_image = transformed_LUT_image
424 return corrected_LUT_image
427 def generate_3d_LUT_from_CTL(lut_path,
430 identity_LUT_bit_depth='half',
435 aces_ctl_directory=None):
442 Parameter description.
447 Return value description.
450 if global_params is None:
453 lut_path_base = os.path.splitext(lut_path)[0]
455 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
456 generate_3d_LUT_image(identity_LUT_image_float, lut_resolution)
458 if identity_LUT_bit_depth != 'half':
459 identity_LUT_image = '%s.%s.%s' % (lut_path_base,
460 identity_LUT_bit_depth,
462 convert_bit_depth(identity_LUT_image_float,
464 identity_LUT_bit_depth)
466 identity_LUT_image = identity_LUT_image_float
468 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
469 apply_CTL_to_image(identity_LUT_image,
470 transformed_LUT_image,
477 corrected_LUT_image = '%s.%s.%s' % (lut_path_base, 'correct', 'exr')
478 corrected_LUT_image = correct_LUT_image(transformed_LUT_image,
482 generate_3d_LUT_from_image(corrected_LUT_image, lut_path, lut_resolution)
485 os.remove(identity_LUT_image)
486 if identity_LUT_image != identity_LUT_image_float:
487 os.remove(identity_LUT_image_float)
488 os.remove(transformed_LUT_image)
489 if corrected_LUT_image != transformed_LUT_image:
490 os.remove(corrected_LUT_image)
500 Parameter description.
505 Return value description.
510 p = optparse.OptionParser(
511 description='A utility to generate LUTs from CTL',
514 usage='%prog [options]')
516 p.add_option('--lut', '-l', type='string', default='')
517 p.add_option('--ctl', '-c', type='string', action='append')
518 p.add_option('--lutResolution1d', '', type='int', default=1024)
519 p.add_option('--lutResolution3d', '', type='int', default=33)
520 p.add_option('--ctlReleasePath', '-r', type='string', default='')
521 p.add_option('--bitDepth', '-b', type='string', default='float')
522 p.add_option('--keepTempImages', '', action='store_true')
523 p.add_option('--minValue', '', type='float', default=0)
524 p.add_option('--maxValue', '', type='float', default=1)
525 p.add_option('--inputScale', '', type='float', default=1)
526 p.add_option('--outputScale', '', type='float', default=1)
527 p.add_option('--ctlRenderParam', '-p', type='string', nargs=2,
530 p.add_option('--generate1d', '', action='store_true')
531 p.add_option('--generate3d', '', action='store_true')
533 options, arguments = p.parse_args()
537 lut_resolution_1d = options.lut_resolution_1d
538 lut_resolution_3d = options.lut_resolution_3d
539 min_value = options.minValue
540 max_value = options.maxValue
541 input_scale = options.inputScale
542 output_scale = options.outputScale
543 ctl_release_path = options.ctlReleasePath
544 generate_1d = options.generate1d is True
545 generate_3d = options.generate3d is True
546 bit_depth = options.bitDepth
547 cleanup = not options.keepTempImages
550 if options.ctlRenderParam is not None:
551 for param in options.ctlRenderParam:
552 params[param[0]] = float(param[1])
555 args_start = sys.argv.index('--') + 1
556 args = sys.argv[args_start:]
558 args_start = len(sys.argv) + 1
562 print('1D LUT generation options')
564 print('3D LUT generation options')
566 print('lut : %s' % lut)
567 print('ctls : %s' % ctls)
568 print('lut res 1d : %s' % lut_resolution_1d)
569 print('lut res 3d : %s' % lut_resolution_3d)
570 print('min value : %s' % min_value)
571 print('max value : %s' % max_value)
572 print('input scale : %s' % input_scale)
573 print('output scale : %s' % output_scale)
574 print('ctl render params : %s' % params)
575 print('ctl release path : %s' % ctl_release_path)
576 print('bit depth of input : %s' % bit_depth)
577 print('cleanup temp images : %s' % cleanup)
580 generate_1d_LUT_from_CTL(lut,
593 generate_3d_LUT_from_CTL(lut,
603 print(('\n\nNo LUT generated. '
604 'You must choose either 1D or 3D LUT generation\n\n'))
607 if __name__ == '__main__':