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 ramp = oiio.ImageOutput.create(ramp_1d_path)
57 spec = oiio.ImageSpec()
58 spec.set_format(oiio.FLOAT)
59 # spec.format.basetype = oiio.FLOAT
60 spec.width = resolution
64 ramp.open(ramp_1d_path, spec, oiio.Create)
66 data = array.array('f',
67 '\0' * spec.width * spec.height * spec.nchannels * 4)
68 for i in range(resolution):
69 value = float(i) / (resolution - 1) * (
70 max_value - min_value) + min_value
71 data[i * spec.nchannels + 0] = value
72 data[i * spec.nchannels + 1] = value
73 data[i * spec.nchannels + 2] = value
75 ramp.write_image(spec.format, data)
79 def write_SPI_1d(filename, from_min, from_max, data, entries, channels):
83 Credit to *Alex Fry* for the original single channel version of the spi1d
89 Parameter description.
94 Return value description.
97 with open(filename, 'w') as fp:
98 fp.write('Version 1\n')
99 fp.write('From %f %f\n' % (from_min, from_max))
100 fp.write('Length %d\n' % entries)
101 fp.write('Components %d\n' % (min(3, channels)))
103 for i in range(0, entries):
105 for j in range(0, min(3, channels)):
106 entry = '%s %s' % (entry, data[i * channels + j])
107 fp.write(' %s\n' % entry)
111 def generate_1d_LUT_from_image(ramp_1d_path,
121 Parameter description.
126 Return value description.
129 if output_path is None:
130 output_path = '%s.%s' % (ramp_1d_path, 'spi1d')
132 ramp = oiio.ImageInput.open(ramp_1d_path)
136 channels = spec.nchannels
138 # Forcibly read data as float, the Python API doesn't handle half-float
141 data = ramp.read_image(type)
143 write_SPI_1d(output_path, min_value, max_value, data, width, channels)
146 def generate_3d_LUT_image(ramp_3d_path, resolution=32):
153 Parameter description.
158 Return value description.
161 args = ['--generate',
165 str(resolution * resolution),
168 lut_extract = Process(description='generate a 3d LUT image',
171 lut_extract.execute()
174 def generate_3d_LUT_from_image(ramp_3d_path, output_path=None, resolution=32):
181 Parameter description.
186 Return value description.
189 if output_path is None:
190 output_path = '%s.%s' % (ramp_3d_path, 'spi1d')
196 str(resolution * resolution),
201 lut_extract = Process(description='extract a 3d LUT',
204 lut_extract.execute()
207 def apply_CTL_to_image(input_image,
213 aces_CTL_directory=None):
220 Parameter description.
225 Return value description.
228 if len(ctl_paths) > 0:
230 if aces_CTL_directory is not None:
231 if os.path.split(aces_CTL_directory)[1] != 'utilities':
232 ctl_module_path = os.path.join(aces_CTL_directory, 'utilities')
234 ctl_module_path = aces_CTL_directory
235 ctlenv['CTL_MODULE_PATH'] = ctl_module_path
238 for ctl in ctl_paths:
239 args += ['-ctl', ctl]
241 args += ['-input_scale', str(input_scale)]
242 args += ['-output_scale', str(output_scale)]
243 args += ['-global_param1', 'aIn', '1.0']
244 for key, value in global_params.iteritems():
245 args += ['-global_param1', key, str(value)]
246 args += [input_image]
247 args += [output_image]
249 ctlp = Process(description='a ctlrender process',
251 args=args, env=ctlenv)
256 def convert_bit_depth(input_image, output_image, depth):
263 Parameter description.
268 Return value description.
276 convert = Process(description='convert image bit depth',
282 def generate_1d_LUT_from_CTL(lut_path,
285 identity_LUT_bit_depth='half',
290 aces_CTL_directory=None,
299 Parameter description.
304 Return value description.
307 lut_path_base = os.path.splitext(lut_path)[0]
309 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
310 generate_1d_LUT_image(identity_LUT_image_float,
315 if identity_LUT_bit_depth != 'half':
316 identity_LUT_image = '%s.%s.%s' % (lut_path_base, 'uint16', 'tiff')
317 convert_bit_depth(identity_LUT_image_float,
319 identity_LUT_bit_depth)
321 identity_LUT_image = identity_LUT_image_float
323 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
324 apply_CTL_to_image(identity_LUT_image,
325 transformed_LUT_image,
332 generate_1d_LUT_from_image(transformed_LUT_image,
338 os.remove(identity_LUT_image)
339 if identity_LUT_image != identity_LUT_image_float:
340 os.remove(identity_LUT_image_float)
341 os.remove(transformed_LUT_image)
344 def correct_LUT_image(transformed_LUT_image,
353 Parameter description.
358 Return value description.
361 transformed = oiio.ImageInput.open(transformed_LUT_image)
363 transformed_spec = transformed.spec()
364 width = transformed_spec.width
365 height = transformed_spec.height
366 channels = transformed_spec.nchannels
368 if width != lut_resolution * lut_resolution or height != lut_resolution:
369 print(('Correcting image as resolution is off. '
370 'Found %d x %d. Expected %d x %d') % (
373 lut_resolution * lut_resolution,
375 print('Generating %s' % corrected_LUT_image)
377 # Forcibly read data as float, the Python API doesn't handle half-float
380 source_data = transformed.read_image(type)
382 correct = oiio.ImageOutput.create(corrected_LUT_image)
384 correct_spec = oiio.ImageSpec()
385 correct_spec.set_format(oiio.FLOAT)
386 correct_spec.width = height
387 correct_spec.height = width
388 correct_spec.nchannels = channels
390 correct.open(corrected_LUT_image, correct_spec, oiio.Create)
392 dest_data = array.array('f',
393 ('\0' * correct_spec.width *
394 correct_spec.height *
395 correct_spec.nchannels * 4))
396 for j in range(0, correct_spec.height):
397 for i in range(0, correct_spec.width):
398 for c in range(0, correct_spec.nchannels):
399 dest_data[(correct_spec.nchannels *
400 correct_spec.width * j +
401 correct_spec.nchannels * i + c)] = (
402 source_data[correct_spec.nchannels *
403 correct_spec.width * j +
404 correct_spec.nchannels * i + c])
406 correct.write_image(correct_spec.format, dest_data)
409 # shutil.copy(transformedLUTImage, correctedLUTImage)
410 corrected_LUT_image = transformed_LUT_image
414 return corrected_LUT_image
417 def generate_3d_LUT_from_CTL(lut_path,
420 identity_LUT_bit_depth='half',
425 aces_CTL_directory=None):
432 Parameter description.
437 Return value description.
440 lut_path_base = os.path.splitext(lut_path)[0]
442 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
443 generate_3d_LUT_image(identity_LUT_image_float, lut_resolution)
445 if identity_LUT_bit_depth != 'half':
446 identity_LUT_image = '%s.%s.%s' % (lut_path_base,
447 identity_LUT_bit_depth,
449 convert_bit_depth(identity_LUT_image_float,
451 identity_LUT_bit_depth)
453 identity_LUT_image = identity_LUT_image_float
455 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
456 apply_CTL_to_image(identity_LUT_image,
457 transformed_LUT_image,
464 corrected_LUT_image = '%s.%s.%s' % (lut_path_base, 'correct', 'exr')
465 corrected_LUT_image = correct_LUT_image(transformed_LUT_image,
469 generate_3d_LUT_from_image(corrected_LUT_image, lut_path, lut_resolution)
472 os.remove(identity_LUT_image)
473 if identity_LUT_image != identity_LUT_image_float:
474 os.remove(identity_LUT_image_float)
475 os.remove(transformed_LUT_image)
476 if corrected_LUT_image != transformed_LUT_image:
477 os.remove(corrected_LUT_image)
487 Parameter description.
492 Return value description.
497 p = optparse.OptionParser(
498 description='A utility to generate LUTs from CTL',
501 usage='%prog [options]')
503 p.add_option('--lut', '-l', type='string', default='')
504 p.add_option('--ctl', '-c', type='string', action='append')
505 p.add_option('--lutResolution1d', '', type='int', default=1024)
506 p.add_option('--lutResolution3d', '', type='int', default=33)
507 p.add_option('--ctlReleasePath', '-r', type='string', default='')
508 p.add_option('--bitDepth', '-b', type='string', default='float')
509 p.add_option('--keepTempImages', '', action='store_true')
510 p.add_option('--minValue', '', type='float', default=0.0)
511 p.add_option('--maxValue', '', type='float', default=1.0)
512 p.add_option('--inputScale', '', type='float', default=1.0)
513 p.add_option('--outputScale', '', type='float', default=1.0)
514 p.add_option('--ctlRenderParam', '-p', type='string', nargs=2,
517 p.add_option('--generate1d', '', action='store_true')
518 p.add_option('--generate3d', '', action='store_true')
520 options, arguments = p.parse_args()
524 lut_resolution_1d = options.lut_resolution_1d
525 lut_resolution_3d = options.lut_resolution_3d
526 min_value = options.minValue
527 max_value = options.maxValue
528 input_scale = options.inputScale
529 output_scale = options.outputScale
530 ctl_release_path = options.ctlReleasePath
531 generate_1d = options.generate1d is True
532 generate_3d = options.generate3d is True
533 bit_depth = options.bitDepth
534 cleanup = not options.keepTempImages
537 if options.ctlRenderParam is not None:
538 for param in options.ctlRenderParam:
539 params[param[0]] = float(param[1])
542 args_start = sys.argv.index('--') + 1
543 args = sys.argv[args_start:]
545 args_start = len(sys.argv) + 1
549 print('1D LUT generation options')
551 print('3D LUT generation options')
553 print('lut : %s' % lut)
554 print('ctls : %s' % ctls)
555 print('lut res 1d : %s' % lut_resolution_1d)
556 print('lut res 3d : %s' % lut_resolution_3d)
557 print('min value : %s' % min_value)
558 print('max value : %s' % max_value)
559 print('input scale : %s' % input_scale)
560 print('output scale : %s' % output_scale)
561 print('ctl render params : %s' % params)
562 print('ctl release path : %s' % ctl_release_path)
563 print('bit depth of input : %s' % bit_depth)
564 print('cleanup temp images : %s' % cleanup)
567 generate_1d_LUT_from_CTL(lut,
580 generate_3d_LUT_from_CTL(lut,
590 print(('\n\nNo LUT generated. '
591 'You must choose either 1D or 3D LUT generation\n\n'))
594 if __name__ == '__main__':