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 ctl_paths is None:
230 if global_params is None:
233 if len(ctl_paths) > 0:
235 if aces_CTL_directory is not None:
236 if os.path.split(aces_CTL_directory)[1] != 'utilities':
237 ctl_module_path = os.path.join(aces_CTL_directory, 'utilities')
239 ctl_module_path = aces_CTL_directory
240 ctlenv['CTL_MODULE_PATH'] = ctl_module_path
243 for ctl in ctl_paths:
244 args += ['-ctl', ctl]
246 args += ['-input_scale', str(input_scale)]
247 args += ['-output_scale', str(output_scale)]
248 args += ['-global_param1', 'aIn', '1.0']
249 for key, value in global_params.iteritems():
250 args += ['-global_param1', key, str(value)]
251 args += [input_image]
252 args += [output_image]
254 ctlp = Process(description='a ctlrender process',
256 args=args, env=ctlenv)
261 def convert_bit_depth(input_image, output_image, depth):
268 Parameter description.
273 Return value description.
281 convert = Process(description='convert image bit depth',
287 def generate_1d_LUT_from_CTL(lut_path,
290 identity_LUT_bit_depth='half',
295 aces_CTL_directory=None,
304 Parameter description.
309 Return value description.
312 if global_params is None:
315 lut_path_base = os.path.splitext(lut_path)[0]
317 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
318 generate_1d_LUT_image(identity_LUT_image_float,
323 if identity_LUT_bit_depth != 'half':
324 identity_LUT_image = '%s.%s.%s' % (lut_path_base, 'uint16', 'tiff')
325 convert_bit_depth(identity_LUT_image_float,
327 identity_LUT_bit_depth)
329 identity_LUT_image = identity_LUT_image_float
331 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
332 apply_CTL_to_image(identity_LUT_image,
333 transformed_LUT_image,
340 generate_1d_LUT_from_image(transformed_LUT_image,
346 os.remove(identity_LUT_image)
347 if identity_LUT_image != identity_LUT_image_float:
348 os.remove(identity_LUT_image_float)
349 os.remove(transformed_LUT_image)
352 def correct_LUT_image(transformed_LUT_image,
361 Parameter description.
366 Return value description.
369 transformed = oiio.ImageInput.open(transformed_LUT_image)
371 transformed_spec = transformed.spec()
372 width = transformed_spec.width
373 height = transformed_spec.height
374 channels = transformed_spec.nchannels
376 if width != lut_resolution * lut_resolution or height != lut_resolution:
377 print(('Correcting image as resolution is off. '
378 'Found %d x %d. Expected %d x %d') % (
381 lut_resolution * lut_resolution,
383 print('Generating %s' % corrected_LUT_image)
385 # Forcibly read data as float, the Python API doesn't handle half-float
388 source_data = transformed.read_image(type)
390 correct = oiio.ImageOutput.create(corrected_LUT_image)
392 correct_spec = oiio.ImageSpec()
393 correct_spec.set_format(oiio.FLOAT)
394 correct_spec.width = height
395 correct_spec.height = width
396 correct_spec.nchannels = channels
398 correct.open(corrected_LUT_image, correct_spec, oiio.Create)
400 dest_data = array.array('f',
401 ('\0' * correct_spec.width *
402 correct_spec.height *
403 correct_spec.nchannels * 4))
404 for j in range(0, correct_spec.height):
405 for i in range(0, correct_spec.width):
406 for c in range(0, correct_spec.nchannels):
407 dest_data[(correct_spec.nchannels *
408 correct_spec.width * j +
409 correct_spec.nchannels * i + c)] = (
410 source_data[correct_spec.nchannels *
411 correct_spec.width * j +
412 correct_spec.nchannels * i + c])
414 correct.write_image(correct_spec.format, dest_data)
417 # shutil.copy(transformedLUTImage, correctedLUTImage)
418 corrected_LUT_image = transformed_LUT_image
422 return corrected_LUT_image
425 def generate_3d_LUT_from_CTL(lut_path,
428 identity_LUT_bit_depth='half',
433 aces_CTL_directory=None):
440 Parameter description.
445 Return value description.
448 if global_params is None:
451 lut_path_base = os.path.splitext(lut_path)[0]
453 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
454 generate_3d_LUT_image(identity_LUT_image_float, lut_resolution)
456 if identity_LUT_bit_depth != 'half':
457 identity_LUT_image = '%s.%s.%s' % (lut_path_base,
458 identity_LUT_bit_depth,
460 convert_bit_depth(identity_LUT_image_float,
462 identity_LUT_bit_depth)
464 identity_LUT_image = identity_LUT_image_float
466 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
467 apply_CTL_to_image(identity_LUT_image,
468 transformed_LUT_image,
475 corrected_LUT_image = '%s.%s.%s' % (lut_path_base, 'correct', 'exr')
476 corrected_LUT_image = correct_LUT_image(transformed_LUT_image,
480 generate_3d_LUT_from_image(corrected_LUT_image, lut_path, lut_resolution)
483 os.remove(identity_LUT_image)
484 if identity_LUT_image != identity_LUT_image_float:
485 os.remove(identity_LUT_image_float)
486 os.remove(transformed_LUT_image)
487 if corrected_LUT_image != transformed_LUT_image:
488 os.remove(corrected_LUT_image)
498 Parameter description.
503 Return value description.
508 p = optparse.OptionParser(
509 description='A utility to generate LUTs from CTL',
512 usage='%prog [options]')
514 p.add_option('--lut', '-l', type='string', default='')
515 p.add_option('--ctl', '-c', type='string', action='append')
516 p.add_option('--lutResolution1d', '', type='int', default=1024)
517 p.add_option('--lutResolution3d', '', type='int', default=33)
518 p.add_option('--ctlReleasePath', '-r', type='string', default='')
519 p.add_option('--bitDepth', '-b', type='string', default='float')
520 p.add_option('--keepTempImages', '', action='store_true')
521 p.add_option('--minValue', '', type='float', default=0.0)
522 p.add_option('--maxValue', '', type='float', default=1.0)
523 p.add_option('--inputScale', '', type='float', default=1.0)
524 p.add_option('--outputScale', '', type='float', default=1.0)
525 p.add_option('--ctlRenderParam', '-p', type='string', nargs=2,
528 p.add_option('--generate1d', '', action='store_true')
529 p.add_option('--generate3d', '', action='store_true')
531 options, arguments = p.parse_args()
535 lut_resolution_1d = options.lut_resolution_1d
536 lut_resolution_3d = options.lut_resolution_3d
537 min_value = options.minValue
538 max_value = options.maxValue
539 input_scale = options.inputScale
540 output_scale = options.outputScale
541 ctl_release_path = options.ctlReleasePath
542 generate_1d = options.generate1d is True
543 generate_3d = options.generate3d is True
544 bit_depth = options.bitDepth
545 cleanup = not options.keepTempImages
548 if options.ctlRenderParam is not None:
549 for param in options.ctlRenderParam:
550 params[param[0]] = float(param[1])
553 args_start = sys.argv.index('--') + 1
554 args = sys.argv[args_start:]
556 args_start = len(sys.argv) + 1
560 print('1D LUT generation options')
562 print('3D LUT generation options')
564 print('lut : %s' % lut)
565 print('ctls : %s' % ctls)
566 print('lut res 1d : %s' % lut_resolution_1d)
567 print('lut res 3d : %s' % lut_resolution_3d)
568 print('min value : %s' % min_value)
569 print('max value : %s' % max_value)
570 print('input scale : %s' % input_scale)
571 print('output scale : %s' % output_scale)
572 print('ctl render params : %s' % params)
573 print('ctl release path : %s' % ctl_release_path)
574 print('bit depth of input : %s' % bit_depth)
575 print('cleanup temp images : %s' % cleanup)
578 generate_1d_LUT_from_CTL(lut,
591 generate_3d_LUT_from_CTL(lut,
601 print(('\n\nNo LUT generated. '
602 'You must choose either 1D or 3D LUT generation\n\n'))
605 if __name__ == '__main__':