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 with open(filename, 'w') as fp:
103 fp.write('Version 1\n')
104 fp.write('From %f %f\n' % (from_min, from_max))
105 fp.write('Length %d\n' % entries)
106 fp.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 fp.write(' %s\n' % entry)
116 def generate_1d_LUT_from_image(ramp_1d_path,
126 Parameter description.
131 Return value description.
134 if output_path is None:
135 output_path = '%s.%s' % (ramp_1d_path, 'spi1d')
138 ramp = oiio.ImageInput.open(ramp_1d_path)
142 type = spec.format.basetype
145 channels = spec.nchannels
148 # Force data to be read as float. The Python API doesn't handle
149 # half-floats well yet.
151 data = ramp.read_image(type)
153 write_SPI_1d(output_path, min_value, max_value, data, width, channels)
156 def generate_3d_LUT_image(ramp_3d_path, resolution=32):
163 Parameter description.
168 Return value description.
171 args = ['--generate',
175 str(resolution * resolution),
178 lut_extract = Process(description='generate a 3d LUT image',
181 lut_extract.execute()
184 def generate_3d_LUT_from_image(ramp_3d_path, output_path=None, resolution=32):
191 Parameter description.
196 Return value description.
199 if output_path is None:
200 output_path = '%s.%s' % (ramp_3d_path, 'spi1d')
206 str(resolution * resolution),
211 lut_extract = Process(description='extract a 3d LUT',
214 lut_extract.execute()
217 def apply_CTL_to_image(input_image,
223 aces_CTL_directory=None):
230 Parameter description.
235 Return value description.
238 if len(ctl_paths) > 0:
240 if aces_CTL_directory != None:
241 if os.path.split(aces_CTL_directory)[1] != 'utilities':
242 ctl_module_path = os.path.join(aces_CTL_directory, 'utilities')
244 ctl_module_path = aces_CTL_directory
245 ctlenv['CTL_MODULE_PATH'] = ctl_module_path
248 for ctl in ctl_paths:
249 args += ['-ctl', ctl]
251 # args += ['-verbose']
252 args += ['-input_scale', str(input_scale)]
253 args += ['-output_scale', str(output_scale)]
254 args += ['-global_param1', 'aIn', '1.0']
255 for key, value in global_params.iteritems():
256 args += ['-global_param1', key, str(value)]
257 args += [input_image]
258 args += [output_image]
260 # print('args : %s' % args)
262 ctlp = Process(description='a ctlrender process',
264 args=args, env=ctlenv)
269 def convert_bit_depth(input_image, output_image, depth):
276 Parameter description.
281 Return value description.
289 convert = Process(description='convert image bit depth',
295 def generate_1d_LUT_from_CTL(lut_path,
298 identity_LUT_bit_depth='half',
303 aces_CTL_directory=None,
312 Parameter description.
317 Return value description.
323 lut_path_base = os.path.splitext(lut_path)[0]
325 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
326 generate_1d_LUT_image(identity_LUT_image_float,
331 if identity_LUT_bit_depth != 'half':
332 identity_LUT_image = '%s.%s.%s' % (lut_path_base, 'uint16', 'tiff')
333 convert_bit_depth(identity_LUT_image_float,
335 identity_LUT_bit_depth)
337 identity_LUT_image = identity_LUT_image_float
339 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
340 apply_CTL_to_image(identity_LUT_image,
341 transformed_LUT_image,
348 generate_1d_LUT_from_image(transformed_LUT_image,
354 os.remove(identity_LUT_image)
355 if identity_LUT_image != identity_LUT_image_float:
356 os.remove(identity_LUT_image_float)
357 os.remove(transformed_LUT_image)
360 def correct_LUT_image(transformed_LUT_image,
369 Parameter description.
374 Return value description.
378 transformed = oiio.ImageInput.open(transformed_LUT_image)
381 transformed_spec = transformed.spec()
382 type = transformed_spec.format.basetype
383 width = transformed_spec.width
384 height = transformed_spec.height
385 channels = transformed_spec.nchannels
388 if width != lut_resolution * lut_resolution or height != lut_resolution:
389 print(('Correcting image as resolution is off. '
390 'Found %d x %d. Expected %d x %d') % (
393 lut_resolution * lut_resolution,
395 print('Generating %s' % corrected_LUT_image)
398 # We're going to generate a new correct image
401 # Get the source data
402 # Force data to be read as float. The Python API doesn't handle
403 # half-floats well yet.
405 source_data = transformed.read_image(type)
407 format = os.path.splitext(corrected_LUT_image)[1]
408 correct = oiio.ImageOutput.create(corrected_LUT_image)
411 correct_spec = oiio.ImageSpec()
412 correct_spec.set_format(oiio.FLOAT)
413 correct_spec.width = height
414 correct_spec.height = width
415 correct_spec.nchannels = channels
417 correct.open(corrected_LUT_image, correct_spec, oiio.Create)
419 dest_data = array.array('f',
420 ('\0' * correct_spec.width *
421 correct_spec.height *
422 correct_spec.nchannels * 4))
423 for j in range(0, correct_spec.height):
424 for i in range(0, correct_spec.width):
425 for c in range(0, correct_spec.nchannels):
427 dest_data[(correct_spec.nchannels *
428 correct_spec.width * j +
429 correct_spec.nchannels * i + c)] = (
430 source_data[correct_spec.nchannels *
431 correct_spec.width * j +
432 correct_spec.nchannels * i + c])
434 correct.write_image(correct_spec.format, dest_data)
437 # shutil.copy(transformedLUTImage, correctedLUTImage)
438 corrected_LUT_image = transformed_LUT_image
442 return corrected_LUT_image
445 def generate_3d_LUT_from_CTL(lut_path,
448 identity_LUT_bit_depth='half',
453 aces_CTL_directory=None):
460 Parameter description.
465 Return value description.
471 lut_path_base = os.path.splitext(lut_path)[0]
473 identity_LUT_image_float = '%s.%s.%s' % (lut_path_base, 'float', 'tiff')
474 generate_3d_LUT_image(identity_LUT_image_float, lut_resolution)
476 if identity_LUT_bit_depth != 'half':
477 identity_LUT_image = '%s.%s.%s' % (lut_path_base,
478 identity_LUT_bit_depth,
480 convert_bit_depth(identity_LUT_image_float,
482 identity_LUT_bit_depth)
484 identity_LUT_image = identity_LUT_image_float
486 transformed_LUT_image = '%s.%s.%s' % (lut_path_base, 'transformed', 'exr')
487 apply_CTL_to_image(identity_LUT_image,
488 transformed_LUT_image,
495 corrected_LUT_image = '%s.%s.%s' % (lut_path_base, 'correct', 'exr')
496 corrected_LUT_image = correct_LUT_image(transformed_LUT_image,
500 generate_3d_LUT_from_image(corrected_LUT_image, lut_path, lut_resolution)
503 os.remove(identity_LUT_image)
504 if identity_LUT_image != identity_LUT_image_float:
505 os.remove(identity_LUT_image_float)
506 os.remove(transformed_LUT_image)
507 if corrected_LUT_image != transformed_LUT_image:
508 os.remove(corrected_LUT_image)
509 # os.remove(correctedLUTImage)
519 Parameter description.
524 Return value description.
529 p = optparse.OptionParser(
530 description='A utility to generate LUTs from CTL',
533 usage='%prog [options]')
535 p.add_option('--lut', '-l', type='string', default='')
536 p.add_option('--ctl', '-c', type='string', action='append')
537 p.add_option('--lutResolution1d', '', type='int', default=1024)
538 p.add_option('--lutResolution3d', '', type='int', default=33)
539 p.add_option('--ctlReleasePath', '-r', type='string', default='')
540 p.add_option('--bitDepth', '-b', type='string', default='float')
541 p.add_option('--keepTempImages', '', action='store_true')
542 p.add_option('--minValue', '', type='float', default=0.0)
543 p.add_option('--maxValue', '', type='float', default=1.0)
544 p.add_option('--inputScale', '', type='float', default=1.0)
545 p.add_option('--outputScale', '', type='float', default=1.0)
546 p.add_option('--ctlRenderParam', '-p', type='string', nargs=2,
549 p.add_option('--generate1d', '', action='store_true')
550 p.add_option('--generate3d', '', action='store_true')
552 options, arguments = p.parse_args()
559 lut_resolution_1d = options.lut_resolution_1d
560 lut_resolution_3d = options.lut_resolution_3d
561 min_value = options.minValue
562 max_value = options.maxValue
563 input_scale = options.inputScale
564 output_scale = options.outputScale
565 ctl_release_path = options.ctlReleasePath
566 generate_1d = options.generate1d is True
567 generate_3d = options.generate3d is True
568 bit_depth = options.bitDepth
569 cleanup = not options.keepTempImages
572 if options.ctlRenderParam != None:
573 for param in options.ctlRenderParam:
574 params[param[0]] = float(param[1])
577 args_start = sys.argv.index('--') + 1
578 args = sys.argv[args_start:]
580 args_start = len(sys.argv) + 1
583 # print('command line : \n%s\n' % ' '.join(sys.argv))
589 print('1D LUT generation options')
591 print('3D LUT generation options')
593 print('lut : %s' % lut)
594 print('ctls : %s' % ctls)
595 print('lut res 1d : %s' % lut_resolution_1d)
596 print('lut res 3d : %s' % lut_resolution_3d)
597 print('min value : %s' % min_value)
598 print('max value : %s' % max_value)
599 print('input scale : %s' % input_scale)
600 print('output scale : %s' % output_scale)
601 print('ctl render params : %s' % params)
602 print('ctl release path : %s' % ctl_release_path)
603 print('bit depth of input : %s' % bit_depth)
604 print('cleanup temp images : %s' % cleanup)
607 generate_1d_LUT_from_CTL(lut,
620 generate_3d_LUT_from_CTL(lut,
630 print(('\n\nNo LUT generated. '
631 'You must choose either 1D or 3D LUT generation\n\n'))
634 if __name__ == '__main__':