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
38 # Functions used to generate LUTs using CTL transforms
40 def generate1dLUTImage(ramp1dPath,
44 # print("Generate 1d LUT image - %s" % ramp1dPath)
47 format = os.path.splitext(ramp1dPath)[1]
48 ramp = oiio.ImageOutput.create(ramp1dPath)
51 spec = oiio.ImageSpec()
52 spec.set_format(oiio.FLOAT)
53 # spec.format.basetype = oiio.FLOAT
54 spec.width = resolution
58 ramp.open(ramp1dPath, spec, oiio.Create)
60 data = array.array("f",
61 "\0" * spec.width * spec.height * spec.nchannels * 4)
62 for i in range(resolution):
63 value = float(i) / (resolution - 1) * (maxValue - minValue) + minValue
64 data[i * spec.nchannels + 0] = value
65 data[i * spec.nchannels + 1] = value
66 data[i * spec.nchannels + 2] = value
68 ramp.write_image(spec.format, data)
72 # Credit to Alex Fry for the original single channel version of the spi1d
74 def writeSPI1D(filename, fromMin, fromMax, data, entries, channels):
75 f = file(filename, 'w')
76 f.write("Version 1\n")
77 f.write("From %f %f\n" % (fromMin, fromMax))
78 f.write("Length %d\n" % entries)
79 f.write("Components %d\n" % (min(3, channels)))
81 for i in range(0, entries):
83 for j in range(0, min(3, channels)):
84 entry = "%s %s" % (entry, data[i * channels + j])
85 f.write(" %s\n" % entry)
90 def generate1dLUTFromImage(ramp1dPath,
94 if outputPath == None:
95 outputPath = ramp1dPath + ".spi1d"
98 ramp = oiio.ImageInput.open(ramp1dPath)
102 type = spec.format.basetype
105 channels = spec.nchannels
108 # Force data to be read as float. The Python API doesn't handle
109 # half-floats well yet.
111 data = ramp.read_image(type)
113 writeSPI1D(outputPath, minValue, maxValue, data, width, channels)
116 def generate3dLUTImage(ramp3dPath, resolution=32):
117 args = ["--generate",
121 str(resolution * resolution),
124 lutExtract = Process(description="generate a 3d LUT image",
130 def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32):
131 if outputPath == None:
132 outputPath = ramp3dPath + ".spi3d"
138 str(resolution * resolution),
143 lutExtract = Process(description="extract a 3d LUT",
149 def applyCTLToImage(inputImage,
155 acesCTLReleaseDir=None):
156 if len(ctlPaths) > 0:
158 if acesCTLReleaseDir != None:
159 if os.path.split(acesCTLReleaseDir)[1] != "utilities":
160 ctlModulePath = "%s/utilities" % acesCTLReleaseDir
162 ctlModulePath = acesCTLReleaseDir
163 ctlenv['CTL_MODULE_PATH'] = ctlModulePath
167 args += ['-ctl', ctl]
169 # args += ["-verbose"]
170 args += ["-input_scale", str(inputScale)]
171 args += ["-output_scale", str(outputScale)]
172 args += ["-global_param1", "aIn", "1.0"]
173 for key, value in globalParams.iteritems():
174 args += ["-global_param1", key, str(value)]
176 args += [outputImage]
178 # print("args : %s" % args)
180 ctlp = Process(description="a ctlrender process",
182 args=args, env=ctlenv)
187 def convertBitDepth(inputImage, outputImage, depth):
193 convert = Process(description="convert image bit depth",
199 def generate1dLUTFromCTL(lutPath,
202 identityLutBitDepth='half',
207 acesCTLReleaseDir=None,
213 lutPathBase = os.path.splitext(lutPath)[0]
215 identityLUTImageFloat = lutPathBase + ".float.tiff"
216 generate1dLUTImage(identityLUTImageFloat,
221 if identityLutBitDepth != 'half':
222 identityLUTImage = lutPathBase + ".uint16.tiff"
223 convertBitDepth(identityLUTImageFloat,
227 identityLUTImage = identityLUTImageFloat
229 transformedLUTImage = lutPathBase + ".transformed.exr"
230 applyCTLToImage(identityLUTImage,
238 generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue)
241 os.remove(identityLUTImage)
242 if identityLUTImage != identityLUTImageFloat:
243 os.remove(identityLUTImageFloat)
244 os.remove(transformedLUTImage)
247 def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution):
249 transformed = oiio.ImageInput.open(transformedLUTImage)
252 transformedSpec = transformed.spec()
253 type = transformedSpec.format.basetype
254 width = transformedSpec.width
255 height = transformedSpec.height
256 channels = transformedSpec.nchannels
259 if width != lutResolution * lutResolution or height != lutResolution:
260 print(("Correcting image as resolution is off. "
261 "Found %d x %d. Expected %d x %d") % (
262 width, height, lutResolution * lutResolution, lutResolution))
263 print("Generating %s" % correctedLUTImage)
266 # We're going to generate a new correct image
269 # Get the source data
270 # Force data to be read as float. The Python API doesn't handle
271 # half-floats well yet.
273 sourceData = transformed.read_image(type)
275 format = os.path.splitext(correctedLUTImage)[1]
276 correct = oiio.ImageOutput.create(correctedLUTImage)
279 correctSpec = oiio.ImageSpec()
280 correctSpec.set_format(oiio.FLOAT)
281 correctSpec.width = height
282 correctSpec.height = width
283 correctSpec.nchannels = channels
285 correct.open(correctedLUTImage, correctSpec, oiio.Create)
287 destData = array.array("f",
288 ("\0" * correctSpec.width *
290 correctSpec.nchannels * 4))
291 for j in range(0, correctSpec.height):
292 for i in range(0, correctSpec.width):
293 for c in range(0, correctSpec.nchannels):
295 destData[(correctSpec.nchannels *
296 correctSpec.width * j +
297 correctSpec.nchannels * i + c)] = (
298 sourceData[correctSpec.nchannels *
299 correctSpec.width * j +
300 correctSpec.nchannels * i + c])
302 correct.write_image(correctSpec.format, destData)
305 # shutil.copy(transformedLUTImage, correctedLUTImage)
306 correctedLUTImage = transformedLUTImage
310 return correctedLUTImage
313 def generate3dLUTFromCTL(lutPath,
316 identityLutBitDepth='half',
321 acesCTLReleaseDir=None):
325 lutPathBase = os.path.splitext(lutPath)[0]
327 identityLUTImageFloat = lutPathBase + ".float.tiff"
328 generate3dLUTImage(identityLUTImageFloat, lutResolution)
330 if identityLutBitDepth != 'half':
331 identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff"
332 convertBitDepth(identityLUTImageFloat,
336 identityLUTImage = identityLUTImageFloat
338 transformedLUTImage = lutPathBase + ".transformed.exr"
339 applyCTLToImage(identityLUTImage,
347 correctedLUTImage = lutPathBase + ".correct.exr"
348 correctedLUTImage = correctLUTImage(transformedLUTImage,
352 generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution)
355 os.remove(identityLUTImage)
356 if identityLUTImage != identityLUTImageFloat:
357 os.remove(identityLUTImageFloat)
358 os.remove(transformedLUTImage)
359 if correctedLUTImage != transformedLUTImage:
360 os.remove(correctedLUTImage)
361 # os.remove(correctedLUTImage)
367 p = optparse.OptionParser(
368 description='A utility to generate LUTs from CTL',
371 usage='%prog [options]')
373 p.add_option('--lut', '-l', type="string", default="")
374 p.add_option('--ctl', '-c', type="string", action="append")
375 p.add_option('--lutResolution1d', '', type="int", default=1024)
376 p.add_option('--lutResolution3d', '', type="int", default=33)
377 p.add_option('--ctlReleasePath', '-r', type="string", default="")
378 p.add_option('--bitDepth', '-b', type="string", default="float")
379 p.add_option('--keepTempImages', '', action="store_true")
380 p.add_option('--minValue', '', type="float", default=0.0)
381 p.add_option('--maxValue', '', type="float", default=1.0)
382 p.add_option('--inputScale', '', type="float", default=1.0)
383 p.add_option('--outputScale', '', type="float", default=1.0)
384 p.add_option('--ctlRenderParam', '-p', type="string", nargs=2,
387 p.add_option('--generate1d', '', action="store_true")
388 p.add_option('--generate3d', '', action="store_true")
390 options, arguments = p.parse_args()
397 lutResolution1d = options.lutResolution1d
398 lutResolution3d = options.lutResolution3d
399 minValue = options.minValue
400 maxValue = options.maxValue
401 inputScale = options.inputScale
402 outputScale = options.outputScale
403 ctlReleasePath = options.ctlReleasePath
404 generate1d = options.generate1d == True
405 generate3d = options.generate3d == True
406 bitDepth = options.bitDepth
407 cleanup = not options.keepTempImages
410 if options.ctlRenderParam != None:
411 for param in options.ctlRenderParam:
412 params[param[0]] = float(param[1])
415 argsStart = sys.argv.index('--') + 1
416 args = sys.argv[argsStart:]
418 argsStart = len(sys.argv) + 1
421 # print("command line : \n%s\n" % " ".join(sys.argv))
427 print("1D LUT generation options")
429 print("3D LUT generation options")
431 print("lut : %s" % lut)
432 print("ctls : %s" % ctls)
433 print("lut res 1d : %s" % lutResolution1d)
434 print("lut res 3d : %s" % lutResolution3d)
435 print("min value : %s" % minValue)
436 print("max value : %s" % maxValue)
437 print("input scale : %s" % inputScale)
438 print("output scale : %s" % outputScale)
439 print("ctl render params : %s" % params)
440 print("ctl release path : %s" % ctlReleasePath)
441 print("bit depth of input : %s" % bitDepth)
442 print("cleanup temp images : %s" % cleanup)
445 generate1dLUTFromCTL(lut,
458 generate3dLUTFromCTL(lut,
468 print(("\n\nNo LUT generated. "
469 "You must choose either 1D or 3D LUT generation\n\n"))
473 if __name__ == '__main__':