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
338 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
350 cs.toReferenceTransforms = []
351 cs.toReferenceTransforms.append( {
354 'interpolation':'linear',
355 'direction':'forward'
358 # AP1 primaries to AP0 primaries
359 cs.toReferenceTransforms.append( {
361 'matrix':mat44FromMat33(acesAP1toAP0),
362 'direction':'forward'
365 cs.fromReferenceTransforms = []
368 ACEScc = createACEScc()
369 configData['colorSpaces'].append(ACEScc)
374 def createACESProxy(name='ACESproxy'):
375 cs = ColorSpace(name)
376 cs.description = "The %s color space" % name
377 cs.equalityGroup = ''
382 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
383 # This transform gets back to the AP1 primaries
384 # Useful as the 1d LUT is only covering the transfer function
385 # The primaries switch is covered by the matrix below
386 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
388 lut = "%s_to_aces.spi1d" % name
389 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
399 cs.toReferenceTransforms = []
400 cs.toReferenceTransforms.append( {
403 'interpolation':'linear',
404 'direction':'forward'
407 # AP1 primaries to AP0 primaries
408 cs.toReferenceTransforms.append( {
410 'matrix':mat44FromMat33(acesAP1toAP0),
411 'direction':'forward'
415 cs.fromReferenceTransforms = []
418 ACESproxy = createACESProxy()
419 configData['colorSpaces'].append(ACESproxy)
424 def createACEScg(name='ACEScg'):
425 cs = ColorSpace(name)
426 cs.description = "The %s color space" % name
427 cs.equalityGroup = ''
431 cs.toReferenceTransforms = []
433 # AP1 primaries to AP0 primaries
434 cs.toReferenceTransforms.append( {
436 'matrix':mat44FromMat33(acesAP1toAP0),
437 'direction':'forward'
440 cs.fromReferenceTransforms = []
443 ACEScg = createACEScg()
444 configData['colorSpaces'].append(ACEScg)
449 def createADX(bitdepth=10, name='ADX'):
450 name = "%s%s" % (name, bitdepth)
451 cs = ColorSpace(name)
452 cs.description = "%s color space - used for film scans" % name
453 cs.equalityGroup = ''
458 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
459 adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
460 0.0, 1023.0/500.0, 0.0, 0.0,
461 0.0, 0.0, 1023.0/500.0, 0.0,
463 offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
465 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
466 adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
467 0.0, 65535.0/8000.0, 0.0, 0.0,
468 0.0, 0.0, 65535.0/8000.0, 0.0,
470 offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
472 cs.toReferenceTransforms = []
474 # Convert from ADX to Channel-Dependent Density
475 cs.toReferenceTransforms.append( {
479 'direction':'forward'
482 # Convert from Channel-Dependent Density to Channel-Independent Density
483 cs.toReferenceTransforms.append( {
485 'matrix':[0.75573, 0.22197, 0.02230, 0,
486 0.05901, 0.96928, -0.02829, 0,
487 0.16134, 0.07406, 0.76460, 0,
489 'direction':'forward'
492 # Copied from Alex Fry's adx_cid_to_rle.py
493 def createCIDtoRLELUT():
494 def interpolate1D(x, xp, fp):
495 return numpy.interp(x, xp, fp)
497 LUT_1D_xp = [-0.190000000000000,
509 LUT_1D_fp = [-6.000000000000000,
521 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
525 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
526 return (100.0 / 55.0) * x - REF_PT
528 def Fit(value, fromMin, fromMax, toMin, toMax):
529 if fromMin == fromMax:
530 raise ValueError("fromMin == fromMax")
531 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
536 for i in xrange(NUM_SAMPLES):
537 x = i/(NUM_SAMPLES-1.0)
538 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
539 data.append(cid_to_rle(x))
541 lut = 'ADX_CID_to_RLE.spi1d'
542 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
546 # Convert Channel Independent Density values to Relative Log Exposure values
547 lut = createCIDtoRLELUT()
548 cs.toReferenceTransforms.append( {
551 'interpolation':'linear',
552 'direction':'forward'
555 # Convert Relative Log Exposure values to Relative Exposure values
556 cs.toReferenceTransforms.append( {
559 'direction':'inverse'
562 # Convert Relative Exposure values to ACES values
563 cs.toReferenceTransforms.append( {
565 'matrix':[0.72286, 0.12630, 0.15084, 0,
566 0.11923, 0.76418, 0.11659, 0,
567 0.01427, 0.08213, 0.90359, 0,
569 'direction':'forward'
572 cs.fromReferenceTransforms = []
575 ADX10 = createADX(bitdepth=10)
576 configData['colorSpaces'].append(ADX10)
578 ADX16 = createADX(bitdepth=16)
579 configData['colorSpaces'].append(ADX16)
582 # Camera Input Transforms
585 # RED color spaces to ACES
586 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
587 for cs in redColorSpaces:
588 configData['colorSpaces'].append(cs)
591 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
592 for cs in canonColorSpaces:
593 configData['colorSpaces'].append(cs)
596 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
597 for cs in sonyColorSpaces:
598 configData['colorSpaces'].append(cs)
601 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
602 for cs in arriColorSpaces:
603 configData['colorSpaces'].append(cs)
606 # Generic log transform
608 def createGenericLog(name='log',
615 lutResolution1d=lutResolution1d):
616 cs = ColorSpace(name)
617 cs.description = "The %s color space" % name
618 cs.equalityGroup = name
619 cs.family = 'Utility'
623 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
625 lut = "%s_to_aces.spi1d" % name
627 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
634 'middleGrey' : middleGrey,
635 'minExposure' : minExposure,
636 'maxExposure' : maxExposure
643 cs.toReferenceTransforms = []
644 cs.toReferenceTransforms.append( {
647 'interpolation':'linear',
648 'direction':'forward'
651 cs.fromReferenceTransforms = []
657 def createACESLMT(lmtName,
660 lutResolution1d=1024,
663 cs = ColorSpace("%s" % lmtName)
664 cs.description = "The ACES Look Transform: %s" % lmtName
665 cs.equalityGroup = ''
670 pprint.pprint( lmtValues )
673 # Generate the shaper transform
675 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
677 shaperLut = "%s_to_aces.spi1d" % shaperName
678 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
680 shaperToACESCTL % acesCTLReleaseDir
682 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
686 1.0/shaperInputScale,
692 shaperOCIOTransform = {
695 'interpolation':'linear',
696 'direction':'inverse'
700 # Generate the forward transform
702 cs.fromReferenceTransforms = []
704 if 'transformCTL' in lmtValues:
706 shaperToACESCTL % acesCTLReleaseDir,
707 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
709 lut = "%s.%s.spi3d" % (shaperName, lmtName)
711 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
715 1.0/shaperInputScale,
721 cs.fromReferenceTransforms.append( shaperOCIOTransform )
722 cs.fromReferenceTransforms.append( {
725 'interpolation':'tetrahedral',
726 'direction':'forward'
730 # Generate the inverse transform
732 cs.toReferenceTransforms = []
734 if 'transformCTLInverse' in lmtValues:
736 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
737 shaperFromACESCTL % acesCTLReleaseDir
739 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
741 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
751 cs.toReferenceTransforms.append( {
754 'interpolation':'tetrahedral',
755 'direction':'forward'
758 shaperInverse = shaperOCIOTransform.copy()
759 shaperInverse['direction'] = 'forward'
760 cs.toReferenceTransforms.append( shaperInverse )
768 lmtLutResolution1d = max(4096, lutResolution1d)
769 lmtLutResolution3d = max(65, lutResolution3d)
772 lmtShaperName = 'LMT Shaper'
775 'minExposure' : -10.0,
778 lmtShaper = createGenericLog(name=lmtShaperName,
779 middleGrey=lmtParams['middleGrey'],
780 minExposure=lmtParams['minExposure'],
781 maxExposure=lmtParams['maxExposure'],
782 lutResolution1d=lmtLutResolution1d)
783 configData['colorSpaces'].append(lmtShaper)
785 shaperInputScale_genericLog2 = 1.0
787 # Log 2 shaper name and CTL transforms bundled up
790 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
791 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
792 shaperInputScale_genericLog2,
796 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
798 for lmt in sortedLMTs:
799 (lmtName, lmtValues) = lmt
801 lmtValues['transformUserName'],
807 configData['colorSpaces'].append(cs)
810 # ACES RRT with the supplied ODT
812 def createACESRRTplusODT(odtName,
815 lutResolution1d=1024,
818 cs = ColorSpace("%s" % odtName)
819 cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
820 cs.equalityGroup = ''
825 pprint.pprint( odtValues )
828 # Generate the shaper transform
830 #if 'shaperCTL' in odtValues:
831 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
833 if 'legalRange' in odtValues:
834 shaperParams['legalRange'] = odtValues['legalRange']
836 shaperParams['legalRange'] = 0
838 shaperLut = "%s_to_aces.spi1d" % shaperName
839 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
841 shaperToACESCTL % acesCTLReleaseDir
843 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
847 1.0/shaperInputScale,
853 shaperOCIOTransform = {
856 'interpolation':'linear',
857 'direction':'inverse'
861 # Generate the forward transform
863 cs.fromReferenceTransforms = []
865 if 'transformLUT' in odtValues:
866 # Copy into the lut dir
867 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
868 lut = lutDir + "/" + transformLUTFileName
869 shutil.copy(odtValues['transformLUT'], lut)
871 cs.fromReferenceTransforms.append( shaperOCIOTransform )
872 cs.fromReferenceTransforms.append( {
874 'path': transformLUTFileName,
875 'interpolation':'tetrahedral',
876 'direction':'forward'
878 elif 'transformCTL' in odtValues:
882 shaperToACESCTL % acesCTLReleaseDir,
883 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
884 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
886 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
888 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
893 1.0/shaperInputScale,
899 cs.fromReferenceTransforms.append( shaperOCIOTransform )
900 cs.fromReferenceTransforms.append( {
903 'interpolation':'tetrahedral',
904 'direction':'forward'
908 # Generate the inverse transform
910 cs.toReferenceTransforms = []
912 if 'transformLUTInverse' in odtValues:
913 # Copy into the lut dir
914 transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
915 lut = lutDir + "/" + transformLUTInverseFileName
916 shutil.copy(odtValues['transformLUTInverse'], lut)
918 cs.toReferenceTransforms.append( {
920 'path': transformLUTInverseFileName,
921 'interpolation':'tetrahedral',
922 'direction':'forward'
925 shaperInverse = shaperOCIOTransform.copy()
926 shaperInverse['direction'] = 'forward'
927 cs.toReferenceTransforms.append( shaperInverse )
928 elif 'transformCTLInverse' in odtValues:
930 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
931 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
932 shaperFromACESCTL % acesCTLReleaseDir
934 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
936 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
947 cs.toReferenceTransforms.append( {
950 'interpolation':'tetrahedral',
951 'direction':'forward'
954 shaperInverse = shaperOCIOTransform.copy()
955 shaperInverse['direction'] = 'forward'
956 cs.toReferenceTransforms.append( shaperInverse )
961 # RRT/ODT shaper options
966 log2ShaperName = shaperName
969 'minExposure' : -6.0,
972 log2Shaper = createGenericLog(name=log2ShaperName,
973 middleGrey=log2Params['middleGrey'],
974 minExposure=log2Params['minExposure'],
975 maxExposure=log2Params['maxExposure'])
976 configData['colorSpaces'].append(log2Shaper)
978 shaperInputScale_genericLog2 = 1.0
980 # Log 2 shaper name and CTL transforms bundled up
983 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
984 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
985 shaperInputScale_genericLog2,
989 shaperData[log2ShaperName] = log2ShaperData
992 # Shaper that also includes the AP1 primaries
993 # - Needed for some LUT baking steps
995 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
996 middleGrey=log2Params['middleGrey'],
997 minExposure=log2Params['minExposure'],
998 maxExposure=log2Params['maxExposure'])
999 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1000 # AP1 primaries to AP0 primaries
1001 log2ShaperAP1.toReferenceTransforms.append( {
1003 'matrix':mat44FromMat33(acesAP1toAP0),
1004 'direction':'forward'
1006 configData['colorSpaces'].append(log2ShaperAP1)
1009 # Choose your shaper
1011 rrtShaperName = log2ShaperName
1012 rrtShaper = log2ShaperData
1015 # RRT + ODT Combinations
1017 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1019 for odt in sortedOdts:
1020 (odtName, odtValues) = odt
1022 # Have to handle ODTs that can generate either legal or full output
1023 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1024 'Academy.Rec709_100nits_dim.a1.0.0',
1025 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1026 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1028 odtNameLegal = odtValues['transformUserName']
1030 odtLegal = odtValues.copy()
1031 odtLegal['legalRange'] = 1
1033 cs = createACESRRTplusODT(
1040 configData['colorSpaces'].append(cs)
1042 # Create a display entry using this color space
1043 configData['displays'][odtNameLegal] = {
1046 'Output Transform':cs }
1048 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1049 'Academy.Rec709_100nits_dim.a1.0.0',
1050 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1052 print( "Generating full range ODT for %s" % odtName)
1054 odtNameFull = "%s - Full" % odtValues['transformUserName']
1055 odtFull = odtValues.copy()
1056 odtFull['legalRange'] = 0
1058 csFull = createACESRRTplusODT(
1065 configData['colorSpaces'].append(csFull)
1067 # Create a display entry using this color space
1068 configData['displays'][odtNameFull] = {
1071 'Output Transform':csFull }
1074 # Generic Matrix transform
1076 def createGenericMatrix(name='matrix',
1077 fromReferenceValues=[],
1078 toReferenceValues=[]):
1079 cs = ColorSpace(name)
1080 cs.description = "The %s color space" % name
1081 cs.equalityGroup = name
1082 cs.family = 'Utility'
1085 cs.toReferenceTransforms = []
1086 if toReferenceValues != []:
1087 for matrix in toReferenceValues:
1088 cs.toReferenceTransforms.append( {
1090 'matrix':mat44FromMat33(matrix),
1091 'direction':'forward'
1094 cs.fromReferenceTransforms = []
1095 if fromReferenceValues != []:
1096 for matrix in fromReferenceValues:
1097 cs.fromReferenceTransforms.append( {
1099 'matrix':mat44FromMat33(matrix),
1100 'direction':'forward'
1105 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1106 configData['colorSpaces'].append(cs)
1108 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1109 configData['colorSpaces'].append(cs)
1111 # ACES to Linear, P3D60 primaries
1112 xyzToP3D60 = [ 2.4027414142, -0.8974841639, -0.3880533700,
1113 -0.8325796487, 1.7692317536, 0.0237127115,
1114 0.0388233815, -0.0824996856, 1.0363685997]
1116 cs = createGenericMatrix('Linear - P3-D60', fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1117 configData['colorSpaces'].append(cs)
1119 # ACES to Linear, P3D60 primaries
1120 xyzToP3DCI = [ 2.7253940305, -1.0180030062, -0.4401631952,
1121 -0.7951680258, 1.6897320548, 0.0226471906,
1122 0.0412418914, -0.0876390192, 1.1009293786]
1124 cs = createGenericMatrix('Linear - P3-DCI', fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1125 configData['colorSpaces'].append(cs)
1127 # ACES to Linear, Rec 709 primaries
1128 xyzToRec709 = [ 3.2409699419, -1.5373831776, -0.4986107603,
1129 -0.9692436363, 1.8759675015, 0.0415550574,
1130 0.0556300797, -0.2039769589, 1.0569715142]
1132 cs = createGenericMatrix('Linear - Rec.709', fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1133 configData['colorSpaces'].append(cs)
1135 # ACES to Linear, Rec 2020 primaries
1136 xyzToRec2020 = [ 1.7166511880, -0.3556707838, -0.2533662814,
1137 -0.6666843518, 1.6164812366, 0.0157685458,
1138 0.0176398574, -0.0427706133, 0.9421031212]
1140 cs = createGenericMatrix('Linear - Rec.2020', fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1141 configData['colorSpaces'].append(cs)
1143 print( "generateLUTs - end" )
1146 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1147 # Add the legal and full variations into this list
1148 odtInfoC = dict(odtInfo)
1149 for odtCTLName, odtValues in odtInfo.iteritems():
1150 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1151 'Academy.Rec709_100nits_dim.a1.0.0',
1152 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1153 odtName = odtValues["transformUserName"]
1155 odtValuesLegal = dict(odtValues)
1156 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1157 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1159 odtValuesFull = dict(odtValues)
1160 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1161 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1163 del( odtInfoC[odtCTLName] )
1165 for odtCTLName, odtValues in odtInfoC.iteritems():
1166 odtPrefix = odtValues["transformUserNamePrefix"]
1167 odtName = odtValues["transformUserName"]
1170 for inputspace in ["ACEScc", "ACESproxy"]:
1171 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1172 args += ["--outputspace", "%s" % odtName ]
1173 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1174 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1175 args += ["--cubesize", str(lutResolution3d) ]
1176 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1178 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1182 for inputspace in ["ACEScc", "ACESproxy"]:
1183 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1184 args += ["--outputspace", "%s" % odtName ]
1185 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1186 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1187 args += ["--cubesize", str(lutResolution3d) ]
1189 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1190 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1193 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1194 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1198 for inputspace in ["ACEScg", "ACES2065-1"]:
1199 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1200 args += ["--outputspace", "%s" % odtName ]
1201 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1202 if inputspace == 'ACEScg':
1203 linShaperName = "%s - AP1" % shaperName
1205 linShaperName = shaperName
1206 args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ]
1208 args += ["--cubesize", str(lutResolution3d) ]
1210 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
1211 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
1214 hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
1215 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
1219 def createConfigDir(configDir, bakeSecondaryLUTs):
1220 dirs = [configDir, "%s/luts" % configDir]
1221 if bakeSecondaryLUTs:
1222 dirs.extend(["%s/baked" % configDir,
1223 "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
1224 "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
1225 "%s/baked/maya" % configDir])
1228 if not os.path.exists(d):
1231 def getTransformInfo(ctlTransform):
1232 fp = open(ctlTransform, 'rb')
1235 lines = fp.readlines()
1237 # Grab transform ID and User Name
1238 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1239 #print( transformID )
1240 transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
1241 transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
1242 #print( transformUserName )
1245 return (transformID, transformUserName, transformUserNamePrefix)
1247 # For versions after WGR9
1248 def getODTInfo(acesCTLReleaseDir):
1249 # Credit to Alex Fry for the original approach here
1250 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1252 for dirName, subdirList, fileList in os.walk(odtDir):
1253 for fname in fileList:
1254 allodt.append((os.path.join(dirName,fname)))
1256 odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1262 for odtCTL in odtCTLs:
1263 odtTokens = os.path.split(odtCTL)
1266 # Handle nested directories
1267 odtPathTokens = os.path.split(odtTokens[-2])
1268 odtDir = odtPathTokens[-1]
1269 while odtPathTokens[-2][-3:] != 'odt':
1270 odtPathTokens = os.path.split(odtPathTokens[-2])
1271 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1274 #print( "odtDir : %s" % odtDir )
1275 transformCTL = odtTokens[-1]
1276 #print( transformCTL )
1277 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1280 # Find id, user name and user name prefix
1281 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1282 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
1285 transformCTLInverse = "InvODT.%s.ctl" % odtName
1286 if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
1287 transformCTLInverse = None
1288 #print( transformCTLInverse )
1290 # Add to list of ODTs
1292 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1293 if transformCTLInverse != None:
1294 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
1296 odts[odtName]['transformID'] = transformID
1297 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1298 odts[odtName]['transformUserName'] = transformUserName
1300 print( "ODT : %s" % odtName )
1301 print( "\tTransform ID : %s" % transformID )
1302 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1303 print( "\tTransform User Name : %s" % transformUserName )
1304 print( "\tForward ctl : %s" % odts[odtName]['transformCTL'])
1305 if 'transformCTLInverse' in odts[odtName]:
1306 print( "\tInverse ctl : %s" % odts[odtName]['transformCTLInverse'])
1308 print( "\tInverse ctl : %s" % "None" )
1314 # For versions after WGR9
1315 def getLMTInfo(acesCTLReleaseDir):
1316 # Credit to Alex Fry for the original approach here
1317 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1319 for dirName, subdirList, fileList in os.walk(lmtDir):
1320 for fname in fileList:
1321 alllmt.append((os.path.join(dirName,fname)))
1323 lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
1329 for lmtCTL in lmtCTLs:
1330 lmtTokens = os.path.split(lmtCTL)
1333 # Handle nested directories
1334 lmtPathTokens = os.path.split(lmtTokens[-2])
1335 lmtDir = lmtPathTokens[-1]
1336 while lmtPathTokens[-2][-3:] != 'ctl':
1337 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1338 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1341 #print( "lmtDir : %s" % lmtDir )
1342 transformCTL = lmtTokens[-1]
1343 #print( transformCTL )
1344 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1347 # Find id, user name and user name prefix
1348 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1349 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
1352 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1353 if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
1354 transformCTLInverse = None
1355 #print( transformCTLInverse )
1357 # Add to list of LMTs
1359 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1360 if transformCTLInverse != None:
1361 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
1363 lmts[lmtName]['transformID'] = transformID
1364 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1365 lmts[lmtName]['transformUserName'] = transformUserName
1367 print( "LMT : %s" % lmtName )
1368 print( "\tTransform ID : %s" % transformID )
1369 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1370 print( "\tTransform User Name : %s" % transformUserName )
1371 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1372 if 'transformCTLInverse' in lmts[lmtName]:
1373 print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1375 print( "\t Inverse ctl : %s" % "None" )
1382 # Create the ACES config
1384 def createACESConfig(acesCTLReleaseDir,
1386 lutResolution1d=4096,
1388 bakeSecondaryLUTs=True,
1391 # Get ODT names and CTL paths
1392 odtInfo = getODTInfo(acesCTLReleaseDir)
1394 # Get ODT names and CTL paths
1395 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1398 createConfigDir(configDir, bakeSecondaryLUTs)
1400 # Generate config data and LUTs for different transforms
1401 lutDir = "%s/luts" % configDir
1402 shaperName = 'Output Shaper'
1403 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
1405 # Create the config using the generated LUTs
1406 print( "Creating generic config")
1407 config = createConfig(configData)
1410 # Write the config to disk
1411 writeConfig(config, "%s/config.ocio" % configDir )
1413 # Create a config that will work well with Nuke using the previously generated LUTs
1414 print( "Creating Nuke-specific config")
1415 nuke_config = createConfig(configData, nuke=True)
1418 # Write the config to disk
1419 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
1421 # Bake secondary LUTs using the config
1422 if bakeSecondaryLUTs:
1423 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
1431 p = optparse.OptionParser(description='An OCIO config generation script',
1432 prog='createACESConfig',
1433 version='createACESConfig 0.1',
1434 usage='%prog [options]')
1435 p.add_option('--acesCTLDir', '-a', default=None)
1436 p.add_option('--configDir', '-c', default=None)
1437 p.add_option('--lutResolution1d', default=4096)
1438 p.add_option('--lutResolution3d', default=64)
1439 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1440 p.add_option('--keepTempImages', action="store_true")
1442 options, arguments = p.parse_args()
1447 acesCTLDir = options.acesCTLDir
1448 configDir = options.configDir
1449 lutResolution1d = int(options.lutResolution1d)
1450 lutResolution3d = int(options.lutResolution3d)
1451 bakeSecondaryLUTs = not(options.dontBakeSecondaryLUTs)
1452 cleanupTempImages = not(options.keepTempImages)
1455 argsStart = sys.argv.index('--') + 1
1456 args = sys.argv[argsStart:]
1458 argsStart = len(sys.argv)+1
1461 print( "command line : \n%s\n" % " ".join(sys.argv) )
1463 if configDir == None:
1464 print( "process: No ACES CTL directory specified" )
1468 # Generate the configuration
1470 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
1473 if __name__ == '__main__':