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, resolution=1024, minValue=0.0, maxValue=1.0):
41 #print( "Generate 1d LUT image - %s" % ramp1dPath)
44 format = os.path.splitext(ramp1dPath)[1]
45 ramp = oiio.ImageOutput.create(ramp1dPath)
48 spec = oiio.ImageSpec()
49 spec.set_format( oiio.FLOAT )
50 #spec.format.basetype = oiio.FLOAT
51 spec.width = resolution
55 ramp.open (ramp1dPath, spec, oiio.Create)
57 data = array.array("f", "\0" * spec.width * spec.height * spec.nchannels * 4)
58 for i in range(resolution):
59 value = float(i)/(resolution-1) * (maxValue - minValue) + minValue
60 data[i*spec.nchannels +0] = value
61 data[i*spec.nchannels +1] = value
62 data[i*spec.nchannels +2] = value
64 ramp.write_image(spec.format, data)
67 # Credit to Alex Fry for the original single channel version of the spi1d writer
68 def writeSPI1D(filename, fromMin, fromMax, data, entries, channels):
69 f = file(filename,'w')
70 f.write("Version 1\n")
71 f.write("From %f %f\n" % (fromMin, fromMax))
72 f.write("Length %d\n" % entries)
73 f.write("Components %d\n" % (min(3, channels)) )
75 for i in range(0, entries):
77 for j in range(0, min(3, channels)):
78 entry = "%s %s" % (entry, data[i*channels + j])
79 f.write(" %s\n" % entry)
83 def generate1dLUTFromImage(ramp1dPath, outputPath=None, minValue=0.0, maxValue=1.0):
84 if outputPath == None:
85 outputPath = ramp1dPath + ".spi1d"
88 ramp = oiio.ImageInput.open( ramp1dPath )
92 type = spec.format.basetype
95 channels = spec.nchannels
98 # Force data to be read as float. The Python API doesn't handle half-floats well yet.
100 data = ramp.read_image(type)
102 writeSPI1D(outputPath, minValue, maxValue, data, width, channels)
104 def generate3dLUTImage(ramp3dPath, resolution=32):
105 args = ["--generate", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--output", ramp3dPath]
106 lutExtract = Process(description="generate a 3d LUT image", cmd="ociolutimage", args=args)
109 def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32):
110 if outputPath == None:
111 outputPath = ramp3dPath + ".spi3d"
113 args = ["--extract", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--input", ramp3dPath, "--output", outputPath]
114 lutExtract = Process(description="extract a 3d LUT", cmd="ociolutimage", args=args)
117 def applyCTLToImage(inputImage,
123 acesCTLReleaseDir=None):
124 if len(ctlPaths) > 0:
126 if acesCTLReleaseDir != None:
127 if os.path.split(acesCTLReleaseDir)[1] != "utilities":
128 ctlModulePath = "%s/utilities" % acesCTLReleaseDir
130 ctlModulePath = acesCTLReleaseDir
131 ctlenv['CTL_MODULE_PATH'] = ctlModulePath
135 args += ['-ctl', ctl]
137 #args += ["-verbose"]
138 args += ["-input_scale", str(inputScale)]
139 args += ["-output_scale", str(outputScale)]
140 args += ["-global_param1", "aIn", "1.0"]
141 for key, value in globalParams.iteritems():
142 args += ["-global_param1", key, str(value)]
144 args += [outputImage]
146 #print( "args : %s" % args )
148 ctlp = Process(description="a ctlrender process", cmd="ctlrender", args=args, env=ctlenv )
152 def convertBitDepth(inputImage, outputImage, depth):
153 args = [inputImage, "-d", depth, "-o", outputImage]
154 convert = Process(description="convert image bit depth", cmd="oiiotool", args=args)
157 def generate1dLUTFromCTL(lutPath,
160 identityLutBitDepth='half',
165 acesCTLReleaseDir=None,
171 lutPathBase = os.path.splitext(lutPath)[0]
173 identityLUTImageFloat = lutPathBase + ".float.tiff"
174 generate1dLUTImage(identityLUTImageFloat, lutResolution, minValue, maxValue)
176 if identityLutBitDepth != 'half':
177 identityLUTImage = lutPathBase + ".uint16.tiff"
178 convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth)
180 identityLUTImage = identityLUTImageFloat
182 transformedLUTImage = lutPathBase + ".transformed.exr"
183 applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir)
185 generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue)
188 os.remove(identityLUTImage)
189 if identityLUTImage != identityLUTImageFloat:
190 os.remove(identityLUTImageFloat)
191 os.remove(transformedLUTImage)
193 def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution):
195 transformed = oiio.ImageInput.open( transformedLUTImage )
198 transformedSpec = transformed.spec()
199 type = transformedSpec.format.basetype
200 width = transformedSpec.width
201 height = transformedSpec.height
202 channels = transformedSpec.nchannels
205 if width != lutResolution * lutResolution or height != lutResolution:
206 print( "Correcting image as resolution is off. Found %d x %d. Expected %d x %d" % (width, height, lutResolution * lutResolution, lutResolution) )
207 print( "Generating %s" % correctedLUTImage)
210 # We're going to generate a new correct image
213 # Get the source data
214 # Force data to be read as float. The Python API doesn't handle half-floats well yet.
216 sourceData = transformed.read_image(type)
218 format = os.path.splitext(correctedLUTImage)[1]
219 correct = oiio.ImageOutput.create(correctedLUTImage)
222 correctSpec = oiio.ImageSpec()
223 correctSpec.set_format( oiio.FLOAT )
224 correctSpec.width = height
225 correctSpec.height = width
226 correctSpec.nchannels = channels
228 correct.open (correctedLUTImage, correctSpec, oiio.Create)
230 destData = array.array("f", "\0" * correctSpec.width * correctSpec.height * correctSpec.nchannels * 4)
231 for j in range(0, correctSpec.height):
232 for i in range(0, correctSpec.width):
233 for c in range(0, correctSpec.nchannels):
235 destData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c] = sourceData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c]
237 correct.write_image(correctSpec.format, destData)
240 #shutil.copy(transformedLUTImage, correctedLUTImage)
241 correctedLUTImage = transformedLUTImage
245 return correctedLUTImage
247 def generate3dLUTFromCTL(lutPath,
250 identityLutBitDepth='half',
255 acesCTLReleaseDir=None):
259 lutPathBase = os.path.splitext(lutPath)[0]
261 identityLUTImageFloat = lutPathBase + ".float.tiff"
262 generate3dLUTImage(identityLUTImageFloat, lutResolution)
265 if identityLutBitDepth != 'half':
266 identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff"
267 convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth)
269 identityLUTImage = identityLUTImageFloat
271 transformedLUTImage = lutPathBase + ".transformed.exr"
272 applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir)
274 correctedLUTImage = lutPathBase + ".correct.exr"
275 correctedLUTImage = correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution)
277 generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution)
280 os.remove(identityLUTImage)
281 if identityLUTImage != identityLUTImageFloat:
282 os.remove(identityLUTImageFloat)
283 os.remove(transformedLUTImage)
284 if correctedLUTImage != transformedLUTImage:
285 os.remove(correctedLUTImage)
286 #os.remove(correctedLUTImage)
291 p = optparse.OptionParser(description='A utility to generate LUTs from CTL',
294 usage='%prog [options]')
296 p.add_option('--lut', '-l', type="string", default="")
297 p.add_option('--ctl', '-c', type="string", action="append")
298 p.add_option('--lutResolution1d', '', type="int", default=1024)
299 p.add_option('--lutResolution3d', '', type="int", default=33)
300 p.add_option('--ctlReleasePath', '-r', type="string", default="")
301 p.add_option('--bitDepth', '-b', type="string", default="float")
302 p.add_option('--keepTempImages', '', action="store_true")
303 p.add_option('--minValue', '', type="float", default=0.0)
304 p.add_option('--maxValue', '', type="float", default=1.0)
305 p.add_option('--inputScale', '', type="float", default=1.0)
306 p.add_option('--outputScale', '', type="float", default=1.0)
307 p.add_option('--ctlRenderParam', '-p', type="string", nargs=2, action="append")
309 p.add_option('--generate1d', '', action="store_true")
310 p.add_option('--generate3d', '', action="store_true")
312 options, arguments = p.parse_args()
319 lutResolution1d = options.lutResolution1d
320 lutResolution3d = options.lutResolution3d
321 minValue = options.minValue
322 maxValue = options.maxValue
323 inputScale = options.inputScale
324 outputScale = options.outputScale
325 ctlReleasePath = options.ctlReleasePath
326 generate1d = options.generate1d == True
327 generate3d = options.generate3d == True
328 bitDepth = options.bitDepth
329 cleanup = not options.keepTempImages
332 if options.ctlRenderParam != None:
333 for param in options.ctlRenderParam:
334 params[param[0]] = float(param[1])
337 argsStart = sys.argv.index('--') + 1
338 args = sys.argv[argsStart:]
340 argsStart = len(sys.argv)+1
343 #print( "command line : \n%s\n" % " ".join(sys.argv) )
349 print( "1D LUT generation options")
351 print( "3D LUT generation options")
353 print( "lut : %s" % lut )
354 print( "ctls : %s" % ctls )
355 print( "lut res 1d : %s" % lutResolution1d )
356 print( "lut res 3d : %s" % lutResolution3d )
357 print( "min value : %s" % minValue )
358 print( "max value : %s" % maxValue )
359 print( "input scale : %s" % inputScale )
360 print( "output scale : %s" % outputScale )
361 print( "ctl render params : %s" % params )
362 print( "ctl release path : %s" % ctlReleasePath )
363 print( "bit depth of input : %s" % bitDepth )
364 print( "cleanup temp images : %s" % cleanup)
367 generate1dLUTFromCTL( lut,
380 generate3dLUTFromCTL( lut,
390 print( "\n\nNo LUT generated. You must choose either 1D or 3D LUT generation\n\n")
393 if __name__ == '__main__':