From: Haarm-Pieter Duiker Date: Wed, 14 Jan 2015 00:24:32 +0000 (-0800) Subject: Moved LUT generation into a separate module X-Git-Url: http://users.mur.at/ms/git/gitweb/?p=OpenColorIO-Configs.git;a=commitdiff_plain;h=1a4370f9bf4d67f522a66c68c236d4d83707a6c1 Moved LUT generation into a separate module --- diff --git a/aces_1.0.0/python/create_aces_config.py b/aces_1.0.0/python/create_aces_config.py index 88701f0..f61002e 100755 --- a/aces_1.0.0/python/create_aces_config.py +++ b/aces_1.0.0/python/create_aces_config.py @@ -49,6 +49,7 @@ import OpenImageIO as oiio import PyOpenColorIO as OCIO import process +import generateLUT as genlut # # Utility functions @@ -74,8 +75,6 @@ def setConfigDefaultRoles( config, if reference: config.setRole( OCIO.Constants.ROLE_REFERENCE, reference ) if scene_linear: config.setRole( OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear ) if texture_paint: config.setRole( OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint ) - - # Write config to disk def writeConfig( config, configPath, sanityCheck=True ): @@ -92,254 +91,6 @@ def writeConfig( config, configPath, sanityCheck=True ): fileHandle.write( config.serialize() ) fileHandle.close() -# -# Functions used to generate LUTs using CTL transforms -# -def generate1dLUTImage(ramp1dPath, resolution=1024, minValue=0.0, maxValue=1.0): - #print( "Generate 1d LUT image - %s" % ramp1dPath) - - # open image - format = os.path.splitext(ramp1dPath)[1] - ramp = oiio.ImageOutput.create(ramp1dPath) - - # set image specs - spec = oiio.ImageSpec() - spec.set_format( oiio.FLOAT ) - #spec.format.basetype = oiio.FLOAT - spec.width = resolution - spec.height = 1 - spec.nchannels = 3 - - ramp.open (ramp1dPath, spec, oiio.Create) - - data = array.array("f", "\0" * spec.width * spec.height * spec.nchannels * 4) - for i in range(resolution): - value = float(i)/(resolution-1) * (maxValue - minValue) + minValue - data[i*spec.nchannels +0] = value - data[i*spec.nchannels +1] = value - data[i*spec.nchannels +2] = value - - ramp.write_image(spec.format, data) - ramp.close() - -# Credit to Alex Fry for the original single channel version of the spi1d writer -def WriteSPI1D(filename, fromMin, fromMax, data, entries, channels): - f = file(filename,'w') - f.write("Version 1\n") - f.write("From %f %f\n" % (fromMin, fromMax)) - f.write("Length %d\n" % entries) - f.write("Components %d\n" % (min(3, channels)) ) - f.write("{\n") - for i in range(0, entries): - entry = "" - for j in range(0, min(3, channels)): - entry = "%s %s" % (entry, data[i*channels + j]) - f.write(" %s\n" % entry) - f.write("}\n") - f.close() - -def generate1dLUTFromImage(ramp1dPath, outputPath=None, minValue=0.0, maxValue=1.0): - if outputPath == None: - outputPath = ramp1dPath + ".spi1d" - - # open image - ramp = oiio.ImageInput.open( ramp1dPath ) - - # get image specs - spec = ramp.spec() - type = spec.format.basetype - width = spec.width - height = spec.height - channels = spec.nchannels - - # get data - # Force data to be read as float. The Python API doesn't handle half-floats well yet. - type = oiio.FLOAT - data = ramp.read_image(type) - - WriteSPI1D(outputPath, minValue, maxValue, data, width, channels) - -def generate3dLUTImage(ramp3dPath, resolution=32): - args = ["--generate", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--output", ramp3dPath] - lutExtract = process.Process(description="generate a 3d LUT image", cmd="ociolutimage", args=args) - lutExtract.execute() - -def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32): - if outputPath == None: - outputPath = ramp3dPath + ".spi3d" - - args = ["--extract", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--input", ramp3dPath, "--output", outputPath] - lutExtract = process.Process(description="extract a 3d LUT", cmd="ociolutimage", args=args) - lutExtract.execute() - -def applyCTLToImage(inputImage, - outputImage, - ctlPaths=[], - inputScale=1.0, - outputScale=1.0, - globalParams={}, - acesCTLReleaseDir=None): - if len(ctlPaths) > 0: - ctlenv = os.environ - if acesCTLReleaseDir != None: - ctlModulePath = "%s/utilities" % acesCTLReleaseDir - ctlenv['CTL_MODULE_PATH'] = ctlModulePath - - args = [] - for ctl in ctlPaths: - args += ['-ctl', ctl] - args += ["-force"] - #args += ["-verbose"] - args += ["-input_scale", str(inputScale)] - args += ["-output_scale", str(outputScale)] - args += ["-global_param1", "aIn", "1.0"] - for key, value in globalParams.iteritems(): - args += ["-global_param1", key, str(value)] - args += [inputImage] - args += [outputImage] - - #print( "args : %s" % args ) - - ctlp = process.Process(description="a ctlrender process", cmd="ctlrender", args=args, env=ctlenv ) - - ctlp.execute() - -def convertBitDepth(inputImage, outputImage, depth): - args = [inputImage, "-d", depth, "-o", outputImage] - convert = process.Process(description="convert image bit depth", cmd="oiiotool", args=args) - convert.execute() - -def generate1dLUTFromCTL(lutPath, - ctlPaths, - lutResolution=1024, - identityLutBitDepth='half', - inputScale=1.0, - outputScale=1.0, - globalParams={}, - cleanup=True, - acesCTLReleaseDir=None, - minValue=0.0, - maxValue=1.0): - #print( lutPath ) - #print( ctlPaths ) - - lutPathBase = os.path.splitext(lutPath)[0] - - identityLUTImageFloat = lutPathBase + ".float.tiff" - generate1dLUTImage(identityLUTImageFloat, lutResolution, minValue, maxValue) - - if identityLutBitDepth != 'half': - identityLUTImage = lutPathBase + ".uint16.tiff" - convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth) - else: - identityLUTImage = identityLUTImageFloat - - transformedLUTImage = lutPathBase + ".transformed.exr" - applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir) - - generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue) - - if cleanup: - os.remove(identityLUTImage) - if identityLUTImage != identityLUTImageFloat: - os.remove(identityLUTImageFloat) - os.remove(transformedLUTImage) - -def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution): - # open image - transformed = oiio.ImageInput.open( transformedLUTImage ) - - # get image specs - transformedSpec = transformed.spec() - type = transformedSpec.format.basetype - width = transformedSpec.width - height = transformedSpec.height - channels = transformedSpec.nchannels - - # rotate or not - if width != lutResolution * lutResolution or height != lutResolution: - print( "Correcting image as resolution is off. Found %d x %d. Expected %d x %d" % (width, height, lutResolution * lutResolution, lutResolution) ) - print( "Generating %s" % correctedLUTImage) - - # - # We're going to generate a new correct image - # - - # Get the source data - # Force data to be read as float. The Python API doesn't handle half-floats well yet. - type = oiio.FLOAT - sourceData = transformed.read_image(type) - - format = os.path.splitext(correctedLUTImage)[1] - correct = oiio.ImageOutput.create(correctedLUTImage) - - # set image specs - correctSpec = oiio.ImageSpec() - correctSpec.set_format( oiio.FLOAT ) - correctSpec.width = height - correctSpec.height = width - correctSpec.nchannels = channels - - correct.open (correctedLUTImage, correctSpec, oiio.Create) - - destData = array.array("f", "\0" * correctSpec.width * correctSpec.height * correctSpec.nchannels * 4) - for j in range(0, correctSpec.height): - for i in range(0, correctSpec.width): - for c in range(0, correctSpec.nchannels): - #print( i, j, c ) - destData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c] = sourceData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c] - - correct.write_image(correctSpec.format, destData) - correct.close() - else: - #shutil.copy(transformedLUTImage, correctedLUTImage) - correctedLUTImage = transformedLUTImage - - transformed.close() - - return correctedLUTImage - -def generate3dLUTFromCTL(lutPath, - ctlPaths, - lutResolution=64, - identityLutBitDepth='half', - inputScale=1.0, - outputScale=1.0, - globalParams={}, - cleanup=True, - acesCTLReleaseDir=None): - #print( lutPath ) - #print( ctlPaths ) - - lutPathBase = os.path.splitext(lutPath)[0] - - identityLUTImageFloat = lutPathBase + ".float.tiff" - generate3dLUTImage(identityLUTImageFloat, lutResolution) - - - if identityLutBitDepth != 'half': - identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff" - convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth) - else: - identityLUTImage = identityLUTImageFloat - - transformedLUTImage = lutPathBase + ".transformed.exr" - applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir) - - correctedLUTImage = lutPathBase + ".correct.exr" - correctedLUTImage = correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution) - - generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution) - - if cleanup: - os.remove(identityLUTImage) - if identityLUTImage != identityLUTImageFloat: - os.remove(identityLUTImageFloat) - os.remove(transformedLUTImage) - if correctedLUTImage != transformedLUTImage: - os.remove(correctedLUTImage) - #os.remove(correctedLUTImage) - def generateOCIOTransform(transforms): #print( "Generating transforms") @@ -614,7 +365,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir ] lut = "%s_to_ACES.spi1d" % name - generate1dLUTFromCTL( lutDir + "/" + lut, + genlut.generate1dLUTFromCTL( lutDir + "/" + lut, ctls, lutResolution1d, 'float', @@ -665,7 +416,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir ] lut = "%s_to_aces.spi1d" % name - generate1dLUTFromCTL( lutDir + "/" + lut, + genlut.generate1dLUTFromCTL( lutDir + "/" + lut, ctls, lutResolution1d, 'uint16', @@ -818,7 +569,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes data.append(cid_to_rle(x)) lut = 'ADX_CID_to_RLE.spi1d' - WriteSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1) + genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1) return lut @@ -893,7 +644,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes data[c] = cineonToLinear(1023.0*c/(lutResolution1d-1)) lut = "CineonLog_to_linear.spi1d" - WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) + genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) cs.toReferenceTransforms.append( { 'type':'lutFile', @@ -1021,7 +772,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1)) lut = "%s_to_linear.spi1d" % transferFunction - WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) + genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) cs.toReferenceTransforms.append( { 'type':'lutFile', @@ -1184,7 +935,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1)) lut = "%s_to_linear.spi1d" % transferFunction - WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) + genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) #print( "Writing %s" % lut) @@ -1200,7 +951,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1)) lut = "%s_to_linear.spi1d" % transferFunction - WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) + genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) #print( "Writing %s" % lut) @@ -1216,7 +967,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1)) lut = "%s_to_linear.spi1d" % transferFunction - WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) + genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) #print( "Writing %s" % lut) @@ -1398,7 +1149,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex)) lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex)) - WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) + genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1) #print( "Writing %s" % lut) cs.toReferenceTransforms.append( { @@ -1463,7 +1214,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes ] lut = "%s_to_aces.spi1d" % name - generate1dLUTFromCTL( lutDir + "/" + lut, + genlut.generate1dLUTFromCTL( lutDir + "/" + lut, ctls, lutResolution1d, 'float', @@ -1518,7 +1269,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes ctls = [ shaperToACESCTL % acesCTLReleaseDir ] - generate1dLUTFromCTL( lutDir + "/" + shaperLut, + genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut, ctls, lutResolution1d, 'float', @@ -1547,7 +1298,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes ] lut = "%s.%s.spi3d" % (shaperName, lmtName) - generate3dLUTFromCTL( lutDir + "/" + lut, + genlut.generate3dLUTFromCTL( lutDir + "/" + lut, ctls, lutResolution3d, 'float', @@ -1577,7 +1328,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes ] lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName) - generate3dLUTFromCTL( lutDir + "/" + lut, + genlut.generate3dLUTFromCTL( lutDir + "/" + lut, ctls, lutResolution3d, 'half', @@ -1681,7 +1432,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes ctls = [ shaperToACESCTL % acesCTLReleaseDir ] - generate1dLUTFromCTL( lutDir + "/" + shaperLut, + genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut, ctls, lutResolution1d, 'float', @@ -1726,7 +1477,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes ] lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName) - generate3dLUTFromCTL( lutDir + "/" + lut, + genlut.generate3dLUTFromCTL( lutDir + "/" + lut, #shaperLUT, ctls, lutResolution3d, @@ -1774,7 +1525,7 @@ def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutRes ] lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName) - generate3dLUTFromCTL( lutDir + "/" + lut, + genlut.generate3dLUTFromCTL( lutDir + "/" + lut, #None, ctls, lutResolution3d, diff --git a/aces_1.0.0/python/generateLUT.py b/aces_1.0.0/python/generateLUT.py new file mode 100644 index 0000000..874157b --- /dev/null +++ b/aces_1.0.0/python/generateLUT.py @@ -0,0 +1,391 @@ +''' +build instructions for osx for needed packages. + +#opencolorio +brew install -vd opencolorio --with-python + +#openimageio +brew tap homebrew/science + +# optional installs +brew install -vd libRaw +brew install -vd OpenCV + +brew install -vd openimageio --with-python + +#ctl +brew install -vd CTL + +#opencolorio - again. +# this time, 'ociolutimage' will build because openimageio is installed +brew uninstall -vd opencolorio +brew install -vd opencolorio --with-python +''' + +import sys +import os +import array + +import OpenImageIO as oiio + +import process + +# +# Functions used to generate LUTs using CTL transforms +# +def generate1dLUTImage(ramp1dPath, resolution=1024, minValue=0.0, maxValue=1.0): + #print( "Generate 1d LUT image - %s" % ramp1dPath) + + # open image + format = os.path.splitext(ramp1dPath)[1] + ramp = oiio.ImageOutput.create(ramp1dPath) + + # set image specs + spec = oiio.ImageSpec() + spec.set_format( oiio.FLOAT ) + #spec.format.basetype = oiio.FLOAT + spec.width = resolution + spec.height = 1 + spec.nchannels = 3 + + ramp.open (ramp1dPath, spec, oiio.Create) + + data = array.array("f", "\0" * spec.width * spec.height * spec.nchannels * 4) + for i in range(resolution): + value = float(i)/(resolution-1) * (maxValue - minValue) + minValue + data[i*spec.nchannels +0] = value + data[i*spec.nchannels +1] = value + data[i*spec.nchannels +2] = value + + ramp.write_image(spec.format, data) + ramp.close() + +# Credit to Alex Fry for the original single channel version of the spi1d writer +def writeSPI1D(filename, fromMin, fromMax, data, entries, channels): + f = file(filename,'w') + f.write("Version 1\n") + f.write("From %f %f\n" % (fromMin, fromMax)) + f.write("Length %d\n" % entries) + f.write("Components %d\n" % (min(3, channels)) ) + f.write("{\n") + for i in range(0, entries): + entry = "" + for j in range(0, min(3, channels)): + entry = "%s %s" % (entry, data[i*channels + j]) + f.write(" %s\n" % entry) + f.write("}\n") + f.close() + +def generate1dLUTFromImage(ramp1dPath, outputPath=None, minValue=0.0, maxValue=1.0): + if outputPath == None: + outputPath = ramp1dPath + ".spi1d" + + # open image + ramp = oiio.ImageInput.open( ramp1dPath ) + + # get image specs + spec = ramp.spec() + type = spec.format.basetype + width = spec.width + height = spec.height + channels = spec.nchannels + + # get data + # Force data to be read as float. The Python API doesn't handle half-floats well yet. + type = oiio.FLOAT + data = ramp.read_image(type) + + writeSPI1D(outputPath, minValue, maxValue, data, width, channels) + +def generate3dLUTImage(ramp3dPath, resolution=32): + args = ["--generate", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--output", ramp3dPath] + lutExtract = process.Process(description="generate a 3d LUT image", cmd="ociolutimage", args=args) + lutExtract.execute() + +def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32): + if outputPath == None: + outputPath = ramp3dPath + ".spi3d" + + args = ["--extract", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--input", ramp3dPath, "--output", outputPath] + lutExtract = process.Process(description="extract a 3d LUT", cmd="ociolutimage", args=args) + lutExtract.execute() + +def applyCTLToImage(inputImage, + outputImage, + ctlPaths=[], + inputScale=1.0, + outputScale=1.0, + globalParams={}, + acesCTLReleaseDir=None): + if len(ctlPaths) > 0: + ctlenv = os.environ + if acesCTLReleaseDir != None: + if os.path.split(acesCTLReleaseDir)[1] != "utilities": + ctlModulePath = "%s/utilities" % acesCTLReleaseDir + else: + ctlModulePath = acesCTLReleaseDir + ctlenv['CTL_MODULE_PATH'] = ctlModulePath + + args = [] + for ctl in ctlPaths: + args += ['-ctl', ctl] + args += ["-force"] + #args += ["-verbose"] + args += ["-input_scale", str(inputScale)] + args += ["-output_scale", str(outputScale)] + args += ["-global_param1", "aIn", "1.0"] + for key, value in globalParams.iteritems(): + args += ["-global_param1", key, str(value)] + args += [inputImage] + args += [outputImage] + + #print( "args : %s" % args ) + + ctlp = process.Process(description="a ctlrender process", cmd="ctlrender", args=args, env=ctlenv ) + + ctlp.execute() + +def convertBitDepth(inputImage, outputImage, depth): + args = [inputImage, "-d", depth, "-o", outputImage] + convert = process.Process(description="convert image bit depth", cmd="oiiotool", args=args) + convert.execute() + +def generate1dLUTFromCTL(lutPath, + ctlPaths, + lutResolution=1024, + identityLutBitDepth='half', + inputScale=1.0, + outputScale=1.0, + globalParams={}, + cleanup=True, + acesCTLReleaseDir=None, + minValue=0.0, + maxValue=1.0): + #print( lutPath ) + #print( ctlPaths ) + + lutPathBase = os.path.splitext(lutPath)[0] + + identityLUTImageFloat = lutPathBase + ".float.tiff" + generate1dLUTImage(identityLUTImageFloat, lutResolution, minValue, maxValue) + + if identityLutBitDepth != 'half': + identityLUTImage = lutPathBase + ".uint16.tiff" + convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth) + else: + identityLUTImage = identityLUTImageFloat + + transformedLUTImage = lutPathBase + ".transformed.exr" + applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir) + + generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue) + + if cleanup: + os.remove(identityLUTImage) + if identityLUTImage != identityLUTImageFloat: + os.remove(identityLUTImageFloat) + os.remove(transformedLUTImage) + +def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution): + # open image + transformed = oiio.ImageInput.open( transformedLUTImage ) + + # get image specs + transformedSpec = transformed.spec() + type = transformedSpec.format.basetype + width = transformedSpec.width + height = transformedSpec.height + channels = transformedSpec.nchannels + + # rotate or not + if width != lutResolution * lutResolution or height != lutResolution: + print( "Correcting image as resolution is off. Found %d x %d. Expected %d x %d" % (width, height, lutResolution * lutResolution, lutResolution) ) + print( "Generating %s" % correctedLUTImage) + + # + # We're going to generate a new correct image + # + + # Get the source data + # Force data to be read as float. The Python API doesn't handle half-floats well yet. + type = oiio.FLOAT + sourceData = transformed.read_image(type) + + format = os.path.splitext(correctedLUTImage)[1] + correct = oiio.ImageOutput.create(correctedLUTImage) + + # set image specs + correctSpec = oiio.ImageSpec() + correctSpec.set_format( oiio.FLOAT ) + correctSpec.width = height + correctSpec.height = width + correctSpec.nchannels = channels + + correct.open (correctedLUTImage, correctSpec, oiio.Create) + + destData = array.array("f", "\0" * correctSpec.width * correctSpec.height * correctSpec.nchannels * 4) + for j in range(0, correctSpec.height): + for i in range(0, correctSpec.width): + for c in range(0, correctSpec.nchannels): + #print( i, j, c ) + destData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c] = sourceData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c] + + correct.write_image(correctSpec.format, destData) + correct.close() + else: + #shutil.copy(transformedLUTImage, correctedLUTImage) + correctedLUTImage = transformedLUTImage + + transformed.close() + + return correctedLUTImage + +def generate3dLUTFromCTL(lutPath, + ctlPaths, + lutResolution=64, + identityLutBitDepth='half', + inputScale=1.0, + outputScale=1.0, + globalParams={}, + cleanup=True, + acesCTLReleaseDir=None): + #print( lutPath ) + #print( ctlPaths ) + + lutPathBase = os.path.splitext(lutPath)[0] + + identityLUTImageFloat = lutPathBase + ".float.tiff" + generate3dLUTImage(identityLUTImageFloat, lutResolution) + + + if identityLutBitDepth != 'half': + identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff" + convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth) + else: + identityLUTImage = identityLUTImageFloat + + transformedLUTImage = lutPathBase + ".transformed.exr" + applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir) + + correctedLUTImage = lutPathBase + ".correct.exr" + correctedLUTImage = correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution) + + generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution) + + if cleanup: + os.remove(identityLUTImage) + if identityLUTImage != identityLUTImageFloat: + os.remove(identityLUTImageFloat) + os.remove(transformedLUTImage) + if correctedLUTImage != transformedLUTImage: + os.remove(correctedLUTImage) + #os.remove(correctedLUTImage) + +def main(): + import optparse + + p = optparse.OptionParser(description='A utility to generate LUTs from CTL', + prog='generateLUT', + version='0.01', + usage='%prog [options]') + + p.add_option('--lut', '-l', type="string", default="") + p.add_option('--ctl', '-c', type="string", action="append") + p.add_option('--lutResolution1d', '', type="int", default=1024) + p.add_option('--lutResolution3d', '', type="int", default=33) + p.add_option('--ctlReleasePath', '-r', type="string", default="") + p.add_option('--bitDepth', '-b', type="string", default="float") + p.add_option('--keepTempImages', '', action="store_true") + p.add_option('--minValue', '', type="float", default=0.0) + p.add_option('--maxValue', '', type="float", default=1.0) + p.add_option('--inputScale', '', type="float", default=1.0) + p.add_option('--outputScale', '', type="float", default=1.0) + p.add_option('--ctlRenderParam', '-p', type="string", nargs=2, action="append") + + p.add_option('--generate1d', '', action="store_true") + p.add_option('--generate3d', '', action="store_true") + + options, arguments = p.parse_args() + + # + # Get options + # + lut = options.lut + ctls = options.ctl + lutResolution1d = options.lutResolution1d + lutResolution3d = options.lutResolution3d + minValue = options.minValue + maxValue = options.maxValue + inputScale = options.inputScale + outputScale = options.outputScale + ctlReleasePath = options.ctlReleasePath + generate1d = options.generate1d == True + generate3d = options.generate3d == True + bitDepth = options.bitDepth + cleanup = not options.keepTempImages + + params = {} + if options.ctlRenderParam != None: + for param in options.ctlRenderParam: + params[param[0]] = float(param[1]) + + try: + argsStart = sys.argv.index('--') + 1 + args = sys.argv[argsStart:] + except: + argsStart = len(sys.argv)+1 + args = [] + + #print( "command line : \n%s\n" % " ".join(sys.argv) ) + + # + # Generate LUTs + # + if generate1d: + print( "1D LUT generation options") + else: + print( "3D LUT generation options") + + print( "lut : %s" % lut ) + print( "ctls : %s" % ctls ) + print( "lut res 1d : %s" % lutResolution1d ) + print( "lut res 3d : %s" % lutResolution3d ) + print( "min value : %s" % minValue ) + print( "max value : %s" % maxValue ) + print( "input scale : %s" % inputScale ) + print( "output scale : %s" % outputScale ) + print( "ctl render params : %s" % params ) + print( "ctl release path : %s" % ctlReleasePath ) + print( "bit depth of input : %s" % bitDepth ) + print( "cleanup temp images : %s" % cleanup) + + if generate1d: + generate1dLUTFromCTL( lut, + ctls, + lutResolution1d, + bitDepth, + inputScale, + outputScale, + params, + cleanup, + ctlReleasePath, + minValue, + maxValue) + + elif generate3d: + generate3dLUTFromCTL( lut, + ctls, + lutResolution3d, + bitDepth, + inputScale, + outputScale, + params, + cleanup, + ctlReleasePath) + else: + print( "\n\nNo LUT generated. You must choose either 1D or 3D LUT generation\n\n") +# main + +if __name__ == '__main__': + main() +