2 # -*- coding: utf-8 -*-
5 build instructions for osx for needed packages.
8 brew install -vd opencolorio --with-python
11 brew tap homebrew/science
14 brew install -vd libRaw
15 brew install -vd OpenCV
17 brew install -vd openimageio --with-python
23 # this time, 'ociolutimage' will build because openimageio is installed
24 brew uninstall -vd opencolorio
25 brew install -vd opencolorio --with-python
32 import OpenImageIO as oiio
34 from aces_ocio.process import Process
36 __author__ = 'ACES Developers'
37 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
39 __maintainer__ = 'ACES Developers'
40 __email__ = 'aces@oscars.org'
41 __status__ = 'Production'
43 __all__ = ['generate1dLUTImage',
45 'generate1dLUTFromImage',
47 'generate3dLUTFromImage',
50 'generate1dLUTFromCTL',
52 'generate3dLUTFromCTL',
56 def generate1dLUTImage(ramp1dPath,
66 Parameter description.
71 Return value description.
74 # print("Generate 1d LUT image - %s" % ramp1dPath)
77 format = os.path.splitext(ramp1dPath)[1]
78 ramp = oiio.ImageOutput.create(ramp1dPath)
81 spec = oiio.ImageSpec()
82 spec.set_format(oiio.FLOAT)
83 # spec.format.basetype = oiio.FLOAT
84 spec.width = resolution
88 ramp.open(ramp1dPath, spec, oiio.Create)
90 data = array.array("f",
91 "\0" * spec.width * spec.height * spec.nchannels * 4)
92 for i in range(resolution):
93 value = float(i) / (resolution - 1) * (maxValue - minValue) + minValue
94 data[i * spec.nchannels + 0] = value
95 data[i * spec.nchannels + 1] = value
96 data[i * spec.nchannels + 2] = value
98 ramp.write_image(spec.format, data)
102 def writeSPI1D(filename, fromMin, fromMax, data, entries, channels):
106 Credit to *Alex Fry* for the original single channel version of the spi1d
112 Parameter description.
117 Return value description.
120 f = file(filename, 'w')
121 f.write("Version 1\n")
122 f.write("From %f %f\n" % (fromMin, fromMax))
123 f.write("Length %d\n" % entries)
124 f.write("Components %d\n" % (min(3, channels)))
126 for i in range(0, entries):
128 for j in range(0, min(3, channels)):
129 entry = "%s %s" % (entry, data[i * channels + j])
130 f.write(" %s\n" % entry)
135 def generate1dLUTFromImage(ramp1dPath,
145 Parameter description.
150 Return value description.
153 if outputPath == None:
154 outputPath = ramp1dPath + ".spi1d"
157 ramp = oiio.ImageInput.open(ramp1dPath)
161 type = spec.format.basetype
164 channels = spec.nchannels
167 # Force data to be read as float. The Python API doesn't handle
168 # half-floats well yet.
170 data = ramp.read_image(type)
172 writeSPI1D(outputPath, minValue, maxValue, data, width, channels)
175 def generate3dLUTImage(ramp3dPath, resolution=32):
182 Parameter description.
187 Return value description.
190 args = ["--generate",
194 str(resolution * resolution),
197 lutExtract = Process(description="generate a 3d LUT image",
203 def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32):
210 Parameter description.
215 Return value description.
218 if outputPath == None:
219 outputPath = ramp3dPath + ".spi3d"
225 str(resolution * resolution),
230 lutExtract = Process(description="extract a 3d LUT",
236 def applyCTLToImage(inputImage,
242 acesCTLReleaseDir=None):
249 Parameter description.
254 Return value description.
257 if len(ctlPaths) > 0:
259 if acesCTLReleaseDir != None:
260 if os.path.split(acesCTLReleaseDir)[1] != "utilities":
261 ctlModulePath = "%s/utilities" % acesCTLReleaseDir
263 ctlModulePath = acesCTLReleaseDir
264 ctlenv['CTL_MODULE_PATH'] = ctlModulePath
268 args += ['-ctl', ctl]
270 # args += ["-verbose"]
271 args += ["-input_scale", str(inputScale)]
272 args += ["-output_scale", str(outputScale)]
273 args += ["-global_param1", "aIn", "1.0"]
274 for key, value in globalParams.iteritems():
275 args += ["-global_param1", key, str(value)]
277 args += [outputImage]
279 # print("args : %s" % args)
281 ctlp = Process(description="a ctlrender process",
283 args=args, env=ctlenv)
288 def convertBitDepth(inputImage, outputImage, depth):
295 Parameter description.
300 Return value description.
308 convert = Process(description="convert image bit depth",
314 def generate1dLUTFromCTL(lutPath,
317 identityLutBitDepth='half',
322 acesCTLReleaseDir=None,
331 Parameter description.
336 Return value description.
342 lutPathBase = os.path.splitext(lutPath)[0]
344 identityLUTImageFloat = lutPathBase + ".float.tiff"
345 generate1dLUTImage(identityLUTImageFloat,
350 if identityLutBitDepth != 'half':
351 identityLUTImage = lutPathBase + ".uint16.tiff"
352 convertBitDepth(identityLUTImageFloat,
356 identityLUTImage = identityLUTImageFloat
358 transformedLUTImage = lutPathBase + ".transformed.exr"
359 applyCTLToImage(identityLUTImage,
367 generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue)
370 os.remove(identityLUTImage)
371 if identityLUTImage != identityLUTImageFloat:
372 os.remove(identityLUTImageFloat)
373 os.remove(transformedLUTImage)
376 def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution):
383 Parameter description.
388 Return value description.
392 transformed = oiio.ImageInput.open(transformedLUTImage)
395 transformedSpec = transformed.spec()
396 type = transformedSpec.format.basetype
397 width = transformedSpec.width
398 height = transformedSpec.height
399 channels = transformedSpec.nchannels
402 if width != lutResolution * lutResolution or height != lutResolution:
403 print(("Correcting image as resolution is off. "
404 "Found %d x %d. Expected %d x %d") % (
405 width, height, lutResolution * lutResolution, lutResolution))
406 print("Generating %s" % correctedLUTImage)
409 # We're going to generate a new correct image
412 # Get the source data
413 # Force data to be read as float. The Python API doesn't handle
414 # half-floats well yet.
416 sourceData = transformed.read_image(type)
418 format = os.path.splitext(correctedLUTImage)[1]
419 correct = oiio.ImageOutput.create(correctedLUTImage)
422 correctSpec = oiio.ImageSpec()
423 correctSpec.set_format(oiio.FLOAT)
424 correctSpec.width = height
425 correctSpec.height = width
426 correctSpec.nchannels = channels
428 correct.open(correctedLUTImage, correctSpec, oiio.Create)
430 destData = array.array("f",
431 ("\0" * correctSpec.width *
433 correctSpec.nchannels * 4))
434 for j in range(0, correctSpec.height):
435 for i in range(0, correctSpec.width):
436 for c in range(0, correctSpec.nchannels):
438 destData[(correctSpec.nchannels *
439 correctSpec.width * j +
440 correctSpec.nchannels * i + c)] = (
441 sourceData[correctSpec.nchannels *
442 correctSpec.width * j +
443 correctSpec.nchannels * i + c])
445 correct.write_image(correctSpec.format, destData)
448 # shutil.copy(transformedLUTImage, correctedLUTImage)
449 correctedLUTImage = transformedLUTImage
453 return correctedLUTImage
456 def generate3dLUTFromCTL(lutPath,
459 identityLutBitDepth='half',
464 acesCTLReleaseDir=None):
471 Parameter description.
476 Return value description.
482 lutPathBase = os.path.splitext(lutPath)[0]
484 identityLUTImageFloat = lutPathBase + ".float.tiff"
485 generate3dLUTImage(identityLUTImageFloat, lutResolution)
487 if identityLutBitDepth != 'half':
488 identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff"
489 convertBitDepth(identityLUTImageFloat,
493 identityLUTImage = identityLUTImageFloat
495 transformedLUTImage = lutPathBase + ".transformed.exr"
496 applyCTLToImage(identityLUTImage,
504 correctedLUTImage = lutPathBase + ".correct.exr"
505 correctedLUTImage = correctLUTImage(transformedLUTImage,
509 generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution)
512 os.remove(identityLUTImage)
513 if identityLUTImage != identityLUTImageFloat:
514 os.remove(identityLUTImageFloat)
515 os.remove(transformedLUTImage)
516 if correctedLUTImage != transformedLUTImage:
517 os.remove(correctedLUTImage)
518 # os.remove(correctedLUTImage)
528 Parameter description.
533 Return value description.
538 p = optparse.OptionParser(
539 description='A utility to generate LUTs from CTL',
542 usage='%prog [options]')
544 p.add_option('--lut', '-l', type="string", default="")
545 p.add_option('--ctl', '-c', type="string", action="append")
546 p.add_option('--lutResolution1d', '', type="int", default=1024)
547 p.add_option('--lutResolution3d', '', type="int", default=33)
548 p.add_option('--ctlReleasePath', '-r', type="string", default="")
549 p.add_option('--bitDepth', '-b', type="string", default="float")
550 p.add_option('--keepTempImages', '', action="store_true")
551 p.add_option('--minValue', '', type="float", default=0.0)
552 p.add_option('--maxValue', '', type="float", default=1.0)
553 p.add_option('--inputScale', '', type="float", default=1.0)
554 p.add_option('--outputScale', '', type="float", default=1.0)
555 p.add_option('--ctlRenderParam', '-p', type="string", nargs=2,
558 p.add_option('--generate1d', '', action="store_true")
559 p.add_option('--generate3d', '', action="store_true")
561 options, arguments = p.parse_args()
568 lutResolution1d = options.lutResolution1d
569 lutResolution3d = options.lutResolution3d
570 minValue = options.minValue
571 maxValue = options.maxValue
572 inputScale = options.inputScale
573 outputScale = options.outputScale
574 ctlReleasePath = options.ctlReleasePath
575 generate1d = options.generate1d == True
576 generate3d = options.generate3d == True
577 bitDepth = options.bitDepth
578 cleanup = not options.keepTempImages
581 if options.ctlRenderParam != None:
582 for param in options.ctlRenderParam:
583 params[param[0]] = float(param[1])
586 argsStart = sys.argv.index('--') + 1
587 args = sys.argv[argsStart:]
589 argsStart = len(sys.argv) + 1
592 # print("command line : \n%s\n" % " ".join(sys.argv))
598 print("1D LUT generation options")
600 print("3D LUT generation options")
602 print("lut : %s" % lut)
603 print("ctls : %s" % ctls)
604 print("lut res 1d : %s" % lutResolution1d)
605 print("lut res 3d : %s" % lutResolution3d)
606 print("min value : %s" % minValue)
607 print("max value : %s" % maxValue)
608 print("input scale : %s" % inputScale)
609 print("output scale : %s" % outputScale)
610 print("ctl render params : %s" % params)
611 print("ctl release path : %s" % ctlReleasePath)
612 print("bit depth of input : %s" % bitDepth)
613 print("cleanup temp images : %s" % cleanup)
616 generate1dLUTFromCTL(lut,
629 generate3dLUTFromCTL(lut,
639 print(("\n\nNo LUT generated. "
640 "You must choose either 1D or 3D LUT generation\n\n"))
644 if __name__ == '__main__':