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
54 import generateLUT as genlut
55 import createREDColorSpaces as red
56 import createCanonColorSpaces as canon
57 import createSonyColorSpaces as sony
58 import createARRIColorSpaces as arri
63 def setConfigDefaultRoles( config,
75 if color_picking: config.setRole( OCIO.Constants.ROLE_COLOR_PICKING, color_picking )
76 if color_timing: config.setRole( OCIO.Constants.ROLE_COLOR_TIMING, color_timing )
77 if compositing_log: config.setRole( OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log )
78 if data: config.setRole( OCIO.Constants.ROLE_DATA, data )
79 if default: config.setRole( OCIO.Constants.ROLE_DEFAULT, default )
80 if matte_paint: config.setRole( OCIO.Constants.ROLE_MATTE_PAINT, matte_paint )
81 if reference: config.setRole( OCIO.Constants.ROLE_REFERENCE, reference )
82 if scene_linear: config.setRole( OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear )
83 if texture_paint: config.setRole( OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint )
85 # Write config to disk
86 def writeConfig( config, configPath, sanityCheck=True ):
92 print "Configuration was not written due to a failed Sanity Check"
96 fileHandle = open( configPath, mode='w' )
97 fileHandle.write( config.serialize() )
100 def generateOCIOTransform(transforms):
101 #print( "Generating transforms")
103 interpolationOptions = {
104 'linear':OCIO.Constants.INTERP_LINEAR,
105 'nearest':OCIO.Constants.INTERP_NEAREST,
106 'tetrahedral':OCIO.Constants.INTERP_TETRAHEDRAL
109 'forward':OCIO.Constants.TRANSFORM_DIR_FORWARD,
110 'inverse':OCIO.Constants.TRANSFORM_DIR_INVERSE
115 for transform in transforms:
116 if transform['type'] == 'lutFile':
118 ocioTransform = OCIO.FileTransform( src=transform['path'],
119 interpolation=interpolationOptions[transform['interpolation']],
120 direction=directionOptions[transform['direction']] )
122 ocioTransforms.append(ocioTransform)
123 elif transform['type'] == 'matrix':
124 ocioTransform = OCIO.MatrixTransform()
125 # MatrixTransform member variables can't be initialized directly. Each must be set individually
126 ocioTransform.setMatrix( transform['matrix'] )
128 if 'offset' in transform:
129 ocioTransform.setOffset( transform['offset'] )
130 if 'direction' in transform:
131 ocioTransform.setDirection( directionOptions[transform['direction']] )
133 ocioTransforms.append(ocioTransform)
134 elif transform['type'] == 'exponent':
135 ocioTransform = OCIO.ExponentTransform()
136 ocioTransform.setValue( transform['value'] )
138 ocioTransforms.append(ocioTransform)
139 elif transform['type'] == 'log':
140 ocioTransform = OCIO.LogTransform(base=transform['base'],
141 direction=directionOptions[transform['direction']])
143 ocioTransforms.append(ocioTransform)
145 print( "Ignoring unknown transform type : %s" % transform['type'] )
147 # Build a group transform if necessary
148 if len(ocioTransforms) > 1:
149 transformG = OCIO.GroupTransform()
150 for transform in ocioTransforms:
151 transformG.push_back( transform )
152 transform = transformG
154 # Or take the first transform from the list
156 transform = ocioTransforms[0]
160 def createConfig(configData, nuke=False):
162 config = OCIO.Config()
165 # Set config wide values
167 config.setDescription( "An ACES config generated from python" )
168 config.setSearchPath( "luts" )
171 # Define the reference color space
173 referenceData = configData['referenceColorSpace']
174 print( "Adding the reference color space : %s" % referenceData.name)
176 # Create a color space
177 reference = OCIO.ColorSpace( name=referenceData.name,
178 bitDepth=referenceData.bitDepth,
179 description=referenceData.description,
180 equalityGroup=referenceData.equalityGroup,
181 family=referenceData.family,
182 isData=referenceData.isData,
183 allocation=referenceData.allocationType,
184 allocationVars=referenceData.allocationVars )
187 config.addColorSpace( reference )
190 # Create the rest of the color spaces
192 #sortedColorspaces = sorted(configData['colorSpaces'], key=lambda x: x.name)
193 #print( sortedColorspaces )
194 #for colorspace in sortedColorspaces:
195 for colorspace in sorted(configData['colorSpaces']):
196 print( "Creating new color space : %s" % colorspace.name)
198 ocioColorspace = OCIO.ColorSpace( name=colorspace.name,
199 bitDepth=colorspace.bitDepth,
200 description=colorspace.description,
201 equalityGroup=colorspace.equalityGroup,
202 family=colorspace.family,
203 isData=colorspace.isData,
204 allocation=colorspace.allocationType,
205 allocationVars=colorspace.allocationVars )
207 if colorspace.toReferenceTransforms != []:
208 print( "Generating To-Reference transforms")
209 ocioTransform = generateOCIOTransform(colorspace.toReferenceTransforms)
210 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE )
212 if colorspace.fromReferenceTransforms != []:
213 print( "Generating From-Reference transforms")
214 ocioTransform = generateOCIOTransform(colorspace.fromReferenceTransforms)
215 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE )
217 config.addColorSpace(ocioColorspace)
222 # Define the views and displays
227 # Generic display and view setup
229 for display, viewList in configData['displays'].iteritems():
230 for viewName, colorspace in viewList.iteritems():
231 config.addDisplay( display, viewName, colorspace.name )
232 if not (viewName in views):
233 views.append(viewName)
234 displays.append(display)
235 # A Nuke specific set of views and displays
238 # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
241 for display, viewList in configData['displays'].iteritems():
242 for viewName, colorspace in viewList.iteritems():
243 if( viewName == 'Output Transform'):
245 config.addDisplay( display, viewName, colorspace.name )
246 if not (viewName in views):
247 views.append(viewName)
248 displays.append(display)
250 config.addDisplay( 'linear', 'View', 'ACES2065-1' )
251 displays.append('linear')
252 config.addDisplay( 'log', 'View', 'ACEScc' )
253 displays.append('log')
255 # Set active displays and views
256 config.setActiveDisplays( ','.join(sorted(displays)) )
257 config.setActiveViews( ','.join(views) )
260 # Need to generalize this at some point
264 setConfigDefaultRoles( config,
265 color_picking=reference.getName(),
266 color_timing=reference.getName(),
267 compositing_log=reference.getName(),
268 data=reference.getName(),
269 default=reference.getName(),
270 matte_paint=reference.getName(),
271 reference=reference.getName(),
272 scene_linear=reference.getName(),
273 texture_paint=reference.getName() )
275 # Check to make sure we didn't screw something up
281 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
284 # Output is a list of colorspaces and transforms that convert between those
285 # colorspaces and reference color space, ACES
286 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d=4096, lutResolution3d=64, cleanup=True):
287 print( "generateLUTs - begin" )
291 # Define the reference color space
293 ACES = ColorSpace('ACES2065-1')
294 ACES.description = "The Academy Color Encoding System reference color space"
295 ACES.equalityGroup = ''
298 ACES.allocationType=OCIO.Constants.ALLOCATION_LG2
299 ACES.allocationVars=[-15, 6]
301 configData['referenceColorSpace'] = ACES
304 # Define the displays
306 configData['displays'] = {}
309 # Define the other color spaces
311 configData['colorSpaces'] = []
313 # Matrix converting ACES AP1 primaries to AP0
314 acesAP1toAP0 = [ 0.6954522414, 0.1406786965, 0.1638690622,
315 0.0447945634, 0.8596711185, 0.0955343182,
316 -0.0055258826, 0.0040252103, 1.0015006723]
318 # Matrix converting ACES AP0 primaries to XYZ
319 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
320 0.3439664498, 0.7281660966, -0.0721325464,
321 0.0000000000, 0.0000000000, 1.0088251844]
326 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
327 cs = ColorSpace(name)
328 cs.description = "The %s color space" % name
329 cs.equalityGroup = ''
334 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
335 # This transform gets back to the AP1 primaries
336 # Useful as the 1d LUT is only covering the transfer function
337 # The primaries switch is covered by the matrix below
338 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
340 lut = "%s_to_ACES.spi1d" % name
341 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
353 cs.toReferenceTransforms = []
354 cs.toReferenceTransforms.append( {
357 'interpolation':'linear',
358 'direction':'forward'
361 # AP1 primaries to AP0 primaries
362 cs.toReferenceTransforms.append( {
364 'matrix':mat44FromMat33(acesAP1toAP0),
365 'direction':'forward'
368 cs.fromReferenceTransforms = []
371 ACEScc = createACEScc()
372 configData['colorSpaces'].append(ACEScc)
377 def createACESProxy(name='ACESproxy'):
378 cs = ColorSpace(name)
379 cs.description = "The %s color space" % name
380 cs.equalityGroup = ''
385 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
386 # This transform gets back to the AP1 primaries
387 # Useful as the 1d LUT is only covering the transfer function
388 # The primaries switch is covered by the matrix below
389 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
391 lut = "%s_to_aces.spi1d" % name
392 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
402 cs.toReferenceTransforms = []
403 cs.toReferenceTransforms.append( {
406 'interpolation':'linear',
407 'direction':'forward'
410 # AP1 primaries to AP0 primaries
411 cs.toReferenceTransforms.append( {
413 'matrix':mat44FromMat33(acesAP1toAP0),
414 'direction':'forward'
418 cs.fromReferenceTransforms = []
421 ACESproxy = createACESProxy()
422 configData['colorSpaces'].append(ACESproxy)
427 def createACEScg(name='ACEScg'):
428 cs = ColorSpace(name)
429 cs.description = "The %s color space" % name
430 cs.equalityGroup = ''
434 cs.toReferenceTransforms = []
436 # AP1 primaries to AP0 primaries
437 cs.toReferenceTransforms.append( {
439 'matrix':mat44FromMat33(acesAP1toAP0),
440 'direction':'forward'
443 cs.fromReferenceTransforms = []
446 ACEScg = createACEScg()
447 configData['colorSpaces'].append(ACEScg)
452 def createADX(bitdepth=10, name='ADX'):
453 name = "%s%s" % (name, bitdepth)
454 cs = ColorSpace(name)
455 cs.description = "%s color space - used for film scans" % name
456 cs.equalityGroup = ''
461 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
462 adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
463 0.0, 1023.0/500.0, 0.0, 0.0,
464 0.0, 0.0, 1023.0/500.0, 0.0,
466 offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
468 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
469 adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
470 0.0, 65535.0/8000.0, 0.0, 0.0,
471 0.0, 0.0, 65535.0/8000.0, 0.0,
473 offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
475 cs.toReferenceTransforms = []
477 # Convert from ADX to Channel-Dependent Density
478 cs.toReferenceTransforms.append( {
482 'direction':'forward'
485 # Convert from Channel-Dependent Density to Channel-Independent Density
486 cs.toReferenceTransforms.append( {
488 'matrix':[0.75573, 0.22197, 0.02230, 0,
489 0.05901, 0.96928, -0.02829, 0,
490 0.16134, 0.07406, 0.76460, 0,
492 'direction':'forward'
495 # Copied from Alex Fry's adx_cid_to_rle.py
496 def createCIDtoRLELUT():
497 def interpolate1D(x, xp, fp):
498 return numpy.interp(x, xp, fp)
500 LUT_1D_xp = [-0.190000000000000,
512 LUT_1D_fp = [-6.000000000000000,
524 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
528 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
529 return (100.0 / 55.0) * x - REF_PT
531 def Fit(value, fromMin, fromMax, toMin, toMax):
532 if fromMin == fromMax:
533 raise ValueError("fromMin == fromMax")
534 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
539 for i in xrange(NUM_SAMPLES):
540 x = i/(NUM_SAMPLES-1.0)
541 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
542 data.append(cid_to_rle(x))
544 lut = 'ADX_CID_to_RLE.spi1d'
545 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
549 # Convert Channel Independent Density values to Relative Log Exposure values
550 lut = createCIDtoRLELUT()
551 cs.toReferenceTransforms.append( {
554 'interpolation':'linear',
555 'direction':'forward'
558 # Convert Relative Log Exposure values to Relative Exposure values
559 cs.toReferenceTransforms.append( {
562 'direction':'inverse'
565 # Convert Relative Exposure values to ACES values
566 cs.toReferenceTransforms.append( {
568 'matrix':[0.72286, 0.12630, 0.15084, 0,
569 0.11923, 0.76418, 0.11659, 0,
570 0.01427, 0.08213, 0.90359, 0,
572 'direction':'forward'
575 cs.fromReferenceTransforms = []
578 ADX10 = createADX(bitdepth=10)
579 configData['colorSpaces'].append(ADX10)
581 ADX16 = createADX(bitdepth=16)
582 configData['colorSpaces'].append(ADX16)
585 # Camera Input Transforms
588 # RED color spaces to ACES
589 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
590 for cs in redColorSpaces:
591 configData['colorSpaces'].append(cs)
594 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
595 for cs in canonColorSpaces:
596 configData['colorSpaces'].append(cs)
599 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
600 for cs in sonyColorSpaces:
601 configData['colorSpaces'].append(cs)
604 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
605 for cs in arriColorSpaces:
606 configData['colorSpaces'].append(cs)
609 # Generic log transform
611 def createGenericLog(name='log',
618 lutResolution1d=lutResolution1d):
619 cs = ColorSpace(name)
620 cs.description = "The %s color space" % name
621 cs.equalityGroup = name
622 cs.family = 'Utility'
626 #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
627 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
629 lut = "%s_to_aces.spi1d" % name
631 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
638 'middleGrey' : middleGrey,
639 'minExposure' : minExposure,
640 'maxExposure' : maxExposure
647 cs.toReferenceTransforms = []
648 cs.toReferenceTransforms.append( {
651 'interpolation':'linear',
652 'direction':'forward'
655 cs.fromReferenceTransforms = []
661 def createACESLMT(lmtName,
664 lutResolution1d=1024,
667 cs = ColorSpace("%s" % lmtName)
668 cs.description = "The ACES Look Transform: %s" % lmtName
669 cs.equalityGroup = ''
674 pprint.pprint( lmtValues )
677 # Generate the shaper transform
679 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
681 shaperLut = "%s_to_aces.spi1d" % shaperName
682 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
684 shaperToACESCTL % acesCTLReleaseDir
686 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
690 1.0/shaperInputScale,
696 shaperOCIOTransform = {
699 'interpolation':'linear',
700 'direction':'inverse'
704 # Generate the forward transform
706 cs.fromReferenceTransforms = []
708 if 'transformCTL' in lmtValues:
710 shaperToACESCTL % acesCTLReleaseDir,
711 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
713 lut = "%s.%s.spi3d" % (shaperName, lmtName)
715 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
719 1.0/shaperInputScale,
725 cs.fromReferenceTransforms.append( shaperOCIOTransform )
726 cs.fromReferenceTransforms.append( {
729 'interpolation':'tetrahedral',
730 'direction':'forward'
734 # Generate the inverse transform
736 cs.toReferenceTransforms = []
738 if 'transformCTLInverse' in lmtValues:
740 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
741 shaperFromACESCTL % acesCTLReleaseDir
743 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
745 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
755 cs.toReferenceTransforms.append( {
758 'interpolation':'tetrahedral',
759 'direction':'forward'
762 shaperInverse = shaperOCIOTransform.copy()
763 shaperInverse['direction'] = 'forward'
764 cs.toReferenceTransforms.append( shaperInverse )
772 lmtLutResolution1d = max(4096, lutResolution1d)
773 lmtLutResolution3d = max(65, lutResolution3d)
776 lmtShaperName = 'LMT Shaper'
779 'minExposure' : -10.0,
782 lmtShaper = createGenericLog(name=lmtShaperName,
783 middleGrey=lmtParams['middleGrey'],
784 minExposure=lmtParams['minExposure'],
785 maxExposure=lmtParams['maxExposure'],
786 lutResolution1d=lmtLutResolution1d)
787 configData['colorSpaces'].append(lmtShaper)
789 shaperInputScale_genericLog2 = 1.0
791 # Log 2 shaper name and CTL transforms bundled up
794 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
795 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
796 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
797 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
798 shaperInputScale_genericLog2,
802 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
804 for lmt in sortedLMTs:
805 (lmtName, lmtValues) = lmt
807 lmtValues['transformUserName'],
813 configData['colorSpaces'].append(cs)
816 # ACES RRT with the supplied ODT
818 def createACESRRTplusODT(odtName,
821 lutResolution1d=1024,
824 cs = ColorSpace("%s" % odtName)
825 cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
826 cs.equalityGroup = ''
831 pprint.pprint( odtValues )
834 # Generate the shaper transform
836 #if 'shaperCTL' in odtValues:
837 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
839 if 'legalRange' in odtValues:
840 shaperParams['legalRange'] = odtValues['legalRange']
842 shaperParams['legalRange'] = 0
844 shaperLut = "%s_to_aces.spi1d" % shaperName
845 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
847 shaperToACESCTL % acesCTLReleaseDir
849 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
853 1.0/shaperInputScale,
859 shaperOCIOTransform = {
862 'interpolation':'linear',
863 'direction':'inverse'
867 # Generate the forward transform
869 cs.fromReferenceTransforms = []
871 if 'transformLUT' in odtValues:
872 # Copy into the lut dir
873 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
874 lut = lutDir + "/" + transformLUTFileName
875 shutil.copy(odtValues['transformLUT'], lut)
877 cs.fromReferenceTransforms.append( shaperOCIOTransform )
878 cs.fromReferenceTransforms.append( {
880 'path': transformLUTFileName,
881 'interpolation':'tetrahedral',
882 'direction':'forward'
884 elif 'transformCTL' in odtValues:
888 shaperToACESCTL % acesCTLReleaseDir,
889 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
890 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
892 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
894 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
899 1.0/shaperInputScale,
905 cs.fromReferenceTransforms.append( shaperOCIOTransform )
906 cs.fromReferenceTransforms.append( {
909 'interpolation':'tetrahedral',
910 'direction':'forward'
914 # Generate the inverse transform
916 cs.toReferenceTransforms = []
918 if 'transformLUTInverse' in odtValues:
919 # Copy into the lut dir
920 transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
921 lut = lutDir + "/" + transformLUTInverseFileName
922 shutil.copy(odtValues['transformLUTInverse'], lut)
924 cs.toReferenceTransforms.append( {
926 'path': transformLUTInverseFileName,
927 'interpolation':'tetrahedral',
928 'direction':'forward'
931 shaperInverse = shaperOCIOTransform.copy()
932 shaperInverse['direction'] = 'forward'
933 cs.toReferenceTransforms.append( shaperInverse )
934 elif 'transformCTLInverse' in odtValues:
936 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
937 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
938 shaperFromACESCTL % acesCTLReleaseDir
940 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
942 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
953 cs.toReferenceTransforms.append( {
956 'interpolation':'tetrahedral',
957 'direction':'forward'
960 shaperInverse = shaperOCIOTransform.copy()
961 shaperInverse['direction'] = 'forward'
962 cs.toReferenceTransforms.append( shaperInverse )
967 # RRT/ODT shaper options
972 log2ShaperName = shaperName
975 'minExposure' : -6.0,
978 log2Shaper = createGenericLog(name=log2ShaperName,
979 middleGrey=log2Params['middleGrey'],
980 minExposure=log2Params['minExposure'],
981 maxExposure=log2Params['maxExposure'])
982 configData['colorSpaces'].append(log2Shaper)
984 shaperInputScale_genericLog2 = 1.0
986 # Log 2 shaper name and CTL transforms bundled up
989 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
990 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
991 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
992 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
993 shaperInputScale_genericLog2,
997 shaperData[log2ShaperName] = log2ShaperData
1000 # Shaper that also includes the AP1 primaries
1001 # - Needed for some LUT baking steps
1003 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1004 middleGrey=log2Params['middleGrey'],
1005 minExposure=log2Params['minExposure'],
1006 maxExposure=log2Params['maxExposure'])
1007 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1008 # AP1 primaries to AP0 primaries
1009 log2ShaperAP1.toReferenceTransforms.append( {
1011 'matrix':mat44FromMat33(acesAP1toAP0),
1012 'direction':'forward'
1014 configData['colorSpaces'].append(log2ShaperAP1)
1017 # Choose your shaper
1020 # Shaper name. Should really be automated or made a user choice
1022 # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
1023 #shaperName = 'log2Shaper'
1025 #if shaperName in shaperData:
1026 # rrtShaperName = shaperName
1027 # rrtShaper = shaperData[shaperName]
1030 rrtShaperName = log2ShaperName
1031 rrtShaper = log2ShaperData
1034 # RRT + ODT Combinations
1036 #for odtName, odtValues in odtInfo.iteritems():
1037 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1039 for odt in sortedOdts:
1040 (odtName, odtValues) = odt
1042 # Have to handle ODTs that can generate either legal or full output
1043 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1044 'Academy.Rec709_100nits_dim.a1.0.0',
1045 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1046 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1048 odtNameLegal = odtValues['transformUserName']
1050 odtLegal = odtValues.copy()
1051 odtLegal['legalRange'] = 1
1053 cs = createACESRRTplusODT(
1060 configData['colorSpaces'].append(cs)
1062 # Create a display entry using this color space
1063 configData['displays'][odtNameLegal] = {
1066 'Output Transform':cs }
1068 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1069 'Academy.Rec709_100nits_dim.a1.0.0',
1070 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1072 print( "Generating full range ODT for %s" % odtName)
1074 odtNameFull = "%s - Full" % odtValues['transformUserName']
1075 odtFull = odtValues.copy()
1076 odtFull['legalRange'] = 0
1078 csFull = createACESRRTplusODT(
1085 configData['colorSpaces'].append(csFull)
1087 # Create a display entry using this color space
1088 configData['displays'][odtNameFull] = {
1091 'Output Transform':csFull }
1094 # Generic Matrix transform
1096 def createGenericMatrix(name='matrix',
1097 fromReferenceValues=[],
1098 toReferenceValues=[]):
1099 cs = ColorSpace(name)
1100 cs.description = "The %s color space" % name
1101 cs.equalityGroup = name
1102 cs.family = 'Utility'
1105 cs.toReferenceTransforms = []
1106 if toReferenceValues != []:
1107 for matrix in toReferenceValues:
1108 cs.toReferenceTransforms.append( {
1110 'matrix':mat44FromMat33(matrix),
1111 'direction':'forward'
1114 cs.fromReferenceTransforms = []
1115 if fromReferenceValues != []:
1116 for matrix in fromReferenceValues:
1117 cs.fromReferenceTransforms.append( {
1119 'matrix':mat44FromMat33(matrix),
1120 'direction':'forward'
1125 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1126 configData['colorSpaces'].append(cs)
1128 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1129 configData['colorSpaces'].append(cs)
1131 # ACES to Linear, P3D60 primaries
1132 xyzToP3D60 = [ 2.4027414142, -0.8974841639, -0.3880533700,
1133 -0.8325796487, 1.7692317536, 0.0237127115,
1134 0.0388233815, -0.0824996856, 1.0363685997]
1136 cs = createGenericMatrix('Linear - P3-D60', fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1137 configData['colorSpaces'].append(cs)
1139 # ACES to Linear, P3D60 primaries
1140 xyzToP3DCI = [ 2.7253940305, -1.0180030062, -0.4401631952,
1141 -0.7951680258, 1.6897320548, 0.0226471906,
1142 0.0412418914, -0.0876390192, 1.1009293786]
1144 cs = createGenericMatrix('Linear - P3-DCI', fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1145 configData['colorSpaces'].append(cs)
1147 # ACES to Linear, Rec 709 primaries
1148 xyzToRec709 = [ 3.2409699419, -1.5373831776, -0.4986107603,
1149 -0.9692436363, 1.8759675015, 0.0415550574,
1150 0.0556300797, -0.2039769589, 1.0569715142]
1152 cs = createGenericMatrix('Linear - Rec.709', fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1153 configData['colorSpaces'].append(cs)
1155 # ACES to Linear, Rec 2020 primaries
1156 xyzToRec2020 = [ 1.7166511880, -0.3556707838, -0.2533662814,
1157 -0.6666843518, 1.6164812366, 0.0157685458,
1158 0.0176398574, -0.0427706133, 0.9421031212]
1160 cs = createGenericMatrix('Linear - Rec.2020', fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1161 configData['colorSpaces'].append(cs)
1163 print( "generateLUTs - end" )
1166 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1167 # Add the legal and full variations into this list
1168 odtInfoC = dict(odtInfo)
1169 for odtCTLName, odtValues in odtInfo.iteritems():
1170 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1171 'Academy.Rec709_100nits_dim.a1.0.0',
1172 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1173 odtName = odtValues["transformUserName"]
1175 odtValuesLegal = dict(odtValues)
1176 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1177 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1179 odtValuesFull = dict(odtValues)
1180 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1181 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1183 del( odtInfoC[odtCTLName] )
1185 for odtCTLName, odtValues in odtInfoC.iteritems():
1186 odtPrefix = odtValues["transformUserNamePrefix"]
1187 odtName = odtValues["transformUserName"]
1190 for inputspace in ["ACEScc", "ACESproxy"]:
1191 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1192 args += ["--outputspace", "%s" % odtName ]
1193 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1194 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1195 args += ["--cubesize", str(lutResolution3d) ]
1196 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1198 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1202 for inputspace in ["ACEScc", "ACESproxy"]:
1203 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1204 args += ["--outputspace", "%s" % odtName ]
1205 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1206 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1207 args += ["--cubesize", str(lutResolution3d) ]
1209 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1210 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1213 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1214 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1218 for inputspace in ["ACEScg", "ACES2065-1"]:
1219 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1220 args += ["--outputspace", "%s" % odtName ]
1221 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1222 if inputspace == 'ACEScg':
1223 linShaperName = "%s - AP1" % shaperName
1225 linShaperName = shaperName
1226 args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ]
1228 args += ["--cubesize", str(lutResolution3d) ]
1230 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
1231 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
1234 hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
1235 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
1239 def createConfigDir(configDir, bakeSecondaryLUTs):
1240 dirs = [configDir, "%s/luts" % configDir]
1241 if bakeSecondaryLUTs:
1242 dirs.extend(["%s/baked" % configDir,
1243 "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
1244 "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
1245 "%s/baked/maya" % configDir])
1248 if not os.path.exists(d):
1251 def getTransformInfo(ctlTransform):
1252 fp = open(ctlTransform, 'rb')
1255 lines = fp.readlines()
1257 # Grab transform ID and User Name
1258 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1259 #print( transformID )
1260 transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
1261 transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
1262 #print( transformUserName )
1265 return (transformID, transformUserName, transformUserNamePrefix)
1267 # For versions after WGR9
1268 def getODTInfo(acesCTLReleaseDir):
1269 # Credit to Alex Fry for the original approach here
1270 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1272 for dirName, subdirList, fileList in os.walk(odtDir):
1273 for fname in fileList:
1274 allodt.append((os.path.join(dirName,fname)))
1276 odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1282 for odtCTL in odtCTLs:
1283 odtTokens = os.path.split(odtCTL)
1286 # Handle nested directories
1287 odtPathTokens = os.path.split(odtTokens[-2])
1288 odtDir = odtPathTokens[-1]
1289 while odtPathTokens[-2][-3:] != 'odt':
1290 odtPathTokens = os.path.split(odtPathTokens[-2])
1291 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1294 #print( "odtDir : %s" % odtDir )
1295 transformCTL = odtTokens[-1]
1296 #print( transformCTL )
1297 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1300 # Find id, user name and user name prefix
1301 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1302 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
1305 transformCTLInverse = "InvODT.%s.ctl" % odtName
1306 if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
1307 transformCTLInverse = None
1308 #print( transformCTLInverse )
1310 # Add to list of ODTs
1312 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1313 if transformCTLInverse != None:
1314 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
1316 odts[odtName]['transformID'] = transformID
1317 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1318 odts[odtName]['transformUserName'] = transformUserName
1320 print( "ODT : %s" % odtName )
1321 print( "\tTransform ID : %s" % transformID )
1322 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1323 print( "\tTransform User Name : %s" % transformUserName )
1324 print( "\tForward ctl : %s" % odts[odtName]['transformCTL'])
1325 if 'transformCTLInverse' in odts[odtName]:
1326 print( "\tInverse ctl : %s" % odts[odtName]['transformCTLInverse'])
1328 print( "\tInverse ctl : %s" % "None" )
1334 # For versions after WGR9
1335 def getLMTInfo(acesCTLReleaseDir):
1336 # Credit to Alex Fry for the original approach here
1337 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1339 for dirName, subdirList, fileList in os.walk(lmtDir):
1340 for fname in fileList:
1341 alllmt.append((os.path.join(dirName,fname)))
1343 lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
1349 for lmtCTL in lmtCTLs:
1350 lmtTokens = os.path.split(lmtCTL)
1353 # Handle nested directories
1354 lmtPathTokens = os.path.split(lmtTokens[-2])
1355 lmtDir = lmtPathTokens[-1]
1356 while lmtPathTokens[-2][-3:] != 'ctl':
1357 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1358 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1361 #print( "lmtDir : %s" % lmtDir )
1362 transformCTL = lmtTokens[-1]
1363 #print( transformCTL )
1364 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1367 # Find id, user name and user name prefix
1368 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1369 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
1372 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1373 if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
1374 transformCTLInverse = None
1375 #print( transformCTLInverse )
1377 # Add to list of LMTs
1379 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1380 if transformCTLInverse != None:
1381 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
1383 lmts[lmtName]['transformID'] = transformID
1384 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1385 lmts[lmtName]['transformUserName'] = transformUserName
1387 print( "LMT : %s" % lmtName )
1388 print( "\tTransform ID : %s" % transformID )
1389 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1390 print( "\tTransform User Name : %s" % transformUserName )
1391 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1392 if 'transformCTLInverse' in lmts[lmtName]:
1393 print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1395 print( "\t Inverse ctl : %s" % "None" )
1402 # Create the ACES config
1404 def createACESConfig(acesCTLReleaseDir,
1406 lutResolution1d=4096,
1408 bakeSecondaryLUTs=True,
1411 # Get ODT names and CTL paths
1412 odtInfo = getODTInfo(acesCTLReleaseDir)
1414 # Get ODT names and CTL paths
1415 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1418 createConfigDir(configDir, bakeSecondaryLUTs)
1420 # Generate config data and LUTs for different transforms
1421 lutDir = "%s/luts" % configDir
1422 shaperName = 'Output Shaper'
1423 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
1425 # Create the config using the generated LUTs
1426 print( "Creating generic config")
1427 config = createConfig(configData)
1430 # Write the config to disk
1431 writeConfig(config, "%s/config.ocio" % configDir )
1433 # Create a config that will work well with Nuke using the previously generated LUTs
1434 print( "Creating Nuke-specific config")
1435 nuke_config = createConfig(configData, nuke=True)
1438 # Write the config to disk
1439 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
1441 # Bake secondary LUTs using the config
1442 if bakeSecondaryLUTs:
1443 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
1451 p = optparse.OptionParser(description='An OCIO config generation script',
1452 prog='createACESConfig',
1453 version='createACESConfig 0.1',
1454 usage='%prog [options]')
1455 p.add_option('--acesCTLDir', '-a', default=None)
1456 p.add_option('--configDir', '-c', default=None)
1457 p.add_option('--lutResolution1d', default=4096)
1458 p.add_option('--lutResolution3d', default=64)
1459 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1460 p.add_option('--keepTempImages', action="store_true")
1462 options, arguments = p.parse_args()
1467 acesCTLDir = options.acesCTLDir
1468 configDir = options.configDir
1469 lutResolution1d = int(options.lutResolution1d)
1470 lutResolution3d = int(options.lutResolution3d)
1471 bakeSecondaryLUTs = not(options.dontBakeSecondaryLUTs)
1472 cleanupTempImages = not(options.keepTempImages)
1475 argsStart = sys.argv.index('--') + 1
1476 args = sys.argv[argsStart:]
1478 argsStart = len(sys.argv)+1
1481 print( "command line : \n%s\n" % " ".join(sys.argv) )
1483 if configDir == None:
1484 print( "process: No ACES CTL directory specified" )
1488 # Generate the configuration
1490 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
1493 if __name__ == '__main__':