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 for colorspace in sorted(configData['colorSpaces']):
193 print( "Creating new color space : %s" % colorspace.name)
195 ocioColorspace = OCIO.ColorSpace( name=colorspace.name,
196 bitDepth=colorspace.bitDepth,
197 description=colorspace.description,
198 equalityGroup=colorspace.equalityGroup,
199 family=colorspace.family,
200 isData=colorspace.isData,
201 allocation=colorspace.allocationType,
202 allocationVars=colorspace.allocationVars )
204 if colorspace.toReferenceTransforms != []:
205 print( "Generating To-Reference transforms")
206 ocioTransform = generateOCIOTransform(colorspace.toReferenceTransforms)
207 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE )
209 if colorspace.fromReferenceTransforms != []:
210 print( "Generating From-Reference transforms")
211 ocioTransform = generateOCIOTransform(colorspace.fromReferenceTransforms)
212 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE )
214 config.addColorSpace(ocioColorspace)
219 # Define the views and displays
224 # Generic display and view setup
226 for display, viewList in configData['displays'].iteritems():
227 for viewName, colorspace in viewList.iteritems():
228 config.addDisplay( display, viewName, colorspace.name )
229 if not (viewName in views):
230 views.append(viewName)
231 displays.append(display)
232 # A Nuke specific set of views and displays
235 # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
238 for display, viewList in configData['displays'].iteritems():
239 for viewName, colorspace in viewList.iteritems():
240 if( viewName == 'Output Transform'):
242 config.addDisplay( display, viewName, colorspace.name )
243 if not (viewName in views):
244 views.append(viewName)
245 displays.append(display)
247 config.addDisplay( 'linear', 'View', 'ACES2065-1' )
248 displays.append('linear')
249 config.addDisplay( 'log', 'View', 'ACEScc' )
250 displays.append('log')
252 # Set active displays and views
253 config.setActiveDisplays( ','.join(sorted(displays)) )
254 config.setActiveViews( ','.join(views) )
257 # Need to generalize this at some point
261 setConfigDefaultRoles( config,
262 color_picking=reference.getName(),
263 color_timing=reference.getName(),
264 compositing_log=reference.getName(),
265 data=reference.getName(),
266 default=reference.getName(),
267 matte_paint=reference.getName(),
268 reference=reference.getName(),
269 scene_linear=reference.getName(),
270 texture_paint=reference.getName() )
272 # Check to make sure we didn't screw something up
278 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
281 # Output is a list of colorspaces and transforms that convert between those
282 # colorspaces and reference color space, ACES
283 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d=4096, lutResolution3d=64, cleanup=True):
284 print( "generateLUTs - begin" )
288 # Define the reference color space
290 ACES = ColorSpace('ACES2065-1')
291 ACES.description = "The Academy Color Encoding System reference color space"
292 ACES.equalityGroup = ''
295 ACES.allocationType=OCIO.Constants.ALLOCATION_LG2
296 ACES.allocationVars=[-15, 6]
298 configData['referenceColorSpace'] = ACES
301 # Define the displays
303 configData['displays'] = {}
306 # Define the other color spaces
308 configData['colorSpaces'] = []
310 # Matrix converting ACES AP1 primaries to AP0
311 acesAP1toAP0 = [ 0.6954522414, 0.1406786965, 0.1638690622,
312 0.0447945634, 0.8596711185, 0.0955343182,
313 -0.0055258826, 0.0040252103, 1.0015006723]
315 # Matrix converting ACES AP0 primaries to XYZ
316 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
317 0.3439664498, 0.7281660966, -0.0721325464,
318 0.0000000000, 0.0000000000, 1.0088251844]
323 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
324 cs = ColorSpace(name)
325 cs.description = "The %s color space" % name
326 cs.equalityGroup = ''
331 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
332 # This transform gets back to the AP1 primaries
333 # Useful as the 1d LUT is only covering the transfer function
334 # The primaries switch is covered by the matrix below
335 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
337 lut = "%s_to_ACES.spi1d" % name
339 # Remove spaces and parentheses
340 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
342 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
354 cs.toReferenceTransforms = []
355 cs.toReferenceTransforms.append( {
358 'interpolation':'linear',
359 'direction':'forward'
362 # AP1 primaries to AP0 primaries
363 cs.toReferenceTransforms.append( {
365 'matrix':mat44FromMat33(acesAP1toAP0),
366 'direction':'forward'
369 cs.fromReferenceTransforms = []
372 ACEScc = createACEScc()
373 configData['colorSpaces'].append(ACEScc)
378 def createACESProxy(name='ACESproxy'):
379 cs = ColorSpace(name)
380 cs.description = "The %s color space" % name
381 cs.equalityGroup = ''
386 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
387 # This transform gets back to the AP1 primaries
388 # Useful as the 1d LUT is only covering the transfer function
389 # The primaries switch is covered by the matrix below
390 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
392 lut = "%s_to_aces.spi1d" % name
394 # Remove spaces and parentheses
395 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
397 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
407 cs.toReferenceTransforms = []
408 cs.toReferenceTransforms.append( {
411 'interpolation':'linear',
412 'direction':'forward'
415 # AP1 primaries to AP0 primaries
416 cs.toReferenceTransforms.append( {
418 'matrix':mat44FromMat33(acesAP1toAP0),
419 'direction':'forward'
423 cs.fromReferenceTransforms = []
426 ACESproxy = createACESProxy()
427 configData['colorSpaces'].append(ACESproxy)
432 def createACEScg(name='ACEScg'):
433 cs = ColorSpace(name)
434 cs.description = "The %s color space" % name
435 cs.equalityGroup = ''
439 cs.toReferenceTransforms = []
441 # AP1 primaries to AP0 primaries
442 cs.toReferenceTransforms.append( {
444 'matrix':mat44FromMat33(acesAP1toAP0),
445 'direction':'forward'
448 cs.fromReferenceTransforms = []
451 ACEScg = createACEScg()
452 configData['colorSpaces'].append(ACEScg)
457 def createADX(bitdepth=10, name='ADX'):
458 name = "%s%s" % (name, bitdepth)
459 cs = ColorSpace(name)
460 cs.description = "%s color space - used for film scans" % name
461 cs.equalityGroup = ''
466 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
467 adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
468 0.0, 1023.0/500.0, 0.0, 0.0,
469 0.0, 0.0, 1023.0/500.0, 0.0,
471 offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
473 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
474 adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
475 0.0, 65535.0/8000.0, 0.0, 0.0,
476 0.0, 0.0, 65535.0/8000.0, 0.0,
478 offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
480 cs.toReferenceTransforms = []
482 # Convert from ADX to Channel-Dependent Density
483 cs.toReferenceTransforms.append( {
487 'direction':'forward'
490 # Convert from Channel-Dependent Density to Channel-Independent Density
491 cs.toReferenceTransforms.append( {
493 'matrix':[0.75573, 0.22197, 0.02230, 0,
494 0.05901, 0.96928, -0.02829, 0,
495 0.16134, 0.07406, 0.76460, 0,
497 'direction':'forward'
500 # Copied from Alex Fry's adx_cid_to_rle.py
501 def createCIDtoRLELUT():
502 def interpolate1D(x, xp, fp):
503 return numpy.interp(x, xp, fp)
505 LUT_1D_xp = [-0.190000000000000,
517 LUT_1D_fp = [-6.000000000000000,
529 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
533 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
534 return (100.0 / 55.0) * x - REF_PT
536 def Fit(value, fromMin, fromMax, toMin, toMax):
537 if fromMin == fromMax:
538 raise ValueError("fromMin == fromMax")
539 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
544 for i in xrange(NUM_SAMPLES):
545 x = i/(NUM_SAMPLES-1.0)
546 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
547 data.append(cid_to_rle(x))
549 lut = 'ADX_CID_to_RLE.spi1d'
550 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
554 # Convert Channel Independent Density values to Relative Log Exposure values
555 lut = createCIDtoRLELUT()
556 cs.toReferenceTransforms.append( {
559 'interpolation':'linear',
560 'direction':'forward'
563 # Convert Relative Log Exposure values to Relative Exposure values
564 cs.toReferenceTransforms.append( {
567 'direction':'inverse'
570 # Convert Relative Exposure values to ACES values
571 cs.toReferenceTransforms.append( {
573 'matrix':[0.72286, 0.12630, 0.15084, 0,
574 0.11923, 0.76418, 0.11659, 0,
575 0.01427, 0.08213, 0.90359, 0,
577 'direction':'forward'
580 cs.fromReferenceTransforms = []
583 ADX10 = createADX(bitdepth=10)
584 configData['colorSpaces'].append(ADX10)
586 ADX16 = createADX(bitdepth=16)
587 configData['colorSpaces'].append(ADX16)
590 # Camera Input Transforms
593 # RED color spaces to ACES
594 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
595 for cs in redColorSpaces:
596 configData['colorSpaces'].append(cs)
599 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
600 for cs in canonColorSpaces:
601 configData['colorSpaces'].append(cs)
604 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
605 for cs in sonyColorSpaces:
606 configData['colorSpaces'].append(cs)
609 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
610 for cs in arriColorSpaces:
611 configData['colorSpaces'].append(cs)
614 # Generic log transform
616 def createGenericLog(name='log',
623 lutResolution1d=lutResolution1d):
624 cs = ColorSpace(name)
625 cs.description = "The %s color space" % name
626 cs.equalityGroup = name
627 cs.family = 'Utility'
631 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
633 lut = "%s_to_aces.spi1d" % name
635 # Remove spaces and parentheses
636 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
638 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
645 'middleGrey' : middleGrey,
646 'minExposure' : minExposure,
647 'maxExposure' : maxExposure
654 cs.toReferenceTransforms = []
655 cs.toReferenceTransforms.append( {
658 'interpolation':'linear',
659 'direction':'forward'
662 cs.fromReferenceTransforms = []
668 def createACESLMT(lmtName,
671 lutResolution1d=1024,
674 cs = ColorSpace("%s" % lmtName)
675 cs.description = "The ACES Look Transform: %s" % lmtName
676 cs.equalityGroup = ''
681 pprint.pprint( lmtValues )
684 # Generate the shaper transform
686 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
688 shaperLut = "%s_to_aces.spi1d" % shaperName
689 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
691 shaperToACESCTL % acesCTLReleaseDir
694 # Remove spaces and parentheses
695 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace('(', '_')
697 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
701 1.0/shaperInputScale,
707 shaperOCIOTransform = {
710 'interpolation':'linear',
711 'direction':'inverse'
715 # Generate the forward transform
717 cs.fromReferenceTransforms = []
719 if 'transformCTL' in lmtValues:
721 shaperToACESCTL % acesCTLReleaseDir,
722 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
724 lut = "%s.%s.spi3d" % (shaperName, lmtName)
726 # Remove spaces and parentheses
727 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
729 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
733 1.0/shaperInputScale,
739 cs.fromReferenceTransforms.append( shaperOCIOTransform )
740 cs.fromReferenceTransforms.append( {
743 'interpolation':'tetrahedral',
744 'direction':'forward'
748 # Generate the inverse transform
750 cs.toReferenceTransforms = []
752 if 'transformCTLInverse' in lmtValues:
754 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
755 shaperFromACESCTL % acesCTLReleaseDir
757 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
759 # Remove spaces and parentheses
760 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
762 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
772 cs.toReferenceTransforms.append( {
775 'interpolation':'tetrahedral',
776 'direction':'forward'
779 shaperInverse = shaperOCIOTransform.copy()
780 shaperInverse['direction'] = 'forward'
781 cs.toReferenceTransforms.append( shaperInverse )
789 lmtLutResolution1d = max(4096, lutResolution1d)
790 lmtLutResolution3d = max(65, lutResolution3d)
793 lmtShaperName = 'LMT Shaper'
796 'minExposure' : -10.0,
799 lmtShaper = createGenericLog(name=lmtShaperName,
800 middleGrey=lmtParams['middleGrey'],
801 minExposure=lmtParams['minExposure'],
802 maxExposure=lmtParams['maxExposure'],
803 lutResolution1d=lmtLutResolution1d)
804 configData['colorSpaces'].append(lmtShaper)
806 shaperInputScale_genericLog2 = 1.0
808 # Log 2 shaper name and CTL transforms bundled up
811 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
812 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
813 shaperInputScale_genericLog2,
817 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
819 for lmt in sortedLMTs:
820 (lmtName, lmtValues) = lmt
822 lmtValues['transformUserName'],
828 configData['colorSpaces'].append(cs)
831 # ACES RRT with the supplied ODT
833 def createACESRRTplusODT(odtName,
836 lutResolution1d=1024,
839 cs = ColorSpace("%s" % odtName)
840 cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
841 cs.equalityGroup = ''
846 pprint.pprint( odtValues )
849 # Generate the shaper transform
851 #if 'shaperCTL' in odtValues:
852 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
854 if 'legalRange' in odtValues:
855 shaperParams['legalRange'] = odtValues['legalRange']
857 shaperParams['legalRange'] = 0
859 shaperLut = "%s_to_aces.spi1d" % shaperName
860 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
862 shaperToACESCTL % acesCTLReleaseDir
865 # Remove spaces and parentheses
866 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace('(', '_')
868 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
872 1.0/shaperInputScale,
878 shaperOCIOTransform = {
881 'interpolation':'linear',
882 'direction':'inverse'
886 # Generate the forward transform
888 cs.fromReferenceTransforms = []
890 if 'transformLUT' in odtValues:
891 # Copy into the lut dir
892 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
893 lut = lutDir + "/" + transformLUTFileName
894 shutil.copy(odtValues['transformLUT'], lut)
896 cs.fromReferenceTransforms.append( shaperOCIOTransform )
897 cs.fromReferenceTransforms.append( {
899 'path': transformLUTFileName,
900 'interpolation':'tetrahedral',
901 'direction':'forward'
903 elif 'transformCTL' in odtValues:
907 shaperToACESCTL % acesCTLReleaseDir,
908 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
909 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
911 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
913 # Remove spaces and parentheses
914 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
916 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
921 1.0/shaperInputScale,
927 cs.fromReferenceTransforms.append( shaperOCIOTransform )
928 cs.fromReferenceTransforms.append( {
931 'interpolation':'tetrahedral',
932 'direction':'forward'
936 # Generate the inverse transform
938 cs.toReferenceTransforms = []
940 if 'transformLUTInverse' in odtValues:
941 # Copy into the lut dir
942 transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
943 lut = lutDir + "/" + transformLUTInverseFileName
944 shutil.copy(odtValues['transformLUTInverse'], lut)
946 cs.toReferenceTransforms.append( {
948 'path': transformLUTInverseFileName,
949 'interpolation':'tetrahedral',
950 'direction':'forward'
953 shaperInverse = shaperOCIOTransform.copy()
954 shaperInverse['direction'] = 'forward'
955 cs.toReferenceTransforms.append( shaperInverse )
956 elif 'transformCTLInverse' in odtValues:
958 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
959 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
960 shaperFromACESCTL % acesCTLReleaseDir
962 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
964 # Remove spaces and parentheses
965 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
967 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
978 cs.toReferenceTransforms.append( {
981 'interpolation':'tetrahedral',
982 'direction':'forward'
985 shaperInverse = shaperOCIOTransform.copy()
986 shaperInverse['direction'] = 'forward'
987 cs.toReferenceTransforms.append( shaperInverse )
992 # RRT/ODT shaper options
997 log2ShaperName = shaperName
1000 'minExposure' : -6.0,
1003 log2Shaper = createGenericLog(name=log2ShaperName,
1004 middleGrey=log2Params['middleGrey'],
1005 minExposure=log2Params['minExposure'],
1006 maxExposure=log2Params['maxExposure'])
1007 configData['colorSpaces'].append(log2Shaper)
1009 shaperInputScale_genericLog2 = 1.0
1011 # Log 2 shaper name and CTL transforms bundled up
1014 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1015 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1016 shaperInputScale_genericLog2,
1020 shaperData[log2ShaperName] = log2ShaperData
1023 # Shaper that also includes the AP1 primaries
1024 # - Needed for some LUT baking steps
1026 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1027 middleGrey=log2Params['middleGrey'],
1028 minExposure=log2Params['minExposure'],
1029 maxExposure=log2Params['maxExposure'])
1030 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1031 # AP1 primaries to AP0 primaries
1032 log2ShaperAP1.toReferenceTransforms.append( {
1034 'matrix':mat44FromMat33(acesAP1toAP0),
1035 'direction':'forward'
1037 configData['colorSpaces'].append(log2ShaperAP1)
1040 # Choose your shaper
1042 rrtShaperName = log2ShaperName
1043 rrtShaper = log2ShaperData
1046 # RRT + ODT Combinations
1048 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1050 for odt in sortedOdts:
1051 (odtName, odtValues) = odt
1053 # Have to handle ODTs that can generate either legal or full output
1054 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1055 'Academy.Rec709_100nits_dim.a1.0.0',
1056 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1057 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1059 odtNameLegal = odtValues['transformUserName']
1061 odtLegal = odtValues.copy()
1062 odtLegal['legalRange'] = 1
1064 cs = createACESRRTplusODT(
1071 configData['colorSpaces'].append(cs)
1073 # Create a display entry using this color space
1074 configData['displays'][odtNameLegal] = {
1077 'Output Transform':cs }
1079 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1080 'Academy.Rec709_100nits_dim.a1.0.0',
1081 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1083 print( "Generating full range ODT for %s" % odtName)
1085 odtNameFull = "%s - Full" % odtValues['transformUserName']
1086 odtFull = odtValues.copy()
1087 odtFull['legalRange'] = 0
1089 csFull = createACESRRTplusODT(
1096 configData['colorSpaces'].append(csFull)
1098 # Create a display entry using this color space
1099 configData['displays'][odtNameFull] = {
1102 'Output Transform':csFull }
1105 # Generic Matrix transform
1107 def createGenericMatrix(name='matrix',
1108 fromReferenceValues=[],
1109 toReferenceValues=[]):
1110 cs = ColorSpace(name)
1111 cs.description = "The %s color space" % name
1112 cs.equalityGroup = name
1113 cs.family = 'Utility'
1116 cs.toReferenceTransforms = []
1117 if toReferenceValues != []:
1118 for matrix in toReferenceValues:
1119 cs.toReferenceTransforms.append( {
1121 'matrix':mat44FromMat33(matrix),
1122 'direction':'forward'
1125 cs.fromReferenceTransforms = []
1126 if fromReferenceValues != []:
1127 for matrix in fromReferenceValues:
1128 cs.fromReferenceTransforms.append( {
1130 'matrix':mat44FromMat33(matrix),
1131 'direction':'forward'
1136 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1137 configData['colorSpaces'].append(cs)
1139 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1140 configData['colorSpaces'].append(cs)
1142 # ACES to Linear, P3D60 primaries
1143 xyzToP3D60 = [ 2.4027414142, -0.8974841639, -0.3880533700,
1144 -0.8325796487, 1.7692317536, 0.0237127115,
1145 0.0388233815, -0.0824996856, 1.0363685997]
1147 cs = createGenericMatrix('Linear - P3-D60', fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1148 configData['colorSpaces'].append(cs)
1150 # ACES to Linear, P3D60 primaries
1151 xyzToP3DCI = [ 2.7253940305, -1.0180030062, -0.4401631952,
1152 -0.7951680258, 1.6897320548, 0.0226471906,
1153 0.0412418914, -0.0876390192, 1.1009293786]
1155 cs = createGenericMatrix('Linear - P3-DCI', fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1156 configData['colorSpaces'].append(cs)
1158 # ACES to Linear, Rec 709 primaries
1159 xyzToRec709 = [ 3.2409699419, -1.5373831776, -0.4986107603,
1160 -0.9692436363, 1.8759675015, 0.0415550574,
1161 0.0556300797, -0.2039769589, 1.0569715142]
1163 cs = createGenericMatrix('Linear - Rec.709', fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1164 configData['colorSpaces'].append(cs)
1166 # ACES to Linear, Rec 2020 primaries
1167 xyzToRec2020 = [ 1.7166511880, -0.3556707838, -0.2533662814,
1168 -0.6666843518, 1.6164812366, 0.0157685458,
1169 0.0176398574, -0.0427706133, 0.9421031212]
1171 cs = createGenericMatrix('Linear - Rec.2020', fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1172 configData['colorSpaces'].append(cs)
1174 print( "generateLUTs - end" )
1177 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1178 # Add the legal and full variations into this list
1179 odtInfoC = dict(odtInfo)
1180 for odtCTLName, odtValues in odtInfo.iteritems():
1181 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1182 'Academy.Rec709_100nits_dim.a1.0.0',
1183 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1184 odtName = odtValues["transformUserName"]
1186 odtValuesLegal = dict(odtValues)
1187 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1188 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1190 odtValuesFull = dict(odtValues)
1191 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1192 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1194 del( odtInfoC[odtCTLName] )
1196 for odtCTLName, odtValues in odtInfoC.iteritems():
1197 odtPrefix = odtValues["transformUserNamePrefix"]
1198 odtName = odtValues["transformUserName"]
1201 for inputspace in ["ACEScc", "ACESproxy"]:
1202 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1203 args += ["--outputspace", "%s" % odtName ]
1204 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1205 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1206 args += ["--cubesize", str(lutResolution3d) ]
1207 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1209 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1213 for inputspace in ["ACEScc", "ACESproxy"]:
1214 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1215 args += ["--outputspace", "%s" % odtName ]
1216 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1217 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1218 args += ["--cubesize", str(lutResolution3d) ]
1220 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1221 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1224 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1225 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1229 for inputspace in ["ACEScg", "ACES2065-1"]:
1230 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1231 args += ["--outputspace", "%s" % odtName ]
1232 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1233 if inputspace == 'ACEScg':
1234 linShaperName = "%s - AP1" % shaperName
1236 linShaperName = shaperName
1237 args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ]
1239 args += ["--cubesize", str(lutResolution3d) ]
1241 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
1242 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
1245 hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
1246 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
1250 def createConfigDir(configDir, bakeSecondaryLUTs):
1251 dirs = [configDir, "%s/luts" % configDir]
1252 if bakeSecondaryLUTs:
1253 dirs.extend(["%s/baked" % configDir,
1254 "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
1255 "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
1256 "%s/baked/maya" % configDir])
1259 if not os.path.exists(d):
1262 def getTransformInfo(ctlTransform):
1263 fp = open(ctlTransform, 'rb')
1266 lines = fp.readlines()
1268 # Grab transform ID and User Name
1269 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1270 #print( transformID )
1271 transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
1272 transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
1273 #print( transformUserName )
1276 return (transformID, transformUserName, transformUserNamePrefix)
1278 # For versions after WGR9
1279 def getODTInfo(acesCTLReleaseDir):
1280 # Credit to Alex Fry for the original approach here
1281 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1283 for dirName, subdirList, fileList in os.walk(odtDir):
1284 for fname in fileList:
1285 allodt.append((os.path.join(dirName,fname)))
1287 odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1293 for odtCTL in odtCTLs:
1294 odtTokens = os.path.split(odtCTL)
1297 # Handle nested directories
1298 odtPathTokens = os.path.split(odtTokens[-2])
1299 odtDir = odtPathTokens[-1]
1300 while odtPathTokens[-2][-3:] != 'odt':
1301 odtPathTokens = os.path.split(odtPathTokens[-2])
1302 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1305 #print( "odtDir : %s" % odtDir )
1306 transformCTL = odtTokens[-1]
1307 #print( transformCTL )
1308 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1311 # Find id, user name and user name prefix
1312 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1313 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
1316 transformCTLInverse = "InvODT.%s.ctl" % odtName
1317 if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
1318 transformCTLInverse = None
1319 #print( transformCTLInverse )
1321 # Add to list of ODTs
1323 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1324 if transformCTLInverse != None:
1325 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
1327 odts[odtName]['transformID'] = transformID
1328 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1329 odts[odtName]['transformUserName'] = transformUserName
1331 print( "ODT : %s" % odtName )
1332 print( "\tTransform ID : %s" % transformID )
1333 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1334 print( "\tTransform User Name : %s" % transformUserName )
1335 print( "\tForward ctl : %s" % odts[odtName]['transformCTL'])
1336 if 'transformCTLInverse' in odts[odtName]:
1337 print( "\tInverse ctl : %s" % odts[odtName]['transformCTLInverse'])
1339 print( "\tInverse ctl : %s" % "None" )
1345 # For versions after WGR9
1346 def getLMTInfo(acesCTLReleaseDir):
1347 # Credit to Alex Fry for the original approach here
1348 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1350 for dirName, subdirList, fileList in os.walk(lmtDir):
1351 for fname in fileList:
1352 alllmt.append((os.path.join(dirName,fname)))
1354 lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
1360 for lmtCTL in lmtCTLs:
1361 lmtTokens = os.path.split(lmtCTL)
1364 # Handle nested directories
1365 lmtPathTokens = os.path.split(lmtTokens[-2])
1366 lmtDir = lmtPathTokens[-1]
1367 while lmtPathTokens[-2][-3:] != 'ctl':
1368 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1369 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1372 #print( "lmtDir : %s" % lmtDir )
1373 transformCTL = lmtTokens[-1]
1374 #print( transformCTL )
1375 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1378 # Find id, user name and user name prefix
1379 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1380 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
1383 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1384 if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
1385 transformCTLInverse = None
1386 #print( transformCTLInverse )
1388 # Add to list of LMTs
1390 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1391 if transformCTLInverse != None:
1392 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
1394 lmts[lmtName]['transformID'] = transformID
1395 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1396 lmts[lmtName]['transformUserName'] = transformUserName
1398 print( "LMT : %s" % lmtName )
1399 print( "\tTransform ID : %s" % transformID )
1400 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1401 print( "\tTransform User Name : %s" % transformUserName )
1402 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1403 if 'transformCTLInverse' in lmts[lmtName]:
1404 print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1406 print( "\t Inverse ctl : %s" % "None" )
1413 # Create the ACES config
1415 def createACESConfig(acesCTLReleaseDir,
1417 lutResolution1d=4096,
1419 bakeSecondaryLUTs=True,
1422 # Get ODT names and CTL paths
1423 odtInfo = getODTInfo(acesCTLReleaseDir)
1425 # Get ODT names and CTL paths
1426 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1429 createConfigDir(configDir, bakeSecondaryLUTs)
1431 # Generate config data and LUTs for different transforms
1432 lutDir = "%s/luts" % configDir
1433 shaperName = 'Output Shaper'
1434 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
1436 # Create the config using the generated LUTs
1437 print( "Creating generic config")
1438 config = createConfig(configData)
1441 # Write the config to disk
1442 writeConfig(config, "%s/config.ocio" % configDir )
1444 # Create a config that will work well with Nuke using the previously generated LUTs
1445 print( "Creating Nuke-specific config")
1446 nuke_config = createConfig(configData, nuke=True)
1449 # Write the config to disk
1450 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
1452 # Bake secondary LUTs using the config
1453 if bakeSecondaryLUTs:
1454 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
1462 p = optparse.OptionParser(description='An OCIO config generation script',
1463 prog='createACESConfig',
1464 version='createACESConfig 0.1',
1465 usage='%prog [options]')
1466 p.add_option('--acesCTLDir', '-a', default=None)
1467 p.add_option('--configDir', '-c', default=None)
1468 p.add_option('--lutResolution1d', default=4096)
1469 p.add_option('--lutResolution3d', default=64)
1470 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1471 p.add_option('--keepTempImages', action="store_true")
1473 options, arguments = p.parse_args()
1478 acesCTLDir = options.acesCTLDir
1479 configDir = options.configDir
1480 lutResolution1d = int(options.lutResolution1d)
1481 lutResolution3d = int(options.lutResolution3d)
1482 bakeSecondaryLUTs = not(options.dontBakeSecondaryLUTs)
1483 cleanupTempImages = not(options.keepTempImages)
1486 argsStart = sys.argv.index('--') + 1
1487 args = sys.argv[argsStart:]
1489 argsStart = len(sys.argv)+1
1492 print( "command line : \n%s\n" % " ".join(sys.argv) )
1495 print( "process: No ACES CTL directory specified" )
1499 print( "process: No configuration directory specified" )
1502 # Generate the configuration
1504 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
1507 if __name__ == '__main__':