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 # Functions used to generate LUTs using CTL transforms
58 def generate1dLUTImage(ramp1dPath,
62 # print("Generate 1d LUT image - %s" % ramp1dPath)
65 format = os.path.splitext(ramp1dPath)[1]
66 ramp = oiio.ImageOutput.create(ramp1dPath)
69 spec = oiio.ImageSpec()
70 spec.set_format(oiio.FLOAT)
71 # spec.format.basetype = oiio.FLOAT
72 spec.width = resolution
76 ramp.open(ramp1dPath, spec, oiio.Create)
78 data = array.array("f",
79 "\0" * spec.width * spec.height * spec.nchannels * 4)
80 for i in range(resolution):
81 value = float(i) / (resolution - 1) * (maxValue - minValue) + minValue
82 data[i * spec.nchannels + 0] = value
83 data[i * spec.nchannels + 1] = value
84 data[i * spec.nchannels + 2] = value
86 ramp.write_image(spec.format, data)
90 # Credit to Alex Fry for the original single channel version of the spi1d
92 def writeSPI1D(filename, fromMin, fromMax, data, entries, channels):
93 f = file(filename, 'w')
94 f.write("Version 1\n")
95 f.write("From %f %f\n" % (fromMin, fromMax))
96 f.write("Length %d\n" % entries)
97 f.write("Components %d\n" % (min(3, channels)))
99 for i in range(0, entries):
101 for j in range(0, min(3, channels)):
102 entry = "%s %s" % (entry, data[i * channels + j])
103 f.write(" %s\n" % entry)
108 def generate1dLUTFromImage(ramp1dPath,
112 if outputPath == None:
113 outputPath = ramp1dPath + ".spi1d"
116 ramp = oiio.ImageInput.open(ramp1dPath)
120 type = spec.format.basetype
123 channels = spec.nchannels
126 # Force data to be read as float. The Python API doesn't handle
127 # half-floats well yet.
129 data = ramp.read_image(type)
131 writeSPI1D(outputPath, minValue, maxValue, data, width, channels)
134 def generate3dLUTImage(ramp3dPath, resolution=32):
135 args = ["--generate",
139 str(resolution * resolution),
142 lutExtract = Process(description="generate a 3d LUT image",
148 def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32):
149 if outputPath == None:
150 outputPath = ramp3dPath + ".spi3d"
156 str(resolution * resolution),
161 lutExtract = Process(description="extract a 3d LUT",
167 def applyCTLToImage(inputImage,
173 acesCTLReleaseDir=None):
174 if len(ctlPaths) > 0:
176 if acesCTLReleaseDir != None:
177 if os.path.split(acesCTLReleaseDir)[1] != "utilities":
178 ctlModulePath = "%s/utilities" % acesCTLReleaseDir
180 ctlModulePath = acesCTLReleaseDir
181 ctlenv['CTL_MODULE_PATH'] = ctlModulePath
185 args += ['-ctl', ctl]
187 # args += ["-verbose"]
188 args += ["-input_scale", str(inputScale)]
189 args += ["-output_scale", str(outputScale)]
190 args += ["-global_param1", "aIn", "1.0"]
191 for key, value in globalParams.iteritems():
192 args += ["-global_param1", key, str(value)]
194 args += [outputImage]
196 # print("args : %s" % args)
198 ctlp = Process(description="a ctlrender process",
200 args=args, env=ctlenv)
205 def convertBitDepth(inputImage, outputImage, depth):
211 convert = Process(description="convert image bit depth",
217 def generate1dLUTFromCTL(lutPath,
220 identityLutBitDepth='half',
225 acesCTLReleaseDir=None,
231 lutPathBase = os.path.splitext(lutPath)[0]
233 identityLUTImageFloat = lutPathBase + ".float.tiff"
234 generate1dLUTImage(identityLUTImageFloat,
239 if identityLutBitDepth != 'half':
240 identityLUTImage = lutPathBase + ".uint16.tiff"
241 convertBitDepth(identityLUTImageFloat,
245 identityLUTImage = identityLUTImageFloat
247 transformedLUTImage = lutPathBase + ".transformed.exr"
248 applyCTLToImage(identityLUTImage,
256 generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue)
259 os.remove(identityLUTImage)
260 if identityLUTImage != identityLUTImageFloat:
261 os.remove(identityLUTImageFloat)
262 os.remove(transformedLUTImage)
265 def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution):
267 transformed = oiio.ImageInput.open(transformedLUTImage)
270 transformedSpec = transformed.spec()
271 type = transformedSpec.format.basetype
272 width = transformedSpec.width
273 height = transformedSpec.height
274 channels = transformedSpec.nchannels
277 if width != lutResolution * lutResolution or height != lutResolution:
278 print(("Correcting image as resolution is off. "
279 "Found %d x %d. Expected %d x %d") % (
280 width, height, lutResolution * lutResolution, lutResolution))
281 print("Generating %s" % correctedLUTImage)
284 # We're going to generate a new correct image
287 # Get the source data
288 # Force data to be read as float. The Python API doesn't handle
289 # half-floats well yet.
291 sourceData = transformed.read_image(type)
293 format = os.path.splitext(correctedLUTImage)[1]
294 correct = oiio.ImageOutput.create(correctedLUTImage)
297 correctSpec = oiio.ImageSpec()
298 correctSpec.set_format(oiio.FLOAT)
299 correctSpec.width = height
300 correctSpec.height = width
301 correctSpec.nchannels = channels
303 correct.open(correctedLUTImage, correctSpec, oiio.Create)
305 destData = array.array("f",
306 ("\0" * correctSpec.width *
308 correctSpec.nchannels * 4))
309 for j in range(0, correctSpec.height):
310 for i in range(0, correctSpec.width):
311 for c in range(0, correctSpec.nchannels):
313 destData[(correctSpec.nchannels *
314 correctSpec.width * j +
315 correctSpec.nchannels * i + c)] = (
316 sourceData[correctSpec.nchannels *
317 correctSpec.width * j +
318 correctSpec.nchannels * i + c])
320 correct.write_image(correctSpec.format, destData)
323 # shutil.copy(transformedLUTImage, correctedLUTImage)
324 correctedLUTImage = transformedLUTImage
328 return correctedLUTImage
331 def generate3dLUTFromCTL(lutPath,
334 identityLutBitDepth='half',
339 acesCTLReleaseDir=None):
343 lutPathBase = os.path.splitext(lutPath)[0]
345 identityLUTImageFloat = lutPathBase + ".float.tiff"
346 generate3dLUTImage(identityLUTImageFloat, lutResolution)
348 if identityLutBitDepth != 'half':
349 identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff"
350 convertBitDepth(identityLUTImageFloat,
354 identityLUTImage = identityLUTImageFloat
356 transformedLUTImage = lutPathBase + ".transformed.exr"
357 applyCTLToImage(identityLUTImage,
365 correctedLUTImage = lutPathBase + ".correct.exr"
366 correctedLUTImage = correctLUTImage(transformedLUTImage,
370 generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution)
373 os.remove(identityLUTImage)
374 if identityLUTImage != identityLUTImageFloat:
375 os.remove(identityLUTImageFloat)
376 os.remove(transformedLUTImage)
377 if correctedLUTImage != transformedLUTImage:
378 os.remove(correctedLUTImage)
379 # os.remove(correctedLUTImage)
385 p = optparse.OptionParser(
386 description='A utility to generate LUTs from CTL',
389 usage='%prog [options]')
391 p.add_option('--lut', '-l', type="string", default="")
392 p.add_option('--ctl', '-c', type="string", action="append")
393 p.add_option('--lutResolution1d', '', type="int", default=1024)
394 p.add_option('--lutResolution3d', '', type="int", default=33)
395 p.add_option('--ctlReleasePath', '-r', type="string", default="")
396 p.add_option('--bitDepth', '-b', type="string", default="float")
397 p.add_option('--keepTempImages', '', action="store_true")
398 p.add_option('--minValue', '', type="float", default=0.0)
399 p.add_option('--maxValue', '', type="float", default=1.0)
400 p.add_option('--inputScale', '', type="float", default=1.0)
401 p.add_option('--outputScale', '', type="float", default=1.0)
402 p.add_option('--ctlRenderParam', '-p', type="string", nargs=2,
405 p.add_option('--generate1d', '', action="store_true")
406 p.add_option('--generate3d', '', action="store_true")
408 options, arguments = p.parse_args()
415 lutResolution1d = options.lutResolution1d
416 lutResolution3d = options.lutResolution3d
417 minValue = options.minValue
418 maxValue = options.maxValue
419 inputScale = options.inputScale
420 outputScale = options.outputScale
421 ctlReleasePath = options.ctlReleasePath
422 generate1d = options.generate1d == True
423 generate3d = options.generate3d == True
424 bitDepth = options.bitDepth
425 cleanup = not options.keepTempImages
428 if options.ctlRenderParam != None:
429 for param in options.ctlRenderParam:
430 params[param[0]] = float(param[1])
433 argsStart = sys.argv.index('--') + 1
434 args = sys.argv[argsStart:]
436 argsStart = len(sys.argv) + 1
439 # print("command line : \n%s\n" % " ".join(sys.argv))
445 print("1D LUT generation options")
447 print("3D LUT generation options")
449 print("lut : %s" % lut)
450 print("ctls : %s" % ctls)
451 print("lut res 1d : %s" % lutResolution1d)
452 print("lut res 3d : %s" % lutResolution3d)
453 print("min value : %s" % minValue)
454 print("max value : %s" % maxValue)
455 print("input scale : %s" % inputScale)
456 print("output scale : %s" % outputScale)
457 print("ctl render params : %s" % params)
458 print("ctl release path : %s" % ctlReleasePath)
459 print("bit depth of input : %s" % bitDepth)
460 print("cleanup temp images : %s" % cleanup)
463 generate1dLUTFromCTL(lut,
476 generate3dLUTFromCTL(lut,
486 print(("\n\nNo LUT generated. "
487 "You must choose either 1D or 3D LUT generation\n\n"))
491 if __name__ == '__main__':