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 write_CSP_1d(filename,
136 Parameter description.
141 Return value description.
144 # May want to use fewer components than there are channels in the data
145 # Most commonly used for single channel LUTs
146 components = min(3, components, channels)
148 with open(filename, 'w') as fp:
149 fp.write('CSPLUTV100\n')
152 fp.write('BEGIN METADATA\n')
153 fp.write('END METADATA\n')
158 fp.write('%f %f\n' % (from_min, from_max))
159 fp.write('0.0 1.0\n')
161 fp.write('%f %f\n' % (from_min, from_max))
162 fp.write('0.0 1.0\n')
164 fp.write('%f %f\n' % (from_min, from_max))
165 fp.write('0.0 1.0\n')
169 fp.write('%d\n' % entries)
171 for i in range(0, entries):
174 entry = '%s %s' % (entry, data[i * channels])
175 fp.write('%s\n' % entry)
177 for i in range(entries):
179 for j in range(components):
180 entry = '%s %s' % (entry, data[i * channels + j])
181 fp.write('%s\n' % entry)
184 def write_1d(filename,
198 Parameter description.
203 Return value description.
206 ocioFormatsToExtensions = {'cinespace' : 'csp',
212 if format in ocioFormatsToExtensions:
213 if ocioFormatsToExtensions[format] == 'csp':
214 write_CSP_1d(filename,
222 write_SPI_1d(filename,
230 def generate_1d_LUT_from_image(ramp_1d_path,
242 Parameter description.
247 Return value description.
250 if output_path is None:
251 output_path = '%s.%s' % (ramp_1d_path, 'spi1d')
253 ramp = oiio.ImageInput.open(ramp_1d_path)
255 ramp_spec = ramp.spec()
256 ramp_width = ramp_spec.width
257 ramp_channels = ramp_spec.nchannels
259 # Forcibly read data as float, the Python API doesn't handle half-float
262 ramp_data = ramp.read_image(type)
264 write_1d(output_path, min_value, max_value,
265 ramp_data, ramp_width, ramp_channels, channels, format)
268 def generate_3d_LUT_image(ramp_3d_path, resolution=32):
275 Parameter description.
280 Return value description.
283 args = ['--generate',
287 str(resolution * resolution),
290 lut_extract = Process(description='generate a 3d LUT image',
293 lut_extract.execute()
296 def generate_3d_LUT_from_image(ramp_3d_path,
306 Parameter description.
311 Return value description.
314 if output_path is None:
315 output_path = '%s.%s' % (ramp_3d_path, 'spi3d')
317 ocioFormatsToExtensions = {'cinespace' : 'csp',
323 if format == 'spi3d' or not (format in ocioFormatsToExtensions):
324 # Extract a spi3d LUT
329 str(resolution * resolution),
334 lut_extract = Process(description='extract a 3d LUT',
337 lut_extract.execute()
340 output_path_spi3d = '%s.%s' % (output_path, 'spi3d')
342 # Extract a spi3d LUT
347 str(resolution * resolution),
352 lut_extract = Process(description='extract a 3d LUT',
355 lut_extract.execute()
357 # Convert to a different format
363 lut_convert = Process(description='convert a 3d LUT',
366 lut_convert.execute()
369 def apply_CTL_to_image(input_image,
375 aces_ctl_directory=None):
382 Parameter description.
387 Return value description.
390 if ctl_paths is None:
392 if global_params is None:
395 if len(ctl_paths) > 0:
397 if aces_ctl_directory is not None:
398 if os.path.split(aces_ctl_directory)[1] != 'utilities':
399 ctl_module_path = os.path.join(aces_ctl_directory, 'utilities')
401 ctl_module_path = aces_ctl_directory
402 ctlenv['CTL_MODULE_PATH'] = ctl_module_path
405 for ctl in ctl_paths:
406 args += ['-ctl', ctl]
408 args += ['-input_scale', str(input_scale)]
409 args += ['-output_scale', str(output_scale)]
410 args += ['-global_param1', 'aIn', '1.0']
411 for key, value in global_params.iteritems():
412 args += ['-global_param1', key, str(value)]
413 args += [input_image]
414 args += [output_image]
416 ctlp = Process(description='a ctlrender process',
418 args=args, env=ctlenv)
423 def convert_bit_depth(input_image, output_image, depth):
430 Parameter description.
435 Return value description.
443 convert = Process(description='convert image bit depth',
449 def generate_1d_LUT_from_CTL(lut_path,
452 identity_LUT_bit_depth='half',
457 aces_ctl_directory=None,
468 Parameter description.
473 Return value description.
476 if global_params is None:
479 lut_path_base = os.path.splitext(lut_path)[0]
481 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
482 generate_1d_LUT_image(identity_LUT_image_float,
487 if identity_LUT_bit_depth not in ['half', 'float']:
488 identity_LUT_image = '%s.%s.%s' % (lut_path_base, 'uint16', 'tiff')
489 convert_bit_depth(identity_LUT_image_float,
491 identity_LUT_bit_depth)
493 identity_LUT_image = identity_LUT_image_float
495 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
496 apply_CTL_to_image(identity_LUT_image,
497 transformed_LUT_image,
504 generate_1d_LUT_from_image(transformed_LUT_image,
512 os.remove(identity_LUT_image)
513 if identity_LUT_image != identity_LUT_image_float:
514 os.remove(identity_LUT_image_float)
515 os.remove(transformed_LUT_image)
518 def correct_LUT_image(transformed_LUT_image,
527 Parameter description.
532 Return value description.
535 transformed = oiio.ImageInput.open(transformed_LUT_image)
537 transformed_spec = transformed.spec()
538 width = transformed_spec.width
539 height = transformed_spec.height
540 channels = transformed_spec.nchannels
542 if width != lut_resolution * lut_resolution or height != lut_resolution:
543 print(('Correcting image as resolution is off. '
544 'Found %d x %d. Expected %d x %d') % (
547 lut_resolution * lut_resolution,
549 print('Generating %s' % corrected_LUT_image)
551 # Forcibly read data as float, the Python API doesn't handle half-float
554 source_data = transformed.read_image(type)
556 correct = oiio.ImageOutput.create(corrected_LUT_image)
558 correct_spec = oiio.ImageSpec()
559 correct_spec.set_format(oiio.FLOAT)
560 correct_spec.width = height
561 correct_spec.height = width
562 correct_spec.nchannels = channels
564 correct.open(corrected_LUT_image, correct_spec, oiio.Create)
566 dest_data = array.array('f',
567 ('\0' * correct_spec.width *
568 correct_spec.height *
569 correct_spec.nchannels * 4))
570 for j in range(0, correct_spec.height):
571 for i in range(0, correct_spec.width):
572 for c in range(0, correct_spec.nchannels):
573 dest_data[(correct_spec.nchannels *
574 correct_spec.width * j +
575 correct_spec.nchannels * i + c)] = (
576 source_data[correct_spec.nchannels *
577 correct_spec.width * j +
578 correct_spec.nchannels * i + c])
580 correct.write_image(correct_spec.format, dest_data)
583 # shutil.copy(transformedLUTImage, correctedLUTImage)
584 corrected_LUT_image = transformed_LUT_image
588 return corrected_LUT_image
591 def generate_3d_LUT_from_CTL(lut_path,
594 identity_LUT_bit_depth='half',
599 aces_ctl_directory=None,
607 Parameter description.
612 Return value description.
615 if global_params is None:
618 lut_path_base = os.path.splitext(lut_path)[0]
620 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
621 generate_3d_LUT_image(identity_LUT_image_float, lut_resolution)
623 if identity_LUT_bit_depth not in ['half', 'float']:
624 identity_LUT_image = '%s.%s.%s' % (lut_path_base,
625 identity_LUT_bit_depth,
627 convert_bit_depth(identity_LUT_image_float,
629 identity_LUT_bit_depth)
631 identity_LUT_image = identity_LUT_image_float
633 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
634 apply_CTL_to_image(identity_LUT_image,
635 transformed_LUT_image,
642 corrected_LUT_image = '%s.%s.%s' % (lut_path_base, 'correct', 'exr')
643 corrected_LUT_image = correct_LUT_image(transformed_LUT_image,
647 generate_3d_LUT_from_image(corrected_LUT_image,
653 os.remove(identity_LUT_image)
654 if identity_LUT_image != identity_LUT_image_float:
655 os.remove(identity_LUT_image_float)
656 os.remove(transformed_LUT_image)
657 if corrected_LUT_image != transformed_LUT_image:
658 os.remove(corrected_LUT_image)
659 if format != 'spi3d':
660 lut_path_spi3d = '%s.%s' % (lut_path, 'spi3d')
661 os.remove(lut_path_spi3d)
670 Parameter description.
675 Return value description.
680 p = optparse.OptionParser(
681 description='A utility to generate LUTs from CTL',
684 usage='%prog [options]')
686 p.add_option('--lut', '-l', type='string', default='')
687 p.add_option('--format', '-f', type='string', default='')
688 p.add_option('--ctl', '-c', type='string', action='append')
689 p.add_option('--lutResolution1d', '', type='int', default=1024)
690 p.add_option('--lutResolution3d', '', type='int', default=33)
691 p.add_option('--ctlReleasePath', '-r', type='string', default='')
692 p.add_option('--bitDepth', '-b', type='string', default='float')
693 p.add_option('--keepTempImages', '', action='store_true')
694 p.add_option('--minValue', '', type='float', default=0)
695 p.add_option('--maxValue', '', type='float', default=1)
696 p.add_option('--inputScale', '', type='float', default=1)
697 p.add_option('--outputScale', '', type='float', default=1)
698 p.add_option('--ctlRenderParam', '-p', type='string', nargs=2,
701 p.add_option('--generate1d', '', action='store_true')
702 p.add_option('--generate3d', '', action='store_true')
704 options, arguments = p.parse_args()
707 format = options.format
709 lut_resolution_1d = options.lutResolution1d
710 lut_resolution_3d = options.lutResolution3d
711 min_value = options.minValue
712 max_value = options.maxValue
713 input_scale = options.inputScale
714 output_scale = options.outputScale
715 ctl_release_path = options.ctlReleasePath
716 generate_1d = options.generate1d is True
717 generate_3d = options.generate3d is True
718 bit_depth = options.bitDepth
719 cleanup = not options.keepTempImages
722 if options.ctlRenderParam is not None:
723 for param in options.ctlRenderParam:
724 params[param[0]] = float(param[1])
727 args_start = sys.argv.index('--') + 1
728 args = sys.argv[args_start:]
730 args_start = len(sys.argv) + 1
734 print('1D LUT generation options')
736 print('3D LUT generation options')
738 print('lut : %s' % lut)
739 print('format : %s' % format)
740 print('ctls : %s' % ctls)
741 print('lut res 1d : %s' % lut_resolution_1d)
742 print('lut res 3d : %s' % lut_resolution_3d)
743 print('min value : %s' % min_value)
744 print('max value : %s' % max_value)
745 print('input scale : %s' % input_scale)
746 print('output scale : %s' % output_scale)
747 print('ctl render params : %s' % params)
748 print('ctl release path : %s' % ctl_release_path)
749 print('bit depth of input : %s' % bit_depth)
750 print('cleanup temp images : %s' % cleanup)
753 generate_1d_LUT_from_CTL(lut,
767 generate_3d_LUT_from_CTL(lut,
778 print(('\n\nNo LUT generated. '
779 'You must choose either 1D or 3D LUT generation\n\n'))
782 if __name__ == '__main__':