5 sys.path.append( "/path/to/script" )
6 import create_aces_config as cac
7 acesReleaseCTLDir = "/path/to/github/checkout/releases/v0.7.1/transforms/ctl"
8 configDir = "/path/to/config/dir"
9 cac.createACESConfig(acesReleaseCTLDir, configDir, 1024, 33, True)
11 usage from command line, from the directory with 'create_aces_config.py'
12 python create_aces_config.py -a "/path/to/github/checkout/releases/v0.7.1/transforms/ctl" -c "/path/to/config/dir" --lutResolution1d 1024 --lutResolution3d 33 --keepTempImages
16 build instructions for osx for needed packages.
19 brew install -vd opencolorio --with-python
22 brew tap homebrew/science
25 brew install -vd libRaw
26 brew install -vd OpenCV
28 brew install -vd openimageio --with-python
34 # this time, 'ociolutimage' will build because openimageio is installed
35 brew uninstall -vd opencolorio
36 brew install -vd opencolorio --with-python
48 import OpenImageIO as oiio
49 import PyOpenColorIO as OCIO
56 def setConfigDefaultRoles( config,
68 if color_picking: config.setRole( OCIO.Constants.ROLE_COLOR_PICKING, color_picking )
69 if color_timing: config.setRole( OCIO.Constants.ROLE_COLOR_TIMING, color_timing )
70 if compositing_log: config.setRole( OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log )
71 if data: config.setRole( OCIO.Constants.ROLE_DATA, data )
72 if default: config.setRole( OCIO.Constants.ROLE_DEFAULT, default )
73 if matte_paint: config.setRole( OCIO.Constants.ROLE_MATTE_PAINT, matte_paint )
74 if reference: config.setRole( OCIO.Constants.ROLE_REFERENCE, reference )
75 if scene_linear: config.setRole( OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear )
76 if texture_paint: config.setRole( OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint )
80 # Write config to disk
81 def writeConfig( config, configPath, sanityCheck=True ):
87 print "Configuration was not written due to a failed Sanity Check"
91 fileHandle = open( configPath, mode='w' )
92 fileHandle.write( config.serialize() )
96 # Functions used to generate LUTs using CTL transforms
98 def generate1dLUTImage(ramp1dPath, resolution=1024, minValue=0.0, maxValue=1.0):
99 #print( "Generate 1d LUT image - %s" % ramp1dPath)
102 format = os.path.splitext(ramp1dPath)[1]
103 ramp = oiio.ImageOutput.create(ramp1dPath)
106 spec = oiio.ImageSpec()
107 spec.set_format( oiio.FLOAT )
108 #spec.format.basetype = oiio.FLOAT
109 spec.width = resolution
113 ramp.open (ramp1dPath, spec, oiio.Create)
115 data = array.array("f", "\0" * spec.width * spec.height * spec.nchannels * 4)
116 for i in range(resolution):
117 value = float(i)/(resolution-1) * (maxValue - minValue) + minValue
118 data[i*spec.nchannels +0] = value
119 data[i*spec.nchannels +1] = value
120 data[i*spec.nchannels +2] = value
122 ramp.write_image(spec.format, data)
125 # Credit to Alex Fry for the original single channel version of the spi1d writer
126 def WriteSPI1D(filename, fromMin, fromMax, data, entries, channels):
127 f = file(filename,'w')
128 f.write("Version 1\n")
129 f.write("From %f %f\n" % (fromMin, fromMax))
130 f.write("Length %d\n" % entries)
131 f.write("Components %d\n" % (min(3, channels)) )
133 for i in range(0, entries):
135 for j in range(0, min(3, channels)):
136 entry = "%s %s" % (entry, data[i*channels + j])
137 f.write(" %s\n" % entry)
141 def generate1dLUTFromImage(ramp1dPath, outputPath=None, minValue=0.0, maxValue=1.0):
142 if outputPath == None:
143 outputPath = ramp1dPath + ".spi1d"
146 ramp = oiio.ImageInput.open( ramp1dPath )
150 type = spec.format.basetype
153 channels = spec.nchannels
156 # Force data to be read as float. The Python API doesn't handle half-floats well yet.
158 data = ramp.read_image(type)
160 WriteSPI1D(outputPath, minValue, maxValue, data, width, channels)
162 def generate3dLUTImage(ramp3dPath, resolution=32):
163 args = ["--generate", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--output", ramp3dPath]
164 lutExtract = process.Process(description="generate a 3d LUT image", cmd="ociolutimage", args=args)
167 def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32):
168 if outputPath == None:
169 outputPath = ramp3dPath + ".spi3d"
171 args = ["--extract", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--input", ramp3dPath, "--output", outputPath]
172 lutExtract = process.Process(description="extract a 3d LUT", cmd="ociolutimage", args=args)
175 def applyCTLToImage(inputImage,
181 acesCTLReleaseDir=None):
182 if len(ctlPaths) > 0:
184 if acesCTLReleaseDir != None:
185 ctlModulePath = "%s/utilities" % acesCTLReleaseDir
186 ctlenv['CTL_MODULE_PATH'] = ctlModulePath
190 args += ['-ctl', ctl]
192 #args += ["-verbose"]
193 args += ["-input_scale", str(inputScale)]
194 args += ["-output_scale", str(outputScale)]
195 args += ["-global_param1", "aIn", "1.0"]
196 for key, value in globalParams.iteritems():
197 args += ["-global_param1", key, str(value)]
199 args += [outputImage]
201 #print( "args : %s" % args )
203 ctlp = process.Process(description="a ctlrender process", cmd="ctlrender", args=args, env=ctlenv )
207 def convertBitDepth(inputImage, outputImage, depth):
208 args = [inputImage, "-d", depth, "-o", outputImage]
209 convert = process.Process(description="convert image bit depth", cmd="oiiotool", args=args)
212 def generate1dLUTFromCTL(lutPath,
215 identityLutBitDepth='half',
220 acesCTLReleaseDir=None,
226 lutPathBase = os.path.splitext(lutPath)[0]
228 identityLUTImageFloat = lutPathBase + ".float.tiff"
229 generate1dLUTImage(identityLUTImageFloat, lutResolution, minValue, maxValue)
231 if identityLutBitDepth != 'half':
232 identityLUTImage = lutPathBase + ".uint16.tiff"
233 convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth)
235 identityLUTImage = identityLUTImageFloat
237 transformedLUTImage = lutPathBase + ".transformed.exr"
238 applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir)
240 generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue)
243 os.remove(identityLUTImage)
244 if identityLUTImage != identityLUTImageFloat:
245 os.remove(identityLUTImageFloat)
246 os.remove(transformedLUTImage)
248 def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution):
250 transformed = oiio.ImageInput.open( transformedLUTImage )
253 transformedSpec = transformed.spec()
254 type = transformedSpec.format.basetype
255 width = transformedSpec.width
256 height = transformedSpec.height
257 channels = transformedSpec.nchannels
260 if width != lutResolution * lutResolution or height != lutResolution:
261 print( "Correcting image as resolution is off. Found %d x %d. Expected %d x %d" % (width, height, lutResolution * lutResolution, lutResolution) )
262 print( "Generating %s" % correctedLUTImage)
265 # We're going to generate a new correct image
268 # Get the source data
269 # Force data to be read as float. The Python API doesn't handle half-floats well yet.
271 sourceData = transformed.read_image(type)
273 format = os.path.splitext(correctedLUTImage)[1]
274 correct = oiio.ImageOutput.create(correctedLUTImage)
277 correctSpec = oiio.ImageSpec()
278 correctSpec.set_format( oiio.FLOAT )
279 correctSpec.width = height
280 correctSpec.height = width
281 correctSpec.nchannels = channels
283 correct.open (correctedLUTImage, correctSpec, oiio.Create)
285 destData = array.array("f", "\0" * correctSpec.width * correctSpec.height * correctSpec.nchannels * 4)
286 for j in range(0, correctSpec.height):
287 for i in range(0, correctSpec.width):
288 for c in range(0, correctSpec.nchannels):
290 destData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c] = sourceData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c]
292 correct.write_image(correctSpec.format, destData)
295 #shutil.copy(transformedLUTImage, correctedLUTImage)
296 correctedLUTImage = transformedLUTImage
300 return correctedLUTImage
302 def generate3dLUTFromCTL(lutPath,
305 identityLutBitDepth='half',
310 acesCTLReleaseDir=None):
314 lutPathBase = os.path.splitext(lutPath)[0]
316 identityLUTImageFloat = lutPathBase + ".float.tiff"
317 generate3dLUTImage(identityLUTImageFloat, lutResolution)
320 if identityLutBitDepth != 'half':
321 identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff"
322 convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth)
324 identityLUTImage = identityLUTImageFloat
326 transformedLUTImage = lutPathBase + ".transformed.exr"
327 applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir)
329 correctedLUTImage = lutPathBase + ".correct.exr"
330 correctedLUTImage = correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution)
332 generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution)
335 os.remove(identityLUTImage)
336 if identityLUTImage != identityLUTImageFloat:
337 os.remove(identityLUTImageFloat)
338 os.remove(transformedLUTImage)
339 if correctedLUTImage != transformedLUTImage:
340 os.remove(correctedLUTImage)
341 #os.remove(correctedLUTImage)
343 def generateOCIOTransform(transforms):
344 #print( "Generating transforms")
346 interpolationOptions = {
347 'linear':OCIO.Constants.INTERP_LINEAR,
348 'nearest':OCIO.Constants.INTERP_NEAREST,
349 'tetrahedral':OCIO.Constants.INTERP_TETRAHEDRAL
352 'forward':OCIO.Constants.TRANSFORM_DIR_FORWARD,
353 'inverse':OCIO.Constants.TRANSFORM_DIR_INVERSE
358 for transform in transforms:
359 if transform['type'] == 'lutFile':
361 ocioTransform = OCIO.FileTransform( src=transform['path'],
362 interpolation=interpolationOptions[transform['interpolation']],
363 direction=directionOptions[transform['direction']] )
365 ocioTransforms.append(ocioTransform)
366 elif transform['type'] == 'matrix':
367 ocioTransform = OCIO.MatrixTransform()
368 # MatrixTransform member variables can't be initialized directly. Each must be set individually
369 ocioTransform.setMatrix( transform['matrix'] )
371 if 'offset' in transform:
372 ocioTransform.setOffset( transform['offset'] )
373 if 'direction' in transform:
374 ocioTransform.setDirection( directionOptions[transform['direction']] )
376 ocioTransforms.append(ocioTransform)
377 elif transform['type'] == 'exponent':
378 ocioTransform = OCIO.ExponentTransform()
379 ocioTransform.setValue( transform['value'] )
381 ocioTransforms.append(ocioTransform)
382 elif transform['type'] == 'log':
383 ocioTransform = OCIO.LogTransform(base=transform['base'],
384 direction=directionOptions[transform['direction']])
386 ocioTransforms.append(ocioTransform)
388 print( "Ignoring unknown transform type : %s" % transform['type'] )
390 # Build a group transform if necessary
391 if len(ocioTransforms) > 1:
392 transformG = OCIO.GroupTransform()
393 for transform in ocioTransforms:
394 transformG.push_back( transform )
395 transform = transformG
397 # Or take the first transform from the list
399 transform = ocioTransforms[0]
403 def createConfig(configData, nuke=False):
405 config = OCIO.Config()
408 # Set config wide values
410 config.setDescription( "An ACES config generated from python" )
411 config.setSearchPath( "luts" )
414 # Define the reference color space
416 referenceData = configData['referenceColorSpace']
417 print( "Adding the reference color space : %s" % referenceData.name)
419 # Create a color space
420 reference = OCIO.ColorSpace( name=referenceData.name,
421 bitDepth=referenceData.bitDepth,
422 description=referenceData.description,
423 equalityGroup=referenceData.equalityGroup,
424 family=referenceData.family,
425 isData=referenceData.isData,
426 allocation=referenceData.allocationType,
427 allocationVars=referenceData.allocationVars )
430 config.addColorSpace( reference )
433 # Create the rest of the color spaces
435 #sortedColorspaces = sorted(configData['colorSpaces'], key=lambda x: x.name)
436 #print( sortedColorspaces )
437 #for colorspace in sortedColorspaces:
438 for colorspace in sorted(configData['colorSpaces']):
439 print( "Creating new color space : %s" % colorspace.name)
441 ocioColorspace = OCIO.ColorSpace( name=colorspace.name,
442 bitDepth=colorspace.bitDepth,
443 description=colorspace.description,
444 equalityGroup=colorspace.equalityGroup,
445 family=colorspace.family,
446 isData=colorspace.isData,
447 allocation=colorspace.allocationType,
448 allocationVars=colorspace.allocationVars )
450 if colorspace.toReferenceTransforms != []:
451 print( "Generating To-Reference transforms")
452 ocioTransform = generateOCIOTransform(colorspace.toReferenceTransforms)
453 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE )
455 if colorspace.fromReferenceTransforms != []:
456 print( "Generating From-Reference transforms")
457 ocioTransform = generateOCIOTransform(colorspace.fromReferenceTransforms)
458 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE )
460 config.addColorSpace(ocioColorspace)
465 # Define the views and displays
470 # Generic display and view setup
472 for display, viewList in configData['displays'].iteritems():
473 for viewName, colorspace in viewList.iteritems():
474 config.addDisplay( display, viewName, colorspace.name )
475 if not (viewName in views):
476 views.append(viewName)
477 displays.append(display)
478 # A Nuke specific set of views and displays
481 # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
484 for display, viewList in configData['displays'].iteritems():
485 for viewName, colorspace in viewList.iteritems():
486 if( viewName == 'Output Transform'):
488 config.addDisplay( display, viewName, colorspace.name )
489 if not (viewName in views):
490 views.append(viewName)
491 displays.append(display)
493 config.addDisplay( 'linear', 'View', 'ACES2065-1' )
494 displays.append('linear')
495 config.addDisplay( 'log', 'View', 'ACEScc' )
496 displays.append('log')
498 # Set active displays and views
499 config.setActiveDisplays( ','.join(sorted(displays)) )
500 config.setActiveViews( ','.join(views) )
503 # Need to generalize this at some point
507 setConfigDefaultRoles( config,
508 color_picking=reference.getName(),
509 color_timing=reference.getName(),
510 compositing_log=reference.getName(),
511 data=reference.getName(),
512 default=reference.getName(),
513 matte_paint=reference.getName(),
514 reference=reference.getName(),
515 scene_linear=reference.getName(),
516 texture_paint=reference.getName() )
518 # Check to make sure we didn't screw something up
524 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
527 "A container for data needed to define an OCIO 'Color Space' "
532 bitDepth=OCIO.Constants.BIT_DEPTH_F32,
536 toReferenceTransforms=[],
537 fromReferenceTransforms=[],
538 allocationType=OCIO.Constants.ALLOCATION_UNIFORM,
539 allocationVars=[0.0, 1.0]):
540 "Initialize the standard class variables"
542 self.bitDepth=bitDepth
543 self.description = description
544 self.equalityGroup=equalityGroup
547 self.toReferenceTransforms=toReferenceTransforms
548 self.fromReferenceTransforms=fromReferenceTransforms
549 self.allocationType=allocationType
550 self.allocationVars=allocationVars
552 # Create a 4x4 matrix (list) based on a 3x3 matrix (list) input
553 def mat44FromMat33(mat33):
554 return [mat33[0], mat33[1], mat33[2], 0.0,
555 mat33[3], mat33[4], mat33[5], 0.0,
556 mat33[6], mat33[7], mat33[8], 0.0,
560 # Output is a list of colorspaces and transforms that convert between those
561 # colorspaces and reference color space, ACES
562 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d=4096, lutResolution3d=64, cleanup=True):
563 print( "generateLUTs - begin" )
567 # Define the reference color space
569 ACES = ColorSpace('ACES2065-1')
570 ACES.description = "The Academy Color Encoding System reference color space"
571 ACES.equalityGroup = ''
574 ACES.allocationType=OCIO.Constants.ALLOCATION_LG2
575 ACES.allocationVars=[-15, 6]
577 configData['referenceColorSpace'] = ACES
580 # Define the displays
582 configData['displays'] = {}
585 # Define the other color spaces
587 configData['colorSpaces'] = []
589 # Matrix converting ACES AP1 primaries to AP0
590 acesAP1toAP0 = [ 0.6954522414, 0.1406786965, 0.1638690622,
591 0.0447945634, 0.8596711185, 0.0955343182,
592 -0.0055258826, 0.0040252103, 1.0015006723]
594 # Matrix converting ACES AP0 primaries to XYZ
595 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
596 0.3439664498, 0.7281660966, -0.0721325464,
597 0.0000000000, 0.0000000000, 1.0088251844]
602 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
603 cs = ColorSpace(name)
604 cs.description = "The %s color space" % name
605 cs.equalityGroup = ''
610 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
611 # This transform gets back to the AP1 primaries
612 # Useful as the 1d LUT is only covering the transfer function
613 # The primaries switch is covered by the matrix below
614 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
616 lut = "%s_to_ACES.spi1d" % name
617 generate1dLUTFromCTL( lutDir + "/" + lut,
629 cs.toReferenceTransforms = []
630 cs.toReferenceTransforms.append( {
633 'interpolation':'linear',
634 'direction':'forward'
637 # AP1 primaries to AP0 primaries
638 cs.toReferenceTransforms.append( {
640 'matrix':mat44FromMat33(acesAP1toAP0),
641 'direction':'forward'
644 cs.fromReferenceTransforms = []
647 ACEScc = createACEScc()
648 configData['colorSpaces'].append(ACEScc)
653 def createACESProxy(name='ACESproxy'):
654 cs = ColorSpace(name)
655 cs.description = "The %s color space" % name
656 cs.equalityGroup = ''
661 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
662 # This transform gets back to the AP1 primaries
663 # Useful as the 1d LUT is only covering the transfer function
664 # The primaries switch is covered by the matrix below
665 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
667 lut = "%s_to_aces.spi1d" % name
668 generate1dLUTFromCTL( lutDir + "/" + lut,
678 cs.toReferenceTransforms = []
679 cs.toReferenceTransforms.append( {
682 'interpolation':'linear',
683 'direction':'forward'
686 # AP1 primaries to AP0 primaries
687 cs.toReferenceTransforms.append( {
689 'matrix':mat44FromMat33(acesAP1toAP0),
690 'direction':'forward'
694 cs.fromReferenceTransforms = []
697 ACESproxy = createACESProxy()
698 configData['colorSpaces'].append(ACESproxy)
703 def createACEScg(name='ACEScg'):
704 cs = ColorSpace(name)
705 cs.description = "The %s color space" % name
706 cs.equalityGroup = ''
710 cs.toReferenceTransforms = []
712 # AP1 primaries to AP0 primaries
713 cs.toReferenceTransforms.append( {
715 'matrix':mat44FromMat33(acesAP1toAP0),
716 'direction':'forward'
719 cs.fromReferenceTransforms = []
722 ACEScg = createACEScg()
723 configData['colorSpaces'].append(ACEScg)
728 def createADX(bitdepth=10, name='ADX'):
729 name = "%s%s" % (name, bitdepth)
730 cs = ColorSpace(name)
731 cs.description = "%s color space - used for film scans" % name
732 cs.equalityGroup = ''
737 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
738 adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
739 0.0, 1023.0/500.0, 0.0, 0.0,
740 0.0, 0.0, 1023.0/500.0, 0.0,
742 offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
744 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
745 adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
746 0.0, 65535.0/8000.0, 0.0, 0.0,
747 0.0, 0.0, 65535.0/8000.0, 0.0,
749 offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
751 cs.toReferenceTransforms = []
753 # Convert from ADX to Channel-Dependent Density
754 cs.toReferenceTransforms.append( {
758 'direction':'forward'
761 # Convert from Channel-Dependent Density to Channel-Independent Density
762 cs.toReferenceTransforms.append( {
764 'matrix':[0.75573, 0.22197, 0.02230, 0,
765 0.05901, 0.96928, -0.02829, 0,
766 0.16134, 0.07406, 0.76460, 0,
768 'direction':'forward'
771 # Copied from Alex Fry's adx_cid_to_rle.py
772 def createCIDtoRLELUT():
773 def interpolate1D(x, xp, fp):
774 return numpy.interp(x, xp, fp)
776 LUT_1D_xp = [-0.190000000000000,
788 LUT_1D_fp = [-6.000000000000000,
800 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
804 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
805 return (100.0 / 55.0) * x - REF_PT
807 def Fit(value, fromMin, fromMax, toMin, toMax):
808 if fromMin == fromMax:
809 raise ValueError("fromMin == fromMax")
810 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
815 for i in xrange(NUM_SAMPLES):
816 x = i/(NUM_SAMPLES-1.0)
817 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
818 data.append(cid_to_rle(x))
820 lut = 'ADX_CID_to_RLE.spi1d'
821 WriteSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
825 # Convert Channel Independent Density values to Relative Log Exposure values
826 lut = createCIDtoRLELUT()
827 cs.toReferenceTransforms.append( {
830 'interpolation':'linear',
831 'direction':'forward'
834 # Convert Relative Log Exposure values to Relative Exposure values
835 cs.toReferenceTransforms.append( {
838 'direction':'inverse'
841 # Convert Relative Exposure values to ACES values
842 cs.toReferenceTransforms.append( {
844 'matrix':[0.72286, 0.12630, 0.15084, 0,
845 0.11923, 0.76418, 0.11659, 0,
846 0.01427, 0.08213, 0.90359, 0,
848 'direction':'forward'
851 cs.fromReferenceTransforms = []
854 ADX10 = createADX(bitdepth=10)
855 configData['colorSpaces'].append(ADX10)
857 ADX16 = createADX(bitdepth=16)
858 configData['colorSpaces'].append(ADX16)
864 def createREDlogFilm(gamut, transferFunction, name='REDlogFilm'):
865 name = "%s - %s" % (transferFunction, gamut)
866 if transferFunction == "":
867 name = "Linear - %s" % gamut
869 name = "%s" % transferFunction
871 cs = ColorSpace(name)
872 cs.description = name
873 cs.equalityGroup = ''
877 def cineonToLinear(codeValue):
881 codeValueToDensity = 0.002
883 blackLinear = pow(10.0, (blackPoint - whitePoint) * (codeValueToDensity / nGamma))
884 codeLinear = pow(10.0, (codeValue - whitePoint) * (codeValueToDensity / nGamma))
886 return (codeLinear - blackLinear)/(1.0 - blackLinear)
888 cs.toReferenceTransforms = []
890 if transferFunction == 'REDlogFilm':
891 data = array.array('f', "\0" * lutResolution1d * 4)
892 for c in range(lutResolution1d):
893 data[c] = cineonToLinear(1023.0*c/(lutResolution1d-1))
895 lut = "CineonLog_to_linear.spi1d"
896 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
898 cs.toReferenceTransforms.append( {
901 'interpolation':'linear',
902 'direction':'forward'
905 if gamut == 'DRAGONcolor':
906 cs.toReferenceTransforms.append( {
908 'matrix':mat44FromMat33([0.532279, 0.376648, 0.091073,
909 0.046344, 0.974513, -0.020860,
910 -0.053976, -0.000320, 1.054267]),
911 'direction':'forward'
913 elif gamut == 'REDcolor3':
914 cs.toReferenceTransforms.append( {
916 'matrix':mat44FromMat33([0.512136, 0.360370, 0.127494,
917 0.070377, 0.903884, 0.025737,
918 -0.020824, 0.017671, 1.003123]),
919 'direction':'forward'
921 elif gamut == 'REDcolor2':
922 cs.toReferenceTransforms.append( {
924 'matrix':mat44FromMat33([0.480997, 0.402289, 0.116714,
925 -0.004938, 1.000154, 0.004781,
926 -0.105257, 0.025320, 1.079907]),
927 'direction':'forward'
930 cs.fromReferenceTransforms = []
934 REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "REDlogFilm", name="REDlogFilm")
935 configData['colorSpaces'].append(REDlogFilmDRAGON)
937 REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "REDlogFilm", name="REDlogFilm")
938 configData['colorSpaces'].append(REDlogFilmREDcolor2)
940 REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "REDlogFilm", name="REDlogFilm")
941 configData['colorSpaces'].append(REDlogFilmREDcolor3)
944 REDlogFilmDRAGON = createREDlogFilm("", "REDlogFilm", name="REDlogFilm")
945 configData['colorSpaces'].append(REDlogFilmDRAGON)
948 REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "", name="REDlogFilm")
949 configData['colorSpaces'].append(REDlogFilmDRAGON)
951 REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "", name="REDlogFilm")
952 configData['colorSpaces'].append(REDlogFilmREDcolor2)
954 REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "", name="REDlogFilm")
955 configData['colorSpaces'].append(REDlogFilmREDcolor3)
960 def createCanonLog(gamut, transferFunction, name='Canon-Log'):
961 name = "%s - %s" % (transferFunction, gamut)
962 if transferFunction == "":
963 name = "Linear - %s" % gamut
965 name = "%s" % transferFunction
967 cs = ColorSpace(name)
968 cs.description = name
969 cs.equalityGroup = ''
973 def legalToFull(codeValue):
974 return (codeValue - 64.0)/(940.0 - 64.0)
976 def canonLogToLinear(codeValue):
977 # log = fullToLegal(c1 * log10(c2*linear + 1) + c3)
978 # linear = (pow(10, (legalToFul(log) - c3)/c1) - 1)/c2
983 linear = (pow(10.0, (legalToFull(codeValue) - c3)/c1) -1.0)/c2
984 linear = 0.9 * linear
985 #print( codeValue, linear )
988 cs.toReferenceTransforms = []
990 if transferFunction == "Canon-Log":
991 data = array.array('f', "\0" * lutResolution1d * 4)
992 for c in range(lutResolution1d):
993 data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1))
995 lut = "%s_to_linear.spi1d" % transferFunction
996 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
998 cs.toReferenceTransforms.append( {
1001 'interpolation':'linear',
1002 'direction':'forward'
1005 if gamut == 'Rec. 709 Daylight':
1006 cs.toReferenceTransforms.append( {
1008 'matrix':[0.561538969, 0.402060105, 0.036400926, 0.0,
1009 0.092739623, 0.924121198, -0.016860821, 0.0,
1010 0.084812961, 0.006373835, 0.908813204, 0.0,
1012 'direction':'forward'
1014 elif gamut == 'Rec. 709 Tungsten':
1015 cs.toReferenceTransforms.append( {
1017 'matrix':[0.566996399, 0.365079418, 0.067924183, 0.0,
1018 0.070901044, 0.880331008, 0.048767948, 0.0,
1019 0.073013542, -0.066540862, 0.99352732, 0.0,
1021 'direction':'forward'
1023 elif gamut == 'DCI-P3 Daylight':
1024 cs.toReferenceTransforms.append( {
1026 'matrix':[0.607160575, 0.299507286, 0.093332140, 0.0,
1027 0.004968120, 1.050982224, -0.055950343, 0.0,
1028 -0.007839939, 0.000809127, 1.007030813, 0.0,
1030 'direction':'forward'
1032 elif gamut == 'DCI-P3 Tungsten':
1033 cs.toReferenceTransforms.append( {
1035 'matrix':[0.650279125, 0.253880169, 0.095840706, 0.0,
1036 -0.026137986, 1.017900530, 0.008237456, 0.0,
1037 0.007757558, -0.063081669, 1.055324110, 0.0,
1039 'direction':'forward'
1041 elif gamut == 'Cinema Gamut Daylight':
1042 cs.toReferenceTransforms.append( {
1044 'matrix':[0.763064455, 0.149021161, 0.087914384, 0.0,
1045 0.003657457, 1.10696038, -0.110617837, 0.0,
1046 -0.009407794,-0.218383305, 1.227791099, 0.0,
1048 'direction':'forward'
1050 elif gamut == 'Cinema Gamut Tungsten':
1051 cs.toReferenceTransforms.append( {
1053 'matrix':[0.817416293, 0.090755698, 0.091828009, 0.0,
1054 -0.035361374, 1.065690585, -0.030329211, 0.0,
1055 0.010390366, -0.299271107, 1.288880741, 0.0,
1057 'direction':'forward'
1060 cs.fromReferenceTransforms = []
1064 CanonLog1 = createCanonLog("Rec. 709 Daylight", "Canon-Log", name="Canon-Log")
1065 configData['colorSpaces'].append(CanonLog1)
1067 CanonLog2 = createCanonLog("Rec. 709 Tungsten", "Canon-Log", name="Canon-Log")
1068 configData['colorSpaces'].append(CanonLog2)
1070 CanonLog3 = createCanonLog("DCI-P3 Daylight", "Canon-Log", name="Canon-Log")
1071 configData['colorSpaces'].append(CanonLog3)
1073 CanonLog4 = createCanonLog("DCI-P3 Tungsten", "Canon-Log", name="Canon-Log")
1074 configData['colorSpaces'].append(CanonLog4)
1076 CanonLog5 = createCanonLog("Cinema Gamut Daylight", "Canon-Log", name="Canon-Log")
1077 configData['colorSpaces'].append(CanonLog5)
1079 CanonLog6 = createCanonLog("Cinema Gamut Tungsten", "Canon-Log", name="Canon-Log")
1080 configData['colorSpaces'].append(CanonLog6)
1082 # Linearization only
1083 CanonLog7 = createCanonLog('', "Canon-Log", name="Canon-Log")
1084 configData['colorSpaces'].append(CanonLog7)
1087 CanonLog8 = createCanonLog("Rec. 709 Daylight", "", name="Canon-Log")
1088 configData['colorSpaces'].append(CanonLog8)
1090 CanonLog9 = createCanonLog("Rec. 709 Tungsten", "", name="Canon-Log")
1091 configData['colorSpaces'].append(CanonLog9)
1093 CanonLog10 = createCanonLog("DCI-P3 Daylight", "", name="Canon-Log")
1094 configData['colorSpaces'].append(CanonLog10)
1096 CanonLog11 = createCanonLog("DCI-P3 Tungsten", "", name="Canon-Log")
1097 configData['colorSpaces'].append(CanonLog11)
1099 CanonLog12 = createCanonLog("Cinema Gamut Daylight", "", name="Canon-Log")
1100 configData['colorSpaces'].append(CanonLog12)
1102 CanonLog13 = createCanonLog("Cinema Gamut Tungsten", "", name="Canon-Log")
1103 configData['colorSpaces'].append(CanonLog13)
1108 def createSlog(gamut, transferFunction, name='S-Log3'):
1109 name = "%s - %s" % (transferFunction, gamut)
1110 if transferFunction == "":
1111 name = "Linear - %s" % gamut
1113 name = "%s" % transferFunction
1115 cs = ColorSpace(name)
1116 cs.description = name
1117 cs.equalityGroup = ''
1121 def sLog1ToLinear(SLog):
1127 lin = ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) * 0.9
1129 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 5.) * 0.9
1132 def sLog2ToLinear(SLog):
1138 lin = ( 219. * ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) / 155.) * 0.9
1140 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 3.53881278538813) * 0.9
1143 def sLog3ToLinear(codeValue):
1144 if codeValue >= (171.2102946929):
1145 linear = pow(10.0, ((codeValue - 420.0) / 261.5)) * (0.18 + 0.01) - 0.01
1147 linear = (codeValue - 95.0)*0.01125000/(171.2102946929 - 95.0)
1148 #print( codeValue, linear )
1151 cs.toReferenceTransforms = []
1153 if transferFunction == "S-Log1":
1154 data = array.array('f', "\0" * lutResolution1d * 4)
1155 for c in range(lutResolution1d):
1156 data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1))
1158 lut = "%s_to_linear.spi1d" % transferFunction
1159 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1161 #print( "Writing %s" % lut)
1163 cs.toReferenceTransforms.append( {
1166 'interpolation':'linear',
1167 'direction':'forward'
1169 elif transferFunction == "S-Log2":
1170 data = array.array('f', "\0" * lutResolution1d * 4)
1171 for c in range(lutResolution1d):
1172 data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1))
1174 lut = "%s_to_linear.spi1d" % transferFunction
1175 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1177 #print( "Writing %s" % lut)
1179 cs.toReferenceTransforms.append( {
1182 'interpolation':'linear',
1183 'direction':'forward'
1185 elif transferFunction == "S-Log3":
1186 data = array.array('f', "\0" * lutResolution1d * 4)
1187 for c in range(lutResolution1d):
1188 data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1))
1190 lut = "%s_to_linear.spi1d" % transferFunction
1191 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1193 #print( "Writing %s" % lut)
1195 cs.toReferenceTransforms.append( {
1198 'interpolation':'linear',
1199 'direction':'forward'
1202 if gamut == 'S-Gamut':
1203 cs.toReferenceTransforms.append( {
1205 'matrix':mat44FromMat33([0.754338638, 0.133697046, 0.111968437,
1206 0.021198141, 1.005410934, -0.026610548,
1207 -0.009756991, 0.004508563, 1.005253201]),
1208 'direction':'forward'
1210 elif gamut == 'S-Gamut Daylight':
1211 cs.toReferenceTransforms.append( {
1213 'matrix':mat44FromMat33([0.8764457030, 0.0145411681, 0.1090131290,
1214 0.0774075345, 0.9529571767, -0.0303647111,
1215 0.0573564351, -0.1151066335, 1.0577501984]),
1216 'direction':'forward'
1218 elif gamut == 'S-Gamut Tungsten':
1219 cs.toReferenceTransforms.append( {
1221 'matrix':mat44FromMat33([1.0110238740, -0.1362526051, 0.1252287310,
1222 0.1011994504, 0.9562196265, -0.0574190769,
1223 0.0600766530, -0.1010185315, 1.0409418785]),
1224 'direction':'forward'
1226 elif gamut == 'S-Gamut3.Cine':
1227 cs.toReferenceTransforms.append( {
1229 'matrix':mat44FromMat33([0.6387886672, 0.2723514337, 0.0888598992,
1230 -0.0039159061, 1.0880732308, -0.0841573249,
1231 -0.0299072021, -0.0264325799, 1.0563397820]),
1232 'direction':'forward'
1234 elif gamut == 'S-Gamut3':
1235 cs.toReferenceTransforms.append( {
1237 'matrix':mat44FromMat33([0.7529825954, 0.1433702162, 0.1036471884,
1238 0.0217076974, 1.0153188355, -0.0370265329,
1239 -0.0094160528, 0.0033704179, 1.0060456349]),
1240 'direction':'forward'
1243 cs.fromReferenceTransforms = []
1247 SLog1SGamut = createSlog("S-Gamut", "S-Log1", name="S-Log")
1248 configData['colorSpaces'].append(SLog1SGamut)
1251 SLog2SGamut = createSlog("S-Gamut", "S-Log2", name="S-Log2")
1252 configData['colorSpaces'].append(SLog2SGamut)
1254 SLog2SGamutDaylight = createSlog("S-Gamut Daylight", "S-Log2", name="S-Log2")
1255 configData['colorSpaces'].append(SLog2SGamutDaylight)
1257 SLog2SGamutTungsten = createSlog("S-Gamut Tungsten", "S-Log2", name="S-Log2")
1258 configData['colorSpaces'].append(SLog2SGamutTungsten)
1261 SLog3SGamut3Cine = createSlog("S-Gamut3.Cine", "S-Log3", name="S-Log3")
1262 configData['colorSpaces'].append(SLog3SGamut3Cine)
1264 SLog3SGamut3 = createSlog("S-Gamut3", "S-Log3", name="S-Log3")
1265 configData['colorSpaces'].append(SLog3SGamut3)
1267 # Linearization only
1268 SLog1 = createSlog("", "S-Log1", name="S-Log")
1269 configData['colorSpaces'].append(SLog1)
1271 SLog2 = createSlog("", "S-Log2", name="S-Log2")
1272 configData['colorSpaces'].append(SLog2)
1274 SLog3 = createSlog("", "S-Log3", name="S-Log3")
1275 configData['colorSpaces'].append(SLog3)
1278 SGamut = createSlog("S-Gamut", "", name="S-Log")
1279 configData['colorSpaces'].append(SGamut)
1281 SGamutDaylight = createSlog("S-Gamut Daylight", "", name="S-Log2")
1282 configData['colorSpaces'].append(SGamutDaylight)
1284 SGamutTungsten = createSlog("S-Gamut Tungsten", "", name="S-Log2")
1285 configData['colorSpaces'].append(SGamutTungsten)
1287 SGamut3Cine = createSlog("S-Gamut3.Cine", "", name="S-Log3")
1288 configData['colorSpaces'].append(SGamut3Cine)
1290 SGamut3 = createSlog("S-Gamut3", "", name="S-Log3")
1291 configData['colorSpaces'].append(SGamut3)
1296 def createLogC(gamut, transferFunction, exposureIndex, name='LogC'):
1297 name = "%s (EI%s) - %s" % (transferFunction, exposureIndex, gamut)
1298 if transferFunction == "":
1299 name = "Linear - %s" % gamut
1301 name = "%s (EI%s)" % (transferFunction, exposureIndex)
1303 cs = ColorSpace(name)
1304 cs.description = name
1305 cs.equalityGroup = ''
1310 IDT_maker_version = "0.08"
1313 blackSignal = 0.003907
1314 midGraySignal = 0.01
1315 encodingGain = 0.256598
1316 encodingOffset = 0.391007
1319 return (math.log(EI/nominalEI)/math.log(2) * (0.89 - 1) / 3 + 1) * encodingGain
1321 def LogCInverseParametersForEI(EI) :
1323 slope = 1.0 / (cut * math.log(10))
1324 offset = math.log10(cut) - slope * cut
1325 gain = EI / nominalEI
1326 gray = midGraySignal / gain
1327 # The higher the EI, the lower the gamma
1328 encGain = gainForEI(EI)
1329 encOffset = encodingOffset
1330 for i in range(0,3) :
1331 nz = ((95.0 / 1023.0 - encOffset) / encGain - offset) / slope
1332 encOffset = encodingOffset - math.log10(1 + nz) * encGain
1333 # Calculate some intermediate values
1335 b = nz - blackSignal / gray
1336 e = slope * a * encGain
1337 f = encGain * (slope * b + offset) + encOffset
1338 # Manipulations so we can return relative exposure
1347 'cut' : (cut - b) / a,
1353 def logCtoLinear(codeValue, exposureIndex):
1354 p = LogCInverseParametersForEI(exposureIndex)
1355 breakpoint = p['e'] * p['cut'] + p['f']
1356 if (codeValue > breakpoint):
1357 linear = (pow(10,(codeValue/1023.0 - p['d']) / p['c']) - p['b']) / p['a']
1359 linear = (codeValue/1023.0 - p['f']) / p['e']
1361 #print( codeValue, linear )
1365 cs.toReferenceTransforms = []
1367 if transferFunction == "V3 LogC":
1368 data = array.array('f', "\0" * lutResolution1d * 4)
1369 for c in range(lutResolution1d):
1370 data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex))
1372 lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex))
1373 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1375 #print( "Writing %s" % lut)
1376 cs.toReferenceTransforms.append( {
1379 'interpolation':'linear',
1380 'direction':'forward'
1383 if gamut == 'Wide Gamut':
1384 cs.toReferenceTransforms.append( {
1386 'matrix':mat44FromMat33([0.680206, 0.236137, 0.083658,
1387 0.085415, 1.017471, -0.102886,
1388 0.002057, -0.062563, 1.060506]),
1389 'direction':'forward'
1392 cs.fromReferenceTransforms = []
1395 transferFunction = "V3 LogC"
1396 gamut = "Wide Gamut"
1397 #EIs = [160.0, 200.0, 250.0, 320.0, 400.0, 500.0, 640.0, 800.0, 1000.0, 1280.0, 1600.0, 2000.0, 2560.0, 3200.0]
1398 EIs = [160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600, 2000, 2560, 3200]
1403 LogCEIfull = createLogC(gamut, transferFunction, EI, name="LogC")
1404 configData['colorSpaces'].append(LogCEIfull)
1406 # Linearization only
1408 LogCEIlinearization = createLogC("", transferFunction, EI, name="LogC")
1409 configData['colorSpaces'].append(LogCEIlinearization)
1412 LogCEIprimaries = createLogC(gamut, "", defaultEI, name="LogC")
1413 configData['colorSpaces'].append(LogCEIprimaries)
1416 # Generic log transform
1418 def createGenericLog(name='log',
1425 lutResolution1d=lutResolution1d):
1426 cs = ColorSpace(name)
1427 cs.description = "The %s color space" % name
1428 cs.equalityGroup = name
1429 cs.family = 'Utility'
1433 #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
1434 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
1436 lut = "%s_to_aces.spi1d" % name
1438 generate1dLUTFromCTL( lutDir + "/" + lut,
1445 'middleGrey' : middleGrey,
1446 'minExposure' : minExposure,
1447 'maxExposure' : maxExposure
1454 cs.toReferenceTransforms = []
1455 cs.toReferenceTransforms.append( {
1458 'interpolation':'linear',
1459 'direction':'forward'
1462 cs.fromReferenceTransforms = []
1468 def createACESLMT(lmtName,
1471 lutResolution1d=1024,
1474 cs = ColorSpace("%s" % lmtName)
1475 cs.description = "The ACES Look Transform: %s" % lmtName
1476 cs.equalityGroup = ''
1481 pprint.pprint( lmtValues )
1484 # Generate the shaper transform
1486 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1488 shaperLut = "%s_to_aces.spi1d" % shaperName
1489 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1491 shaperToACESCTL % acesCTLReleaseDir
1493 generate1dLUTFromCTL( lutDir + "/" + shaperLut,
1497 1.0/shaperInputScale,
1503 shaperOCIOTransform = {
1506 'interpolation':'linear',
1507 'direction':'inverse'
1511 # Generate the forward transform
1513 cs.fromReferenceTransforms = []
1515 if 'transformCTL' in lmtValues:
1517 shaperToACESCTL % acesCTLReleaseDir,
1518 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
1520 lut = "%s.%s.spi3d" % (shaperName, lmtName)
1522 generate3dLUTFromCTL( lutDir + "/" + lut,
1526 1.0/shaperInputScale,
1532 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1533 cs.fromReferenceTransforms.append( {
1536 'interpolation':'tetrahedral',
1537 'direction':'forward'
1541 # Generate the inverse transform
1543 cs.toReferenceTransforms = []
1545 if 'transformCTLInverse' in lmtValues:
1547 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1548 shaperFromACESCTL % acesCTLReleaseDir
1550 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
1552 generate3dLUTFromCTL( lutDir + "/" + lut,
1562 cs.toReferenceTransforms.append( {
1565 'interpolation':'tetrahedral',
1566 'direction':'forward'
1569 shaperInverse = shaperOCIOTransform.copy()
1570 shaperInverse['direction'] = 'forward'
1571 cs.toReferenceTransforms.append( shaperInverse )
1579 lmtLutResolution1d = max(4096, lutResolution1d)
1580 lmtLutResolution3d = max(65, lutResolution3d)
1583 lmtShaperName = 'LMT Shaper'
1585 'middleGrey' : 0.18,
1586 'minExposure' : -10.0,
1589 lmtShaper = createGenericLog(name=lmtShaperName,
1590 middleGrey=lmtParams['middleGrey'],
1591 minExposure=lmtParams['minExposure'],
1592 maxExposure=lmtParams['maxExposure'],
1593 lutResolution1d=lmtLutResolution1d)
1594 configData['colorSpaces'].append(lmtShaper)
1596 shaperInputScale_genericLog2 = 1.0
1598 # Log 2 shaper name and CTL transforms bundled up
1601 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1602 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1603 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1604 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1605 shaperInputScale_genericLog2,
1609 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
1611 for lmt in sortedLMTs:
1612 (lmtName, lmtValues) = lmt
1614 lmtValues['transformUserName'],
1620 configData['colorSpaces'].append(cs)
1623 # ACES RRT with the supplied ODT
1625 def createACESRRTplusODT(odtName,
1628 lutResolution1d=1024,
1631 cs = ColorSpace("%s" % odtName)
1632 cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
1633 cs.equalityGroup = ''
1634 cs.family = 'Output'
1638 pprint.pprint( odtValues )
1641 # Generate the shaper transform
1643 #if 'shaperCTL' in odtValues:
1644 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1646 if 'legalRange' in odtValues:
1647 shaperParams['legalRange'] = odtValues['legalRange']
1649 shaperParams['legalRange'] = 0
1651 shaperLut = "%s_to_aces.spi1d" % shaperName
1652 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1654 shaperToACESCTL % acesCTLReleaseDir
1656 generate1dLUTFromCTL( lutDir + "/" + shaperLut,
1660 1.0/shaperInputScale,
1666 shaperOCIOTransform = {
1669 'interpolation':'linear',
1670 'direction':'inverse'
1674 # Generate the forward transform
1676 cs.fromReferenceTransforms = []
1678 if 'transformLUT' in odtValues:
1679 # Copy into the lut dir
1680 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1681 lut = lutDir + "/" + transformLUTFileName
1682 shutil.copy(odtValues['transformLUT'], lut)
1684 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1685 cs.fromReferenceTransforms.append( {
1687 'path': transformLUTFileName,
1688 'interpolation':'tetrahedral',
1689 'direction':'forward'
1691 elif 'transformCTL' in odtValues:
1695 shaperToACESCTL % acesCTLReleaseDir,
1696 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1697 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1699 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1701 generate3dLUTFromCTL( lutDir + "/" + lut,
1706 1.0/shaperInputScale,
1712 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1713 cs.fromReferenceTransforms.append( {
1716 'interpolation':'tetrahedral',
1717 'direction':'forward'
1721 # Generate the inverse transform
1723 cs.toReferenceTransforms = []
1725 if 'transformLUTInverse' in odtValues:
1726 # Copy into the lut dir
1727 transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
1728 lut = lutDir + "/" + transformLUTInverseFileName
1729 shutil.copy(odtValues['transformLUTInverse'], lut)
1731 cs.toReferenceTransforms.append( {
1733 'path': transformLUTInverseFileName,
1734 'interpolation':'tetrahedral',
1735 'direction':'forward'
1738 shaperInverse = shaperOCIOTransform.copy()
1739 shaperInverse['direction'] = 'forward'
1740 cs.toReferenceTransforms.append( shaperInverse )
1741 elif 'transformCTLInverse' in odtValues:
1743 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1744 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1745 shaperFromACESCTL % acesCTLReleaseDir
1747 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1749 generate3dLUTFromCTL( lutDir + "/" + lut,
1760 cs.toReferenceTransforms.append( {
1763 'interpolation':'tetrahedral',
1764 'direction':'forward'
1767 shaperInverse = shaperOCIOTransform.copy()
1768 shaperInverse['direction'] = 'forward'
1769 cs.toReferenceTransforms.append( shaperInverse )
1774 # RRT/ODT shaper options
1779 log2ShaperName = shaperName
1781 'middleGrey' : 0.18,
1782 'minExposure' : -6.0,
1785 log2Shaper = createGenericLog(name=log2ShaperName,
1786 middleGrey=log2Params['middleGrey'],
1787 minExposure=log2Params['minExposure'],
1788 maxExposure=log2Params['maxExposure'])
1789 configData['colorSpaces'].append(log2Shaper)
1791 shaperInputScale_genericLog2 = 1.0
1793 # Log 2 shaper name and CTL transforms bundled up
1796 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1797 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1798 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1799 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1800 shaperInputScale_genericLog2,
1804 shaperData[log2ShaperName] = log2ShaperData
1807 # Shaper that also includes the AP1 primaries
1808 # - Needed for some LUT baking steps
1810 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1811 middleGrey=log2Params['middleGrey'],
1812 minExposure=log2Params['minExposure'],
1813 maxExposure=log2Params['maxExposure'])
1814 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1815 # AP1 primaries to AP0 primaries
1816 log2ShaperAP1.toReferenceTransforms.append( {
1818 'matrix':mat44FromMat33(acesAP1toAP0),
1819 'direction':'forward'
1821 configData['colorSpaces'].append(log2ShaperAP1)
1824 # Choose your shaper
1827 # Shaper name. Should really be automated or made a user choice
1829 # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
1830 #shaperName = 'log2Shaper'
1832 #if shaperName in shaperData:
1833 # rrtShaperName = shaperName
1834 # rrtShaper = shaperData[shaperName]
1837 rrtShaperName = log2ShaperName
1838 rrtShaper = log2ShaperData
1841 # RRT + ODT Combinations
1843 #for odtName, odtValues in odtInfo.iteritems():
1844 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1846 for odt in sortedOdts:
1847 (odtName, odtValues) = odt
1849 # Have to handle ODTs that can generate either legal or full output
1850 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1851 'Academy.Rec709_100nits_dim.a1.0.0',
1852 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1853 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1855 odtNameLegal = odtValues['transformUserName']
1857 odtLegal = odtValues.copy()
1858 odtLegal['legalRange'] = 1
1860 cs = createACESRRTplusODT(
1867 configData['colorSpaces'].append(cs)
1869 # Create a display entry using this color space
1870 configData['displays'][odtNameLegal] = {
1873 'Output Transform':cs }
1875 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1876 'Academy.Rec709_100nits_dim.a1.0.0',
1877 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1879 print( "Generating full range ODT for %s" % odtName)
1881 odtNameFull = "%s - Full" % odtValues['transformUserName']
1882 odtFull = odtValues.copy()
1883 odtFull['legalRange'] = 0
1885 csFull = createACESRRTplusODT(
1892 configData['colorSpaces'].append(csFull)
1894 # Create a display entry using this color space
1895 configData['displays'][odtNameFull] = {
1898 'Output Transform':csFull }
1901 # Generic Matrix transform
1903 def createGenericMatrix(name='matrix',
1904 fromReferenceValues=[],
1905 toReferenceValues=[]):
1906 cs = ColorSpace(name)
1907 cs.description = "The %s color space" % name
1908 cs.equalityGroup = name
1909 cs.family = 'Utility'
1912 cs.toReferenceTransforms = []
1913 if toReferenceValues != []:
1914 cs.toReferenceTransforms.append( {
1916 'matrix':mat44FromMat33(toReferenceValues),
1917 'direction':'forward'
1920 cs.fromReferenceTransforms = []
1921 if fromReferenceValues != []:
1922 cs.fromReferenceTransforms.append( {
1924 'matrix':mat44FromMat33(fromReferenceValues),
1925 'direction':'forward'
1930 cs = createGenericMatrix('XYZ', fromReferenceValues=acesAP0toXYZ)
1931 configData['colorSpaces'].append(cs)
1933 cs = createGenericMatrix('Linear - AP1', toReferenceValues=acesAP1toAP0)
1934 configData['colorSpaces'].append(cs)
1936 print( "generateLUTs - end" )
1939 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1941 # Add the legal and full variations into this list
1942 odtInfoC = dict(odtInfo)
1943 for odtCTLName, odtValues in odtInfo.iteritems():
1944 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1945 'Academy.Rec709_100nits_dim.a1.0.0',
1946 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1947 odtName = odtValues["transformUserName"]
1949 odtValuesLegal = dict(odtValues)
1950 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1951 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1953 odtValuesFull = dict(odtValues)
1954 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1955 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1957 del( odtInfoC[odtCTLName] )
1959 for odtCTLName, odtValues in odtInfoC.iteritems():
1960 odtPrefix = odtValues["transformUserNamePrefix"]
1961 odtName = odtValues["transformUserName"]
1964 for inputspace in ["ACEScc", "ACESproxy"]:
1965 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1966 args += ["--outputspace", "%s" % odtName ]
1967 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1968 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1969 args += ["--cubesize", str(lutResolution3d) ]
1970 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1972 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1976 for inputspace in ["ACEScc", "ACESproxy"]:
1977 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1978 args += ["--outputspace", "%s" % odtName ]
1979 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1980 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1981 args += ["--cubesize", str(lutResolution3d) ]
1983 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1984 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1987 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1988 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1992 for inputspace in ["ACEScg", "ACES2065-1"]:
1993 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1994 args += ["--outputspace", "%s" % odtName ]
1995 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1996 if inputspace == 'ACEScg':
1997 linShaperName = "%s - AP1" % shaperName
1999 linShaperName = shaperName
2000 args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ]
2002 args += ["--cubesize", str(lutResolution3d) ]
2004 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
2005 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
2008 hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
2009 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
2013 def createConfigDir(configDir, bakeSecondaryLUTs):
2014 dirs = [configDir, "%s/luts" % configDir]
2015 if bakeSecondaryLUTs:
2016 dirs.extend(["%s/baked" % configDir,
2017 "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
2018 "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
2019 "%s/baked/maya" % configDir])
2022 if not os.path.exists(d):
2025 def getTransformInfo(ctlTransform):
2026 fp = open(ctlTransform, 'rb')
2029 lines = fp.readlines()
2031 # Grab transform ID and User Name
2032 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
2033 #print( transformID )
2034 transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
2035 transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
2036 #print( transformUserName )
2039 return (transformID, transformUserName, transformUserNamePrefix)
2041 # For versions after WGR9
2042 def getODTInfo(acesCTLReleaseDir):
2043 # Credit to Alex Fry for the original approach here
2044 odtDir = os.path.join(acesCTLReleaseDir, "odt")
2046 for dirName, subdirList, fileList in os.walk(odtDir):
2047 for fname in fileList:
2048 allodt.append((os.path.join(dirName,fname)))
2050 odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
2056 for odtCTL in odtCTLs:
2057 odtTokens = os.path.split(odtCTL)
2060 # Handle nested directories
2061 odtPathTokens = os.path.split(odtTokens[-2])
2062 odtDir = odtPathTokens[-1]
2063 while odtPathTokens[-2][-3:] != 'odt':
2064 odtPathTokens = os.path.split(odtPathTokens[-2])
2065 odtDir = os.path.join(odtPathTokens[-1], odtDir)
2068 #print( "odtDir : %s" % odtDir )
2069 transformCTL = odtTokens[-1]
2070 #print( transformCTL )
2071 odtName = string.join(transformCTL.split('.')[1:-1], '.')
2074 # Find id, user name and user name prefix
2075 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2076 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
2079 transformCTLInverse = "InvODT.%s.ctl" % odtName
2080 if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
2081 transformCTLInverse = None
2082 #print( transformCTLInverse )
2084 # Add to list of ODTs
2086 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
2087 if transformCTLInverse != None:
2088 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
2090 odts[odtName]['transformID'] = transformID
2091 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
2092 odts[odtName]['transformUserName'] = transformUserName
2094 print( "ODT : %s" % odtName )
2095 print( "\tTransform ID : %s" % transformID )
2096 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2097 print( "\tTransform User Name : %s" % transformUserName )
2098 print( "\tForward ctl : %s" % odts[odtName]['transformCTL'])
2099 if 'transformCTLInverse' in odts[odtName]:
2100 print( "\tInverse ctl : %s" % odts[odtName]['transformCTLInverse'])
2102 print( "\tInverse ctl : %s" % "None" )
2108 # For versions after WGR9
2109 def getLMTInfo(acesCTLReleaseDir):
2110 # Credit to Alex Fry for the original approach here
2111 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
2113 for dirName, subdirList, fileList in os.walk(lmtDir):
2114 for fname in fileList:
2115 alllmt.append((os.path.join(dirName,fname)))
2117 lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
2123 for lmtCTL in lmtCTLs:
2124 lmtTokens = os.path.split(lmtCTL)
2127 # Handle nested directories
2128 lmtPathTokens = os.path.split(lmtTokens[-2])
2129 lmtDir = lmtPathTokens[-1]
2130 while lmtPathTokens[-2][-3:] != 'ctl':
2131 lmtPathTokens = os.path.split(lmtPathTokens[-2])
2132 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
2135 #print( "lmtDir : %s" % lmtDir )
2136 transformCTL = lmtTokens[-1]
2137 #print( transformCTL )
2138 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
2141 # Find id, user name and user name prefix
2142 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2143 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
2146 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
2147 if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
2148 transformCTLInverse = None
2149 #print( transformCTLInverse )
2151 # Add to list of LMTs
2153 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
2154 if transformCTLInverse != None:
2155 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
2157 lmts[lmtName]['transformID'] = transformID
2158 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
2159 lmts[lmtName]['transformUserName'] = transformUserName
2161 print( "LMT : %s" % lmtName )
2162 print( "\tTransform ID : %s" % transformID )
2163 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2164 print( "\tTransform User Name : %s" % transformUserName )
2165 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
2166 if 'transformCTLInverse' in lmts[lmtName]:
2167 print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
2169 print( "\t Inverse ctl : %s" % "None" )
2176 # Create the ACES config
2178 def createACESConfig(acesCTLReleaseDir,
2180 lutResolution1d=4096,
2182 bakeSecondaryLUTs=True,
2185 # Get ODT names and CTL paths
2186 odtInfo = getODTInfo(acesCTLReleaseDir)
2188 # Get ODT names and CTL paths
2189 lmtInfo = getLMTInfo(acesCTLReleaseDir)
2192 createConfigDir(configDir, bakeSecondaryLUTs)
2194 # Generate config data and LUTs for different transforms
2195 lutDir = "%s/luts" % configDir
2196 shaperName = 'Output Shaper'
2197 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
2199 # Create the config using the generated LUTs
2200 print( "Creating generic config")
2201 config = createConfig(configData)
2204 # Write the config to disk
2205 writeConfig(config, "%s/config.ocio" % configDir )
2207 # Create a config that will work well with Nuke using the previously generated LUTs
2208 print( "Creating Nuke-specific config")
2209 nuke_config = createConfig(configData, nuke=True)
2212 # Write the config to disk
2213 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
2215 # Bake secondary LUTs using the config
2216 if bakeSecondaryLUTs:
2217 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
2225 p = optparse.OptionParser(description='An OCIO config generation script',
2226 prog='createACESConfig',
2227 version='createACESConfig 0.1',
2228 usage='%prog [options]')
2229 p.add_option('--acesCTLDir', '-a', default=None)
2230 p.add_option('--configDir', '-c', default=None)
2231 p.add_option('--lutResolution1d', default=4096)
2232 p.add_option('--lutResolution3d', default=64)
2233 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
2234 p.add_option('--keepTempImages', action="store_true")
2236 options, arguments = p.parse_args()
2241 acesCTLDir = options.acesCTLDir
2242 configDir = options.configDir
2243 lutResolution1d = int(options.lutResolution1d)
2244 lutResolution3d = int(options.lutResolution3d)
2245 bakeSecondaryLUTs = not(options.dontBakeSecondaryLUTs)
2246 cleanupTempImages = not(options.keepTempImages)
2249 argsStart = sys.argv.index('--') + 1
2250 args = sys.argv[argsStart:]
2252 argsStart = len(sys.argv)+1
2255 print( "command line : \n%s\n" % " ".join(sys.argv) )
2257 if configDir == None:
2258 print( "process: No ACES CTL directory specified" )
2262 # Generate the configuration
2264 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
2267 if __name__ == '__main__':