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]
597 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
598 cs = ColorSpace(name)
599 cs.description = "The %s color space" % name
600 cs.equalityGroup = ''
605 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
606 # This transform gets back to the AP1 primaries
607 # Useful to the 1d LUT is only covering the transfer function
608 # The primaries switch is covered by the matrix below
609 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
611 lut = "%s_to_ACES.spi1d" % name
612 generate1dLUTFromCTL( lutDir + "/" + lut,
624 cs.toReferenceTransforms = []
625 cs.toReferenceTransforms.append( {
628 'interpolation':'linear',
629 'direction':'forward'
632 # AP1 primaries to AP0 primaries
633 cs.toReferenceTransforms.append( {
635 'matrix':mat44FromMat33(acesAP1toAP0),
636 'direction':'forward'
639 cs.fromReferenceTransforms = []
642 ACEScc = createACEScc()
643 configData['colorSpaces'].append(ACEScc)
648 def createACESProxy(name='ACESproxy'):
649 cs = ColorSpace(name)
650 cs.description = "The %s color space" % name
651 cs.equalityGroup = ''
656 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
657 # This transform gets back to the AP1 primaries
658 # Useful to the 1d LUT is only covering the transfer function
659 # The primaries switch is covered by the matrix below
660 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
662 lut = "%s_to_aces.spi1d" % name
663 generate1dLUTFromCTL( lutDir + "/" + lut,
673 cs.toReferenceTransforms = []
674 cs.toReferenceTransforms.append( {
677 'interpolation':'linear',
678 'direction':'forward'
681 # AP1 primaries to AP0 primaries
682 cs.toReferenceTransforms.append( {
684 'matrix':mat44FromMat33(acesAP1toAP0),
685 'direction':'forward'
689 cs.fromReferenceTransforms = []
692 ACESproxy = createACESProxy()
693 configData['colorSpaces'].append(ACESproxy)
698 def createACEScg(name='ACEScg'):
699 cs = ColorSpace(name)
700 cs.description = "The %s color space" % name
701 cs.equalityGroup = ''
705 cs.toReferenceTransforms = []
707 # AP1 primaries to AP0 primaries
708 cs.toReferenceTransforms.append( {
710 'matrix':mat44FromMat33(acesAP1toAP0),
711 'direction':'forward'
714 cs.fromReferenceTransforms = []
717 ACEScg = createACEScg()
718 configData['colorSpaces'].append(ACEScg)
723 def createADX(bitdepth=10, name='ADX'):
724 name = "%s%s" % (name, bitdepth)
725 cs = ColorSpace(name)
726 cs.description = "%s color space - used for film scans" % name
727 cs.equalityGroup = ''
732 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
733 adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
734 0.0, 1023.0/500.0, 0.0, 0.0,
735 0.0, 0.0, 1023.0/500.0, 0.0,
737 offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
739 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
740 adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
741 0.0, 65535.0/8000.0, 0.0, 0.0,
742 0.0, 0.0, 65535.0/8000.0, 0.0,
744 offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
746 cs.toReferenceTransforms = []
748 # Convert from ADX to Channel-Dependent Density
749 cs.toReferenceTransforms.append( {
753 'direction':'forward'
756 # Convert from Channel-Dependent Density to Channel-Independent Density
757 cs.toReferenceTransforms.append( {
759 'matrix':[0.75573, 0.22197, 0.02230, 0,
760 0.05901, 0.96928, -0.02829, 0,
761 0.16134, 0.07406, 0.76460, 0,
763 'direction':'forward'
766 # Copied from Alex Fry's adx_cid_to_rle.py
767 def createCIDtoRLELUT():
768 def interpolate1D(x, xp, fp):
769 return numpy.interp(x, xp, fp)
771 LUT_1D_xp = [-0.190000000000000,
783 LUT_1D_fp = [-6.000000000000000,
795 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
799 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
800 return (100.0 / 55.0) * x - REF_PT
802 def Fit(value, fromMin, fromMax, toMin, toMax):
803 if fromMin == fromMax:
804 raise ValueError("fromMin == fromMax")
805 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
810 for i in xrange(NUM_SAMPLES):
811 x = i/(NUM_SAMPLES-1.0)
812 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
813 data.append(cid_to_rle(x))
815 lut = 'ADX_CID_to_RLE.spi1d'
816 WriteSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
820 # Convert Channel Independent Density values to Relative Log Exposure values
821 lut = createCIDtoRLELUT()
822 cs.toReferenceTransforms.append( {
825 'interpolation':'linear',
826 'direction':'forward'
829 # Convert Relative Log Exposure values to Relative Exposure values
830 cs.toReferenceTransforms.append( {
833 'direction':'inverse'
836 # Convert Relative Exposure values to ACES values
837 cs.toReferenceTransforms.append( {
839 'matrix':[0.72286, 0.12630, 0.15084, 0,
840 0.11923, 0.76418, 0.11659, 0,
841 0.01427, 0.08213, 0.90359, 0,
843 'direction':'forward'
846 cs.fromReferenceTransforms = []
849 ADX10 = createADX(bitdepth=10)
850 configData['colorSpaces'].append(ADX10)
852 ADX16 = createADX(bitdepth=16)
853 configData['colorSpaces'].append(ADX16)
859 def createREDlogFilm(gamut, transferFunction, name='REDlogFilm'):
860 name = "%s - %s" % (transferFunction, gamut)
861 if transferFunction == "":
862 name = "Linear - %s" % gamut
864 name = "%s" % transferFunction
866 cs = ColorSpace(name)
867 cs.description = name
868 cs.equalityGroup = ''
872 def cineonToLinear(codeValue):
876 codeValueToDensity = 0.002
878 blackLinear = pow(10.0, (blackPoint - whitePoint) * (codeValueToDensity / nGamma))
879 codeLinear = pow(10.0, (codeValue - whitePoint) * (codeValueToDensity / nGamma))
881 return (codeLinear - blackLinear)/(1.0 - blackLinear)
883 cs.toReferenceTransforms = []
885 if transferFunction == 'REDlogFilm':
886 data = array.array('f', "\0" * lutResolution1d * 4)
887 for c in range(lutResolution1d):
888 data[c] = cineonToLinear(1023.0*c/(lutResolution1d-1))
890 lut = "CineonLog_to_linear.spi1d"
891 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
893 cs.toReferenceTransforms.append( {
896 'interpolation':'linear',
897 'direction':'forward'
900 if gamut == 'DRAGONcolor':
901 cs.toReferenceTransforms.append( {
903 'matrix':mat44FromMat33([0.532279, 0.376648, 0.091073,
904 0.046344, 0.974513, -0.020860,
905 -0.053976, -0.000320, 1.054267]),
906 'direction':'forward'
908 elif gamut == 'REDcolor3':
909 cs.toReferenceTransforms.append( {
911 'matrix':mat44FromMat33([0.512136, 0.360370, 0.127494,
912 0.070377, 0.903884, 0.025737,
913 -0.020824, 0.017671, 1.003123]),
914 'direction':'forward'
916 elif gamut == 'REDcolor2':
917 cs.toReferenceTransforms.append( {
919 'matrix':mat44FromMat33([0.480997, 0.402289, 0.116714,
920 -0.004938, 1.000154, 0.004781,
921 -0.105257, 0.025320, 1.079907]),
922 'direction':'forward'
925 cs.fromReferenceTransforms = []
929 REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "REDlogFilm", name="REDlogFilm")
930 configData['colorSpaces'].append(REDlogFilmDRAGON)
932 REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "REDlogFilm", name="REDlogFilm")
933 configData['colorSpaces'].append(REDlogFilmREDcolor2)
935 REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "REDlogFilm", name="REDlogFilm")
936 configData['colorSpaces'].append(REDlogFilmREDcolor3)
939 REDlogFilmDRAGON = createREDlogFilm("", "REDlogFilm", name="REDlogFilm")
940 configData['colorSpaces'].append(REDlogFilmDRAGON)
943 REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "", name="REDlogFilm")
944 configData['colorSpaces'].append(REDlogFilmDRAGON)
946 REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "", name="REDlogFilm")
947 configData['colorSpaces'].append(REDlogFilmREDcolor2)
949 REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "", name="REDlogFilm")
950 configData['colorSpaces'].append(REDlogFilmREDcolor3)
955 def createCanonLog(gamut, transferFunction, name='Canon-Log'):
956 name = "%s - %s" % (transferFunction, gamut)
957 if transferFunction == "":
958 name = "Linear - %s" % gamut
960 name = "%s" % transferFunction
962 cs = ColorSpace(name)
963 cs.description = name
964 cs.equalityGroup = ''
968 def legalToFull(codeValue):
969 return (codeValue - 64.0)/(940.0 - 64.0)
971 def canonLogToLinear(codeValue):
972 # log = fullToLegal(c1 * log10(c2*linear + 1) + c3)
973 # linear = (pow(10, (legalToFul(log) - c3)/c1) - 1)/c2
978 linear = (pow(10.0, (legalToFull(codeValue) - c3)/c1) -1.0)/c2
979 linear = 0.9 * linear
980 #print( codeValue, linear )
983 cs.toReferenceTransforms = []
985 if transferFunction == "Canon-Log":
986 data = array.array('f', "\0" * lutResolution1d * 4)
987 for c in range(lutResolution1d):
988 data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1))
990 lut = "%s_to_linear.spi1d" % transferFunction
991 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
993 cs.toReferenceTransforms.append( {
996 'interpolation':'linear',
997 'direction':'forward'
1000 if gamut == 'Rec. 709 Daylight':
1001 cs.toReferenceTransforms.append( {
1003 'matrix':[0.561538969, 0.402060105, 0.036400926, 0.0,
1004 0.092739623, 0.924121198, -0.016860821, 0.0,
1005 0.084812961, 0.006373835, 0.908813204, 0.0,
1007 'direction':'forward'
1009 elif gamut == 'Rec. 709 Tungsten':
1010 cs.toReferenceTransforms.append( {
1012 'matrix':[0.566996399, 0.365079418, 0.067924183, 0.0,
1013 0.070901044, 0.880331008, 0.048767948, 0.0,
1014 0.073013542, -0.066540862, 0.99352732, 0.0,
1016 'direction':'forward'
1018 elif gamut == 'DCI-P3 Daylight':
1019 cs.toReferenceTransforms.append( {
1021 'matrix':[0.607160575, 0.299507286, 0.093332140, 0.0,
1022 0.004968120, 1.050982224, -0.055950343, 0.0,
1023 -0.007839939, 0.000809127, 1.007030813, 0.0,
1025 'direction':'forward'
1027 elif gamut == 'DCI-P3 Tungsten':
1028 cs.toReferenceTransforms.append( {
1030 'matrix':[0.650279125, 0.253880169, 0.095840706, 0.0,
1031 -0.026137986, 1.017900530, 0.008237456, 0.0,
1032 0.007757558, -0.063081669, 1.055324110, 0.0,
1034 'direction':'forward'
1036 elif gamut == 'Cinema Gamut Daylight':
1037 cs.toReferenceTransforms.append( {
1039 'matrix':[0.763064455, 0.149021161, 0.087914384, 0.0,
1040 0.003657457, 1.10696038, -0.110617837, 0.0,
1041 -0.009407794,-0.218383305, 1.227791099, 0.0,
1043 'direction':'forward'
1045 elif gamut == 'Cinema Gamut Tungsten':
1046 cs.toReferenceTransforms.append( {
1048 'matrix':[0.817416293, 0.090755698, 0.091828009, 0.0,
1049 -0.035361374, 1.065690585, -0.030329211, 0.0,
1050 0.010390366, -0.299271107, 1.288880741, 0.0,
1052 'direction':'forward'
1055 cs.fromReferenceTransforms = []
1059 CanonLog1 = createCanonLog("Rec. 709 Daylight", "Canon-Log", name="Canon-Log")
1060 configData['colorSpaces'].append(CanonLog1)
1062 CanonLog2 = createCanonLog("Rec. 709 Tungsten", "Canon-Log", name="Canon-Log")
1063 configData['colorSpaces'].append(CanonLog2)
1065 CanonLog3 = createCanonLog("DCI-P3 Daylight", "Canon-Log", name="Canon-Log")
1066 configData['colorSpaces'].append(CanonLog3)
1068 CanonLog4 = createCanonLog("DCI-P3 Tungsten", "Canon-Log", name="Canon-Log")
1069 configData['colorSpaces'].append(CanonLog4)
1071 CanonLog5 = createCanonLog("Cinema Gamut Daylight", "Canon-Log", name="Canon-Log")
1072 configData['colorSpaces'].append(CanonLog5)
1074 CanonLog6 = createCanonLog("Cinema Gamut Tungsten", "Canon-Log", name="Canon-Log")
1075 configData['colorSpaces'].append(CanonLog6)
1077 # Linearization only
1078 CanonLog7 = createCanonLog('', "Canon-Log", name="Canon-Log")
1079 configData['colorSpaces'].append(CanonLog7)
1082 CanonLog8 = createCanonLog("Rec. 709 Daylight", "", name="Canon-Log")
1083 configData['colorSpaces'].append(CanonLog8)
1085 CanonLog9 = createCanonLog("Rec. 709 Tungsten", "", name="Canon-Log")
1086 configData['colorSpaces'].append(CanonLog9)
1088 CanonLog10 = createCanonLog("DCI-P3 Daylight", "", name="Canon-Log")
1089 configData['colorSpaces'].append(CanonLog10)
1091 CanonLog11 = createCanonLog("DCI-P3 Tungsten", "", name="Canon-Log")
1092 configData['colorSpaces'].append(CanonLog11)
1094 CanonLog12 = createCanonLog("Cinema Gamut Daylight", "", name="Canon-Log")
1095 configData['colorSpaces'].append(CanonLog12)
1097 CanonLog13 = createCanonLog("Cinema Gamut Tungsten", "", name="Canon-Log")
1098 configData['colorSpaces'].append(CanonLog13)
1103 def createSlog(gamut, transferFunction, name='S-Log3'):
1104 name = "%s - %s" % (transferFunction, gamut)
1105 if transferFunction == "":
1106 name = "Linear - %s" % gamut
1108 name = "%s" % transferFunction
1110 cs = ColorSpace(name)
1111 cs.description = name
1112 cs.equalityGroup = ''
1116 def sLog1ToLinear(SLog):
1122 lin = ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) * 0.9
1124 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 5.) * 0.9
1127 def sLog2ToLinear(SLog):
1133 lin = ( 219. * ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) / 155.) * 0.9
1135 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 3.53881278538813) * 0.9
1138 def sLog3ToLinear(codeValue):
1139 if codeValue >= (171.2102946929):
1140 linear = pow(10.0, ((codeValue - 420.0) / 261.5)) * (0.18 + 0.01) - 0.01
1142 linear = (codeValue - 95.0)*0.01125000/(171.2102946929 - 95.0)
1143 #print( codeValue, linear )
1146 cs.toReferenceTransforms = []
1148 if transferFunction == "S-Log1":
1149 data = array.array('f', "\0" * lutResolution1d * 4)
1150 for c in range(lutResolution1d):
1151 data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1))
1153 lut = "%s_to_linear.spi1d" % transferFunction
1154 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1156 #print( "Writing %s" % lut)
1158 cs.toReferenceTransforms.append( {
1161 'interpolation':'linear',
1162 'direction':'forward'
1164 elif transferFunction == "S-Log2":
1165 data = array.array('f', "\0" * lutResolution1d * 4)
1166 for c in range(lutResolution1d):
1167 data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1))
1169 lut = "%s_to_linear.spi1d" % transferFunction
1170 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1172 #print( "Writing %s" % lut)
1174 cs.toReferenceTransforms.append( {
1177 'interpolation':'linear',
1178 'direction':'forward'
1180 elif transferFunction == "S-Log3":
1181 data = array.array('f', "\0" * lutResolution1d * 4)
1182 for c in range(lutResolution1d):
1183 data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1))
1185 lut = "%s_to_linear.spi1d" % transferFunction
1186 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1188 #print( "Writing %s" % lut)
1190 cs.toReferenceTransforms.append( {
1193 'interpolation':'linear',
1194 'direction':'forward'
1197 if gamut == 'S-Gamut':
1198 cs.toReferenceTransforms.append( {
1200 'matrix':mat44FromMat33([0.754338638, 0.133697046, 0.111968437,
1201 0.021198141, 1.005410934, -0.026610548,
1202 -0.009756991, 0.004508563, 1.005253201]),
1203 'direction':'forward'
1205 elif gamut == 'S-Gamut Daylight':
1206 cs.toReferenceTransforms.append( {
1208 'matrix':mat44FromMat33([0.8764457030, 0.0145411681, 0.1090131290,
1209 0.0774075345, 0.9529571767, -0.0303647111,
1210 0.0573564351, -0.1151066335, 1.0577501984]),
1211 'direction':'forward'
1213 elif gamut == 'S-Gamut Tungsten':
1214 cs.toReferenceTransforms.append( {
1216 'matrix':mat44FromMat33([1.0110238740, -0.1362526051, 0.1252287310,
1217 0.1011994504, 0.9562196265, -0.0574190769,
1218 0.0600766530, -0.1010185315, 1.0409418785]),
1219 'direction':'forward'
1221 elif gamut == 'S-Gamut3.Cine':
1222 cs.toReferenceTransforms.append( {
1224 'matrix':mat44FromMat33([0.6387886672, 0.2723514337, 0.0888598992,
1225 -0.0039159061, 1.0880732308, -0.0841573249,
1226 -0.0299072021, -0.0264325799, 1.0563397820]),
1227 'direction':'forward'
1229 elif gamut == 'S-Gamut3':
1230 cs.toReferenceTransforms.append( {
1232 'matrix':mat44FromMat33([0.7529825954, 0.1433702162, 0.1036471884,
1233 0.0217076974, 1.0153188355, -0.0370265329,
1234 -0.0094160528, 0.0033704179, 1.0060456349]),
1235 'direction':'forward'
1238 cs.fromReferenceTransforms = []
1242 SLog1SGamut = createSlog("S-Gamut", "S-Log1", name="S-Log")
1243 configData['colorSpaces'].append(SLog1SGamut)
1246 SLog2SGamut = createSlog("S-Gamut", "S-Log2", name="S-Log2")
1247 configData['colorSpaces'].append(SLog2SGamut)
1249 SLog2SGamutDaylight = createSlog("S-Gamut Daylight", "S-Log2", name="S-Log2")
1250 configData['colorSpaces'].append(SLog2SGamutDaylight)
1252 SLog2SGamutTungsten = createSlog("S-Gamut Tungsten", "S-Log2", name="S-Log2")
1253 configData['colorSpaces'].append(SLog2SGamutTungsten)
1256 SLog3SGamut3Cine = createSlog("S-Gamut3.Cine", "S-Log3", name="S-Log3")
1257 configData['colorSpaces'].append(SLog3SGamut3Cine)
1259 SLog3SGamut3 = createSlog("S-Gamut3", "S-Log3", name="S-Log3")
1260 configData['colorSpaces'].append(SLog3SGamut3)
1262 # Linearization only
1263 SLog1 = createSlog("", "S-Log1", name="S-Log")
1264 configData['colorSpaces'].append(SLog1)
1266 SLog2 = createSlog("", "S-Log2", name="S-Log2")
1267 configData['colorSpaces'].append(SLog2)
1269 SLog3 = createSlog("", "S-Log3", name="S-Log3")
1270 configData['colorSpaces'].append(SLog3)
1273 SGamut = createSlog("S-Gamut", "", name="S-Log")
1274 configData['colorSpaces'].append(SGamut)
1276 SGamutDaylight = createSlog("S-Gamut Daylight", "", name="S-Log2")
1277 configData['colorSpaces'].append(SGamutDaylight)
1279 SGamutTungsten = createSlog("S-Gamut Tungsten", "", name="S-Log2")
1280 configData['colorSpaces'].append(SGamutTungsten)
1282 SGamut3Cine = createSlog("S-Gamut3.Cine", "", name="S-Log3")
1283 configData['colorSpaces'].append(SGamut3Cine)
1285 SGamut3 = createSlog("S-Gamut3", "", name="S-Log3")
1286 configData['colorSpaces'].append(SGamut3)
1291 def createLogC(gamut, transferFunction, exposureIndex, name='LogC'):
1292 name = "%s (EI%s) - %s" % (transferFunction, exposureIndex, gamut)
1293 if transferFunction == "":
1294 name = "Linear - %s" % gamut
1296 name = "%s (EI%s)" % (transferFunction, exposureIndex)
1298 cs = ColorSpace(name)
1299 cs.description = name
1300 cs.equalityGroup = ''
1305 IDT_maker_version = "0.08"
1308 blackSignal = 0.003907
1309 midGraySignal = 0.01
1310 encodingGain = 0.256598
1311 encodingOffset = 0.391007
1314 return (math.log(EI/nominalEI)/math.log(2) * (0.89 - 1) / 3 + 1) * encodingGain
1316 def LogCInverseParametersForEI(EI) :
1318 slope = 1.0 / (cut * math.log(10))
1319 offset = math.log10(cut) - slope * cut
1320 gain = EI / nominalEI
1321 gray = midGraySignal / gain
1322 # The higher the EI, the lower the gamma
1323 encGain = gainForEI(EI)
1324 encOffset = encodingOffset
1325 for i in range(0,3) :
1326 nz = ((95.0 / 1023.0 - encOffset) / encGain - offset) / slope
1327 encOffset = encodingOffset - math.log10(1 + nz) * encGain
1328 # Calculate some intermediate values
1330 b = nz - blackSignal / gray
1331 e = slope * a * encGain
1332 f = encGain * (slope * b + offset) + encOffset
1333 # Manipulations so we can return relative exposure
1342 'cut' : (cut - b) / a,
1348 def logCtoLinear(codeValue, exposureIndex):
1349 p = LogCInverseParametersForEI(exposureIndex)
1350 breakpoint = p['e'] * p['cut'] + p['f']
1351 if (codeValue > breakpoint):
1352 linear = (pow(10,(codeValue/1023.0 - p['d']) / p['c']) - p['b']) / p['a']
1354 linear = (codeValue/1023.0 - p['f']) / p['e']
1356 #print( codeValue, linear )
1360 cs.toReferenceTransforms = []
1362 if transferFunction == "V3 LogC":
1363 data = array.array('f', "\0" * lutResolution1d * 4)
1364 for c in range(lutResolution1d):
1365 data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex))
1367 lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex))
1368 WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1370 #print( "Writing %s" % lut)
1371 cs.toReferenceTransforms.append( {
1374 'interpolation':'linear',
1375 'direction':'forward'
1378 if gamut == 'Wide Gamut':
1379 cs.toReferenceTransforms.append( {
1381 'matrix':mat44FromMat33([0.680206, 0.236137, 0.083658,
1382 0.085415, 1.017471, -0.102886,
1383 0.002057, -0.062563, 1.060506]),
1384 'direction':'forward'
1387 cs.fromReferenceTransforms = []
1390 transferFunction = "V3 LogC"
1391 gamut = "Wide Gamut"
1392 #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]
1393 EIs = [160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600, 2000, 2560, 3200]
1398 LogCEIfull = createLogC(gamut, transferFunction, EI, name="LogC")
1399 configData['colorSpaces'].append(LogCEIfull)
1401 # Linearization only
1403 LogCEIlinearization = createLogC("", transferFunction, EI, name="LogC")
1404 configData['colorSpaces'].append(LogCEIlinearization)
1407 LogCEIprimaries = createLogC(gamut, "", defaultEI, name="LogC")
1408 configData['colorSpaces'].append(LogCEIprimaries)
1411 # Generic log transform
1413 def createGenericLog(name='log',
1420 lutResolution1d=lutResolution1d):
1421 cs = ColorSpace(name)
1422 cs.description = "The %s color space" % name
1423 cs.equalityGroup = name
1424 cs.family = 'Utility'
1428 #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
1429 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
1431 lut = "%s_to_aces.spi1d" % name
1433 generate1dLUTFromCTL( lutDir + "/" + lut,
1440 'middleGrey' : middleGrey,
1441 'minExposure' : minExposure,
1442 'maxExposure' : maxExposure
1449 cs.toReferenceTransforms = []
1450 cs.toReferenceTransforms.append( {
1453 'interpolation':'linear',
1454 'direction':'forward'
1457 cs.fromReferenceTransforms = []
1463 def createACESLMT(lmtName,
1466 lutResolution1d=1024,
1469 cs = ColorSpace("%s" % lmtName)
1470 cs.description = "The ACES Look Transform: %s" % lmtName
1471 cs.equalityGroup = ''
1476 pprint.pprint( lmtValues )
1479 # Generate the shaper transform
1481 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1483 shaperLut = "%s_to_aces.spi1d" % shaperName
1484 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1486 shaperToACESCTL % acesCTLReleaseDir
1488 generate1dLUTFromCTL( lutDir + "/" + shaperLut,
1492 1.0/shaperInputScale,
1498 shaperOCIOTransform = {
1501 'interpolation':'linear',
1502 'direction':'inverse'
1506 # Generate the forward transform
1508 cs.fromReferenceTransforms = []
1510 if 'transformCTL' in lmtValues:
1512 shaperToACESCTL % acesCTLReleaseDir,
1513 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
1515 lut = "%s.%s.spi3d" % (shaperName, lmtName)
1517 generate3dLUTFromCTL( lutDir + "/" + lut,
1521 1.0/shaperInputScale,
1527 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1528 cs.fromReferenceTransforms.append( {
1531 'interpolation':'tetrahedral',
1532 'direction':'forward'
1536 # Generate the inverse transform
1538 cs.toReferenceTransforms = []
1540 if 'transformCTLInverse' in lmtValues:
1542 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1543 shaperFromACESCTL % acesCTLReleaseDir
1545 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
1547 generate3dLUTFromCTL( lutDir + "/" + lut,
1557 cs.toReferenceTransforms.append( {
1560 'interpolation':'tetrahedral',
1561 'direction':'forward'
1564 shaperInverse = shaperOCIOTransform.copy()
1565 shaperInverse['direction'] = 'forward'
1566 cs.toReferenceTransforms.append( shaperInverse )
1574 lmtLutResolution1d = max(4096, lutResolution1d)
1575 lmtLutResolution3d = max(65, lutResolution3d)
1578 lmtShaperName = 'lmtShaper'
1580 'middleGrey' : 0.18,
1581 'minExposure' : -10.0,
1584 lmtShaper = createGenericLog(name=lmtShaperName,
1585 middleGrey=lmtParams['middleGrey'],
1586 minExposure=lmtParams['minExposure'],
1587 maxExposure=lmtParams['maxExposure'],
1588 lutResolution1d=lmtLutResolution1d)
1589 configData['colorSpaces'].append(lmtShaper)
1591 shaperInputScale_genericLog2 = 1.0
1593 # Log 2 shaper name and CTL transforms bundled up
1596 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1597 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1598 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1599 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1600 shaperInputScale_genericLog2,
1604 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
1606 for lmt in sortedLMTs:
1607 (lmtName, lmtValues) = lmt
1609 lmtValues['transformUserName'],
1615 configData['colorSpaces'].append(cs)
1618 # ACES RRT with the supplied ODT
1620 def createACESRRTplusODT(odtName,
1623 lutResolution1d=1024,
1626 cs = ColorSpace("%s" % odtName)
1627 cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
1628 cs.equalityGroup = ''
1629 cs.family = 'Output'
1633 pprint.pprint( odtValues )
1636 # Generate the shaper transform
1638 #if 'shaperCTL' in odtValues:
1639 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1641 if 'legalRange' in odtValues:
1642 shaperParams['legalRange'] = odtValues['legalRange']
1644 shaperParams['legalRange'] = 0
1646 shaperLut = "%s_to_aces.spi1d" % shaperName
1647 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1649 shaperToACESCTL % acesCTLReleaseDir
1651 generate1dLUTFromCTL( lutDir + "/" + shaperLut,
1655 1.0/shaperInputScale,
1661 shaperOCIOTransform = {
1664 'interpolation':'linear',
1665 'direction':'inverse'
1669 # Generate the forward transform
1671 cs.fromReferenceTransforms = []
1673 if 'transformLUT' in odtValues:
1674 # Copy into the lut dir
1675 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1676 lut = lutDir + "/" + transformLUTFileName
1677 shutil.copy(odtValues['transformLUT'], lut)
1679 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1680 cs.fromReferenceTransforms.append( {
1682 'path': transformLUTFileName,
1683 'interpolation':'tetrahedral',
1684 'direction':'forward'
1686 elif 'transformCTL' in odtValues:
1690 shaperToACESCTL % acesCTLReleaseDir,
1691 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1692 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1694 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1696 generate3dLUTFromCTL( lutDir + "/" + lut,
1701 1.0/shaperInputScale,
1707 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1708 cs.fromReferenceTransforms.append( {
1711 'interpolation':'tetrahedral',
1712 'direction':'forward'
1716 # Generate the inverse transform
1718 cs.toReferenceTransforms = []
1720 if 'transformLUTInverse' in odtValues:
1721 # Copy into the lut dir
1722 transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
1723 lut = lutDir + "/" + transformLUTInverseFileName
1724 shutil.copy(odtValues['transformLUTInverse'], lut)
1726 cs.toReferenceTransforms.append( {
1728 'path': transformLUTInverseFileName,
1729 'interpolation':'tetrahedral',
1730 'direction':'forward'
1733 shaperInverse = shaperOCIOTransform.copy()
1734 shaperInverse['direction'] = 'forward'
1735 cs.toReferenceTransforms.append( shaperInverse )
1736 elif 'transformCTLInverse' in odtValues:
1738 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1739 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1740 shaperFromACESCTL % acesCTLReleaseDir
1742 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1744 generate3dLUTFromCTL( lutDir + "/" + lut,
1755 cs.toReferenceTransforms.append( {
1758 'interpolation':'tetrahedral',
1759 'direction':'forward'
1762 shaperInverse = shaperOCIOTransform.copy()
1763 shaperInverse['direction'] = 'forward'
1764 cs.toReferenceTransforms.append( shaperInverse )
1769 # RRT/ODT shaper options
1774 log2ShaperName = shaperName
1776 'middleGrey' : 0.18,
1777 'minExposure' : -6.0,
1780 log2Shaper = createGenericLog(name=log2ShaperName,
1781 middleGrey=log2Params['middleGrey'],
1782 minExposure=log2Params['minExposure'],
1783 maxExposure=log2Params['maxExposure'])
1784 configData['colorSpaces'].append(log2Shaper)
1786 shaperInputScale_genericLog2 = 1.0
1788 # Log 2 shaper name and CTL transforms bundled up
1791 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1792 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1793 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1794 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1795 shaperInputScale_genericLog2,
1799 shaperData[log2ShaperName] = log2ShaperData
1802 # Shaper that also includes the AP1 primaries
1803 # - Needed for some LUT baking steps
1805 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1806 middleGrey=log2Params['middleGrey'],
1807 minExposure=log2Params['minExposure'],
1808 maxExposure=log2Params['maxExposure'])
1809 log2ShaperAP1.name = "%s AP1" % log2ShaperAP1.name
1810 # AP1 primaries to AP0 primaries
1811 log2ShaperAP1.toReferenceTransforms.append( {
1813 'matrix':mat44FromMat33(acesAP1toAP0),
1814 'direction':'forward'
1816 configData['colorSpaces'].append(log2ShaperAP1)
1819 # Choose your shaper
1822 # Shaper name. Should really be automated or made a user choice
1824 # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
1825 #shaperName = 'log2Shaper'
1827 #if shaperName in shaperData:
1828 # rrtShaperName = shaperName
1829 # rrtShaper = shaperData[shaperName]
1832 rrtShaperName = log2ShaperName
1833 rrtShaper = log2ShaperData
1836 # RRT + ODT Combinations
1838 #for odtName, odtValues in odtInfo.iteritems():
1839 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1841 for odt in sortedOdts:
1842 (odtName, odtValues) = odt
1844 # Have to handle ODTs that can generate either legal or full output
1845 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1846 'Academy.Rec709_100nits_dim.a1.0.0',
1847 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1848 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1850 odtNameLegal = odtValues['transformUserName']
1852 odtLegal = odtValues.copy()
1853 odtLegal['legalRange'] = 1
1855 cs = createACESRRTplusODT(
1862 configData['colorSpaces'].append(cs)
1864 # Create a display entry using this color space
1865 configData['displays'][odtNameLegal] = {
1868 'Output Transform':cs }
1870 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1871 'Academy.Rec709_100nits_dim.a1.0.0',
1872 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1874 print( "Generating full range ODT for %s" % odtName)
1876 odtNameFull = "%s - Full" % odtValues['transformUserName']
1877 odtFull = odtValues.copy()
1878 odtFull['legalRange'] = 0
1880 csFull = createACESRRTplusODT(
1887 configData['colorSpaces'].append(csFull)
1889 # Create a display entry using this color space
1890 configData['displays'][odtNameFull] = {
1893 'Output Transform':csFull }
1896 print( "generateLUTs - end" )
1899 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1901 # Add the legal and full variations into this list
1902 odtInfoC = dict(odtInfo)
1903 for odtCTLName, odtValues in odtInfo.iteritems():
1904 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1905 'Academy.Rec709_100nits_dim.a1.0.0',
1906 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1907 odtName = odtValues["transformUserName"]
1909 odtValuesLegal = dict(odtValues)
1910 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1911 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1913 odtValuesFull = dict(odtValues)
1914 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1915 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1917 del( odtInfoC[odtCTLName] )
1919 for odtCTLName, odtValues in odtInfoC.iteritems():
1920 odtPrefix = odtValues["transformUserNamePrefix"]
1921 odtName = odtValues["transformUserName"]
1924 for inputspace in ["ACEScc", "ACESproxy"]:
1925 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1926 args += ["--outputspace", "%s" % odtName ]
1927 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1928 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1929 args += ["--cubesize", str(lutResolution3d) ]
1930 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1932 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1936 for inputspace in ["ACEScc", "ACESproxy"]:
1937 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1938 args += ["--outputspace", "%s" % odtName ]
1939 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1940 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1941 args += ["--cubesize", str(lutResolution3d) ]
1943 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1944 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1947 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1948 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1952 for inputspace in ["ACEScg", "ACES2065-1"]:
1953 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1954 args += ["--outputspace", "%s" % odtName ]
1955 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1956 if inputspace == 'ACEScg':
1957 linShaperName = "%s AP1" % shaperName
1959 linShaperName = shaperName
1960 args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ]
1962 args += ["--cubesize", str(lutResolution3d) ]
1964 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
1965 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
1968 hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
1969 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
1973 def createConfigDir(configDir, bakeSecondaryLUTs):
1974 dirs = [configDir, "%s/luts" % configDir]
1975 if bakeSecondaryLUTs:
1976 dirs.extend(["%s/baked" % configDir,
1977 "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
1978 "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
1979 "%s/baked/maya" % configDir])
1982 if not os.path.exists(d):
1985 def getTransformInfo(ctlTransform):
1986 fp = open(ctlTransform, 'rb')
1989 lines = fp.readlines()
1991 # Grab transform ID and User Name
1992 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1993 #print( transformID )
1994 transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
1995 transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
1996 #print( transformUserName )
1999 return (transformID, transformUserName, transformUserNamePrefix)
2001 # For versions after WGR9
2002 def getODTInfo(acesCTLReleaseDir):
2003 # Credit to Alex Fry for the original approach here
2004 odtDir = os.path.join(acesCTLReleaseDir, "odt")
2006 for dirName, subdirList, fileList in os.walk(odtDir):
2007 for fname in fileList:
2008 allodt.append((os.path.join(dirName,fname)))
2010 odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
2016 for odtCTL in odtCTLs:
2017 odtTokens = os.path.split(odtCTL)
2020 # Handle nested directories
2021 odtPathTokens = os.path.split(odtTokens[-2])
2022 odtDir = odtPathTokens[-1]
2023 while odtPathTokens[-2][-3:] != 'odt':
2024 odtPathTokens = os.path.split(odtPathTokens[-2])
2025 odtDir = os.path.join(odtPathTokens[-1], odtDir)
2028 #print( "odtDir : %s" % odtDir )
2029 transformCTL = odtTokens[-1]
2030 #print( transformCTL )
2031 odtName = string.join(transformCTL.split('.')[1:-1], '.')
2034 # Find id, user name and user name prefix
2035 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2036 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
2039 transformCTLInverse = "InvODT.%s.ctl" % odtName
2040 if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
2041 transformCTLInverse = None
2042 #print( transformCTLInverse )
2044 # Add to list of ODTs
2046 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
2047 if transformCTLInverse != None:
2048 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
2050 odts[odtName]['transformID'] = transformID
2051 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
2052 odts[odtName]['transformUserName'] = transformUserName
2054 print( "ODT : %s" % odtName )
2055 print( "\tTransform ID : %s" % transformID )
2056 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2057 print( "\tTransform User Name : %s" % transformUserName )
2058 print( "\tForward ctl : %s" % odts[odtName]['transformCTL'])
2059 if 'transformCTLInverse' in odts[odtName]:
2060 print( "\tInverse ctl : %s" % odts[odtName]['transformCTLInverse'])
2062 print( "\tInverse ctl : %s" % "None" )
2068 # For versions after WGR9
2069 def getLMTInfo(acesCTLReleaseDir):
2070 # Credit to Alex Fry for the original approach here
2071 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
2073 for dirName, subdirList, fileList in os.walk(lmtDir):
2074 for fname in fileList:
2075 alllmt.append((os.path.join(dirName,fname)))
2077 lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
2083 for lmtCTL in lmtCTLs:
2084 lmtTokens = os.path.split(lmtCTL)
2087 # Handle nested directories
2088 lmtPathTokens = os.path.split(lmtTokens[-2])
2089 lmtDir = lmtPathTokens[-1]
2090 while lmtPathTokens[-2][-3:] != 'ctl':
2091 lmtPathTokens = os.path.split(lmtPathTokens[-2])
2092 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
2095 #print( "lmtDir : %s" % lmtDir )
2096 transformCTL = lmtTokens[-1]
2097 #print( transformCTL )
2098 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
2101 # Find id, user name and user name prefix
2102 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2103 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
2106 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
2107 if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
2108 transformCTLInverse = None
2109 #print( transformCTLInverse )
2111 # Add to list of LMTs
2113 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
2114 if transformCTLInverse != None:
2115 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
2117 lmts[lmtName]['transformID'] = transformID
2118 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
2119 lmts[lmtName]['transformUserName'] = transformUserName
2121 print( "LMT : %s" % lmtName )
2122 print( "\tTransform ID : %s" % transformID )
2123 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2124 print( "\tTransform User Name : %s" % transformUserName )
2125 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
2126 if 'transformCTLInverse' in lmts[lmtName]:
2127 print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
2129 print( "\t Inverse ctl : %s" % "None" )
2136 # Create the ACES config
2138 def createACESConfig(acesCTLReleaseDir,
2140 lutResolution1d=4096,
2142 bakeSecondaryLUTs=True,
2145 # Get ODT names and CTL paths
2146 odtInfo = getODTInfo(acesCTLReleaseDir)
2148 # Get ODT names and CTL paths
2149 lmtInfo = getLMTInfo(acesCTLReleaseDir)
2152 createConfigDir(configDir, bakeSecondaryLUTs)
2154 # Generate config data and LUTs for different transforms
2155 lutDir = "%s/luts" % configDir
2156 shaperName = 'outputShaper'
2157 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
2159 # Create the config using the generated LUTs
2160 print( "Creating generic config")
2161 config = createConfig(configData)
2164 # Write the config to disk
2165 writeConfig(config, "%s/config.ocio" % configDir )
2167 # Create a config that will work well with Nuke using the previously generated LUTs
2168 print( "Creating Nuke-specific config")
2169 nuke_config = createConfig(configData, nuke=True)
2172 # Write the config to disk
2173 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
2175 # Bake secondary LUTs using the config
2176 if bakeSecondaryLUTs:
2177 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
2185 p = optparse.OptionParser(description='An OCIO config generation script',
2186 prog='createACESConfig',
2187 version='createACESConfig 0.1',
2188 usage='%prog [options]')
2189 p.add_option('--acesCTLDir', '-a', default=None)
2190 p.add_option('--configDir', '-c', default=None)
2191 p.add_option('--lutResolution1d', default=4096)
2192 p.add_option('--lutResolution3d', default=64)
2193 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
2194 p.add_option('--keepTempImages', action="store_true")
2196 options, arguments = p.parse_args()
2201 acesCTLDir = options.acesCTLDir
2202 configDir = options.configDir
2203 lutResolution1d = int(options.lutResolution1d)
2204 lutResolution3d = int(options.lutResolution3d)
2205 bakeSecondaryLUTs = not(options.dontBakeSecondaryLUTs)
2206 cleanupTempImages = not(options.keepTempImages)
2209 argsStart = sys.argv.index('--') + 1
2210 args = sys.argv[argsStart:]
2212 argsStart = len(sys.argv)+1
2215 print( "command line : \n%s\n" % " ".join(sys.argv) )
2217 if configDir == None:
2218 print( "process: No ACES CTL directory specified" )
2222 # Generate the configuration
2224 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
2227 if __name__ == '__main__':