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
52 import generateLUT as genlut
57 def setConfigDefaultRoles( config,
69 if color_picking: config.setRole( OCIO.Constants.ROLE_COLOR_PICKING, color_picking )
70 if color_timing: config.setRole( OCIO.Constants.ROLE_COLOR_TIMING, color_timing )
71 if compositing_log: config.setRole( OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log )
72 if data: config.setRole( OCIO.Constants.ROLE_DATA, data )
73 if default: config.setRole( OCIO.Constants.ROLE_DEFAULT, default )
74 if matte_paint: config.setRole( OCIO.Constants.ROLE_MATTE_PAINT, matte_paint )
75 if reference: config.setRole( OCIO.Constants.ROLE_REFERENCE, reference )
76 if scene_linear: config.setRole( OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear )
77 if texture_paint: config.setRole( OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint )
79 # Write config to disk
80 def writeConfig( config, configPath, sanityCheck=True ):
86 print "Configuration was not written due to a failed Sanity Check"
90 fileHandle = open( configPath, mode='w' )
91 fileHandle.write( config.serialize() )
94 def generateOCIOTransform(transforms):
95 #print( "Generating transforms")
97 interpolationOptions = {
98 'linear':OCIO.Constants.INTERP_LINEAR,
99 'nearest':OCIO.Constants.INTERP_NEAREST,
100 'tetrahedral':OCIO.Constants.INTERP_TETRAHEDRAL
103 'forward':OCIO.Constants.TRANSFORM_DIR_FORWARD,
104 'inverse':OCIO.Constants.TRANSFORM_DIR_INVERSE
109 for transform in transforms:
110 if transform['type'] == 'lutFile':
112 ocioTransform = OCIO.FileTransform( src=transform['path'],
113 interpolation=interpolationOptions[transform['interpolation']],
114 direction=directionOptions[transform['direction']] )
116 ocioTransforms.append(ocioTransform)
117 elif transform['type'] == 'matrix':
118 ocioTransform = OCIO.MatrixTransform()
119 # MatrixTransform member variables can't be initialized directly. Each must be set individually
120 ocioTransform.setMatrix( transform['matrix'] )
122 if 'offset' in transform:
123 ocioTransform.setOffset( transform['offset'] )
124 if 'direction' in transform:
125 ocioTransform.setDirection( directionOptions[transform['direction']] )
127 ocioTransforms.append(ocioTransform)
128 elif transform['type'] == 'exponent':
129 ocioTransform = OCIO.ExponentTransform()
130 ocioTransform.setValue( transform['value'] )
132 ocioTransforms.append(ocioTransform)
133 elif transform['type'] == 'log':
134 ocioTransform = OCIO.LogTransform(base=transform['base'],
135 direction=directionOptions[transform['direction']])
137 ocioTransforms.append(ocioTransform)
139 print( "Ignoring unknown transform type : %s" % transform['type'] )
141 # Build a group transform if necessary
142 if len(ocioTransforms) > 1:
143 transformG = OCIO.GroupTransform()
144 for transform in ocioTransforms:
145 transformG.push_back( transform )
146 transform = transformG
148 # Or take the first transform from the list
150 transform = ocioTransforms[0]
154 def createConfig(configData, nuke=False):
156 config = OCIO.Config()
159 # Set config wide values
161 config.setDescription( "An ACES config generated from python" )
162 config.setSearchPath( "luts" )
165 # Define the reference color space
167 referenceData = configData['referenceColorSpace']
168 print( "Adding the reference color space : %s" % referenceData.name)
170 # Create a color space
171 reference = OCIO.ColorSpace( name=referenceData.name,
172 bitDepth=referenceData.bitDepth,
173 description=referenceData.description,
174 equalityGroup=referenceData.equalityGroup,
175 family=referenceData.family,
176 isData=referenceData.isData,
177 allocation=referenceData.allocationType,
178 allocationVars=referenceData.allocationVars )
181 config.addColorSpace( reference )
184 # Create the rest of the color spaces
186 #sortedColorspaces = sorted(configData['colorSpaces'], key=lambda x: x.name)
187 #print( sortedColorspaces )
188 #for colorspace in sortedColorspaces:
189 for colorspace in sorted(configData['colorSpaces']):
190 print( "Creating new color space : %s" % colorspace.name)
192 ocioColorspace = OCIO.ColorSpace( name=colorspace.name,
193 bitDepth=colorspace.bitDepth,
194 description=colorspace.description,
195 equalityGroup=colorspace.equalityGroup,
196 family=colorspace.family,
197 isData=colorspace.isData,
198 allocation=colorspace.allocationType,
199 allocationVars=colorspace.allocationVars )
201 if colorspace.toReferenceTransforms != []:
202 print( "Generating To-Reference transforms")
203 ocioTransform = generateOCIOTransform(colorspace.toReferenceTransforms)
204 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE )
206 if colorspace.fromReferenceTransforms != []:
207 print( "Generating From-Reference transforms")
208 ocioTransform = generateOCIOTransform(colorspace.fromReferenceTransforms)
209 ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE )
211 config.addColorSpace(ocioColorspace)
216 # Define the views and displays
221 # Generic display and view setup
223 for display, viewList in configData['displays'].iteritems():
224 for viewName, colorspace in viewList.iteritems():
225 config.addDisplay( display, viewName, colorspace.name )
226 if not (viewName in views):
227 views.append(viewName)
228 displays.append(display)
229 # A Nuke specific set of views and displays
232 # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
235 for display, viewList in configData['displays'].iteritems():
236 for viewName, colorspace in viewList.iteritems():
237 if( viewName == 'Output Transform'):
239 config.addDisplay( display, viewName, colorspace.name )
240 if not (viewName in views):
241 views.append(viewName)
242 displays.append(display)
244 config.addDisplay( 'linear', 'View', 'ACES2065-1' )
245 displays.append('linear')
246 config.addDisplay( 'log', 'View', 'ACEScc' )
247 displays.append('log')
249 # Set active displays and views
250 config.setActiveDisplays( ','.join(sorted(displays)) )
251 config.setActiveViews( ','.join(views) )
254 # Need to generalize this at some point
258 setConfigDefaultRoles( config,
259 color_picking=reference.getName(),
260 color_timing=reference.getName(),
261 compositing_log=reference.getName(),
262 data=reference.getName(),
263 default=reference.getName(),
264 matte_paint=reference.getName(),
265 reference=reference.getName(),
266 scene_linear=reference.getName(),
267 texture_paint=reference.getName() )
269 # Check to make sure we didn't screw something up
275 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
278 "A container for data needed to define an OCIO 'Color Space' "
283 bitDepth=OCIO.Constants.BIT_DEPTH_F32,
287 toReferenceTransforms=[],
288 fromReferenceTransforms=[],
289 allocationType=OCIO.Constants.ALLOCATION_UNIFORM,
290 allocationVars=[0.0, 1.0]):
291 "Initialize the standard class variables"
293 self.bitDepth=bitDepth
294 self.description = description
295 self.equalityGroup=equalityGroup
298 self.toReferenceTransforms=toReferenceTransforms
299 self.fromReferenceTransforms=fromReferenceTransforms
300 self.allocationType=allocationType
301 self.allocationVars=allocationVars
303 # Create a 4x4 matrix (list) based on a 3x3 matrix (list) input
304 def mat44FromMat33(mat33):
305 return [mat33[0], mat33[1], mat33[2], 0.0,
306 mat33[3], mat33[4], mat33[5], 0.0,
307 mat33[6], mat33[7], mat33[8], 0.0,
311 # Output is a list of colorspaces and transforms that convert between those
312 # colorspaces and reference color space, ACES
313 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d=4096, lutResolution3d=64, cleanup=True):
314 print( "generateLUTs - begin" )
318 # Define the reference color space
320 ACES = ColorSpace('ACES2065-1')
321 ACES.description = "The Academy Color Encoding System reference color space"
322 ACES.equalityGroup = ''
325 ACES.allocationType=OCIO.Constants.ALLOCATION_LG2
326 ACES.allocationVars=[-15, 6]
328 configData['referenceColorSpace'] = ACES
331 # Define the displays
333 configData['displays'] = {}
336 # Define the other color spaces
338 configData['colorSpaces'] = []
340 # Matrix converting ACES AP1 primaries to AP0
341 acesAP1toAP0 = [ 0.6954522414, 0.1406786965, 0.1638690622,
342 0.0447945634, 0.8596711185, 0.0955343182,
343 -0.0055258826, 0.0040252103, 1.0015006723]
345 # Matrix converting ACES AP0 primaries to XYZ
346 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
347 0.3439664498, 0.7281660966, -0.0721325464,
348 0.0000000000, 0.0000000000, 1.0088251844]
353 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
354 cs = ColorSpace(name)
355 cs.description = "The %s color space" % name
356 cs.equalityGroup = ''
361 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
362 # This transform gets back to the AP1 primaries
363 # Useful as the 1d LUT is only covering the transfer function
364 # The primaries switch is covered by the matrix below
365 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
367 lut = "%s_to_ACES.spi1d" % name
368 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
380 cs.toReferenceTransforms = []
381 cs.toReferenceTransforms.append( {
384 'interpolation':'linear',
385 'direction':'forward'
388 # AP1 primaries to AP0 primaries
389 cs.toReferenceTransforms.append( {
391 'matrix':mat44FromMat33(acesAP1toAP0),
392 'direction':'forward'
395 cs.fromReferenceTransforms = []
398 ACEScc = createACEScc()
399 configData['colorSpaces'].append(ACEScc)
404 def createACESProxy(name='ACESproxy'):
405 cs = ColorSpace(name)
406 cs.description = "The %s color space" % name
407 cs.equalityGroup = ''
412 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
413 # This transform gets back to the AP1 primaries
414 # Useful as the 1d LUT is only covering the transfer function
415 # The primaries switch is covered by the matrix below
416 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
418 lut = "%s_to_aces.spi1d" % name
419 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
429 cs.toReferenceTransforms = []
430 cs.toReferenceTransforms.append( {
433 'interpolation':'linear',
434 'direction':'forward'
437 # AP1 primaries to AP0 primaries
438 cs.toReferenceTransforms.append( {
440 'matrix':mat44FromMat33(acesAP1toAP0),
441 'direction':'forward'
445 cs.fromReferenceTransforms = []
448 ACESproxy = createACESProxy()
449 configData['colorSpaces'].append(ACESproxy)
454 def createACEScg(name='ACEScg'):
455 cs = ColorSpace(name)
456 cs.description = "The %s color space" % name
457 cs.equalityGroup = ''
461 cs.toReferenceTransforms = []
463 # AP1 primaries to AP0 primaries
464 cs.toReferenceTransforms.append( {
466 'matrix':mat44FromMat33(acesAP1toAP0),
467 'direction':'forward'
470 cs.fromReferenceTransforms = []
473 ACEScg = createACEScg()
474 configData['colorSpaces'].append(ACEScg)
479 def createADX(bitdepth=10, name='ADX'):
480 name = "%s%s" % (name, bitdepth)
481 cs = ColorSpace(name)
482 cs.description = "%s color space - used for film scans" % name
483 cs.equalityGroup = ''
488 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
489 adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
490 0.0, 1023.0/500.0, 0.0, 0.0,
491 0.0, 0.0, 1023.0/500.0, 0.0,
493 offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
495 cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
496 adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
497 0.0, 65535.0/8000.0, 0.0, 0.0,
498 0.0, 0.0, 65535.0/8000.0, 0.0,
500 offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
502 cs.toReferenceTransforms = []
504 # Convert from ADX to Channel-Dependent Density
505 cs.toReferenceTransforms.append( {
509 'direction':'forward'
512 # Convert from Channel-Dependent Density to Channel-Independent Density
513 cs.toReferenceTransforms.append( {
515 'matrix':[0.75573, 0.22197, 0.02230, 0,
516 0.05901, 0.96928, -0.02829, 0,
517 0.16134, 0.07406, 0.76460, 0,
519 'direction':'forward'
522 # Copied from Alex Fry's adx_cid_to_rle.py
523 def createCIDtoRLELUT():
524 def interpolate1D(x, xp, fp):
525 return numpy.interp(x, xp, fp)
527 LUT_1D_xp = [-0.190000000000000,
539 LUT_1D_fp = [-6.000000000000000,
551 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
555 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
556 return (100.0 / 55.0) * x - REF_PT
558 def Fit(value, fromMin, fromMax, toMin, toMax):
559 if fromMin == fromMax:
560 raise ValueError("fromMin == fromMax")
561 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
566 for i in xrange(NUM_SAMPLES):
567 x = i/(NUM_SAMPLES-1.0)
568 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
569 data.append(cid_to_rle(x))
571 lut = 'ADX_CID_to_RLE.spi1d'
572 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
576 # Convert Channel Independent Density values to Relative Log Exposure values
577 lut = createCIDtoRLELUT()
578 cs.toReferenceTransforms.append( {
581 'interpolation':'linear',
582 'direction':'forward'
585 # Convert Relative Log Exposure values to Relative Exposure values
586 cs.toReferenceTransforms.append( {
589 'direction':'inverse'
592 # Convert Relative Exposure values to ACES values
593 cs.toReferenceTransforms.append( {
595 'matrix':[0.72286, 0.12630, 0.15084, 0,
596 0.11923, 0.76418, 0.11659, 0,
597 0.01427, 0.08213, 0.90359, 0,
599 'direction':'forward'
602 cs.fromReferenceTransforms = []
605 ADX10 = createADX(bitdepth=10)
606 configData['colorSpaces'].append(ADX10)
608 ADX16 = createADX(bitdepth=16)
609 configData['colorSpaces'].append(ADX16)
615 def createREDlogFilm(gamut, transferFunction, name='REDlogFilm'):
616 name = "%s - %s" % (transferFunction, gamut)
617 if transferFunction == "":
618 name = "Linear - %s" % gamut
620 name = "%s" % transferFunction
622 cs = ColorSpace(name)
623 cs.description = name
624 cs.equalityGroup = ''
628 def cineonToLinear(codeValue):
632 codeValueToDensity = 0.002
634 blackLinear = pow(10.0, (blackPoint - whitePoint) * (codeValueToDensity / nGamma))
635 codeLinear = pow(10.0, (codeValue - whitePoint) * (codeValueToDensity / nGamma))
637 return (codeLinear - blackLinear)/(1.0 - blackLinear)
639 cs.toReferenceTransforms = []
641 if transferFunction == 'REDlogFilm':
642 data = array.array('f', "\0" * lutResolution1d * 4)
643 for c in range(lutResolution1d):
644 data[c] = cineonToLinear(1023.0*c/(lutResolution1d-1))
646 lut = "CineonLog_to_linear.spi1d"
647 genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
649 cs.toReferenceTransforms.append( {
652 'interpolation':'linear',
653 'direction':'forward'
656 if gamut == 'DRAGONcolor':
657 cs.toReferenceTransforms.append( {
659 'matrix':mat44FromMat33([0.532279, 0.376648, 0.091073,
660 0.046344, 0.974513, -0.020860,
661 -0.053976, -0.000320, 1.054267]),
662 'direction':'forward'
664 elif gamut == 'DRAGONcolor2':
665 cs.toReferenceTransforms.append( {
667 'matrix':mat44FromMat33([0.468452, 0.331484, 0.200064,
668 0.040787, 0.857658, 0.101553,
669 -0.047504, -0.000282, 1.047756]),
670 'direction':'forward'
672 elif gamut == 'REDcolor2':
673 cs.toReferenceTransforms.append( {
675 'matrix':mat44FromMat33([0.480997, 0.402289, 0.116714,
676 -0.004938, 1.000154, 0.004781,
677 -0.105257, 0.025320, 1.079907]),
678 'direction':'forward'
680 elif gamut == 'REDcolor3':
681 cs.toReferenceTransforms.append( {
683 'matrix':mat44FromMat33([0.512136, 0.360370, 0.127494,
684 0.070377, 0.903884, 0.025737,
685 -0.020824, 0.017671, 1.003123]),
686 'direction':'forward'
688 elif gamut == 'REDcolor4':
689 cs.toReferenceTransforms.append( {
691 'matrix':mat44FromMat33([0.474202, 0.333677, 0.192121,
692 0.065164, 0.836932, 0.097901,
693 -0.019281, 0.016362, 1.002889]),
694 'direction':'forward'
697 cs.fromReferenceTransforms = []
701 REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "REDlogFilm", name="REDlogFilm")
702 configData['colorSpaces'].append(REDlogFilmDRAGON)
704 REDlogFilmDRAGON2 = createREDlogFilm("DRAGONcolor2", "REDlogFilm", name="REDlogFilm")
705 configData['colorSpaces'].append(REDlogFilmDRAGON2)
707 REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "REDlogFilm", name="REDlogFilm")
708 configData['colorSpaces'].append(REDlogFilmREDcolor2)
710 REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "REDlogFilm", name="REDlogFilm")
711 configData['colorSpaces'].append(REDlogFilmREDcolor3)
713 REDlogFilmREDcolor4 = createREDlogFilm("REDcolor4", "REDlogFilm", name="REDlogFilm")
714 configData['colorSpaces'].append(REDlogFilmREDcolor4)
717 REDlogFilmDRAGON = createREDlogFilm("", "REDlogFilm", name="REDlogFilm")
718 configData['colorSpaces'].append(REDlogFilmDRAGON)
721 REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "", name="REDlogFilm")
722 configData['colorSpaces'].append(REDlogFilmDRAGON)
724 REDlogFilmDRAGON2 = createREDlogFilm("DRAGONcolor2", "", name="REDlogFilm")
725 configData['colorSpaces'].append(REDlogFilmDRAGON2)
727 REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "", name="REDlogFilm")
728 configData['colorSpaces'].append(REDlogFilmREDcolor2)
730 REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "", name="REDlogFilm")
731 configData['colorSpaces'].append(REDlogFilmREDcolor3)
733 REDlogFilmREDcolor4 = createREDlogFilm("REDcolor4", "", name="REDlogFilm")
734 configData['colorSpaces'].append(REDlogFilmREDcolor4)
739 def createCanonLog(gamut, transferFunction, name='Canon-Log'):
740 name = "%s - %s" % (transferFunction, gamut)
741 if transferFunction == "":
742 name = "Linear - %s" % gamut
744 name = "%s" % transferFunction
746 cs = ColorSpace(name)
747 cs.description = name
748 cs.equalityGroup = ''
752 def legalToFull(codeValue):
753 return (codeValue - 64.0)/(940.0 - 64.0)
755 def canonLogToLinear(codeValue):
756 # log = fullToLegal(c1 * log10(c2*linear + 1) + c3)
757 # linear = (pow(10, (legalToFul(log) - c3)/c1) - 1)/c2
762 linear = (pow(10.0, (legalToFull(codeValue) - c3)/c1) -1.0)/c2
763 linear = 0.9 * linear
764 #print( codeValue, linear )
767 cs.toReferenceTransforms = []
769 if transferFunction == "Canon-Log":
770 data = array.array('f', "\0" * lutResolution1d * 4)
771 for c in range(lutResolution1d):
772 data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1))
774 lut = "%s_to_linear.spi1d" % transferFunction
775 genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
777 cs.toReferenceTransforms.append( {
780 'interpolation':'linear',
781 'direction':'forward'
784 if gamut == 'Rec. 709 Daylight':
785 cs.toReferenceTransforms.append( {
787 'matrix':[0.561538969, 0.402060105, 0.036400926, 0.0,
788 0.092739623, 0.924121198, -0.016860821, 0.0,
789 0.084812961, 0.006373835, 0.908813204, 0.0,
791 'direction':'forward'
793 elif gamut == 'Rec. 709 Tungsten':
794 cs.toReferenceTransforms.append( {
796 'matrix':[0.566996399, 0.365079418, 0.067924183, 0.0,
797 0.070901044, 0.880331008, 0.048767948, 0.0,
798 0.073013542, -0.066540862, 0.99352732, 0.0,
800 'direction':'forward'
802 elif gamut == 'DCI-P3 Daylight':
803 cs.toReferenceTransforms.append( {
805 'matrix':[0.607160575, 0.299507286, 0.093332140, 0.0,
806 0.004968120, 1.050982224, -0.055950343, 0.0,
807 -0.007839939, 0.000809127, 1.007030813, 0.0,
809 'direction':'forward'
811 elif gamut == 'DCI-P3 Tungsten':
812 cs.toReferenceTransforms.append( {
814 'matrix':[0.650279125, 0.253880169, 0.095840706, 0.0,
815 -0.026137986, 1.017900530, 0.008237456, 0.0,
816 0.007757558, -0.063081669, 1.055324110, 0.0,
818 'direction':'forward'
820 elif gamut == 'Cinema Gamut Daylight':
821 cs.toReferenceTransforms.append( {
823 'matrix':[0.763064455, 0.149021161, 0.087914384, 0.0,
824 0.003657457, 1.10696038, -0.110617837, 0.0,
825 -0.009407794,-0.218383305, 1.227791099, 0.0,
827 'direction':'forward'
829 elif gamut == 'Cinema Gamut Tungsten':
830 cs.toReferenceTransforms.append( {
832 'matrix':[0.817416293, 0.090755698, 0.091828009, 0.0,
833 -0.035361374, 1.065690585, -0.030329211, 0.0,
834 0.010390366, -0.299271107, 1.288880741, 0.0,
836 'direction':'forward'
839 cs.fromReferenceTransforms = []
843 CanonLog1 = createCanonLog("Rec. 709 Daylight", "Canon-Log", name="Canon-Log")
844 configData['colorSpaces'].append(CanonLog1)
846 CanonLog2 = createCanonLog("Rec. 709 Tungsten", "Canon-Log", name="Canon-Log")
847 configData['colorSpaces'].append(CanonLog2)
849 CanonLog3 = createCanonLog("DCI-P3 Daylight", "Canon-Log", name="Canon-Log")
850 configData['colorSpaces'].append(CanonLog3)
852 CanonLog4 = createCanonLog("DCI-P3 Tungsten", "Canon-Log", name="Canon-Log")
853 configData['colorSpaces'].append(CanonLog4)
855 CanonLog5 = createCanonLog("Cinema Gamut Daylight", "Canon-Log", name="Canon-Log")
856 configData['colorSpaces'].append(CanonLog5)
858 CanonLog6 = createCanonLog("Cinema Gamut Tungsten", "Canon-Log", name="Canon-Log")
859 configData['colorSpaces'].append(CanonLog6)
862 CanonLog7 = createCanonLog('', "Canon-Log", name="Canon-Log")
863 configData['colorSpaces'].append(CanonLog7)
866 CanonLog8 = createCanonLog("Rec. 709 Daylight", "", name="Canon-Log")
867 configData['colorSpaces'].append(CanonLog8)
869 CanonLog9 = createCanonLog("Rec. 709 Tungsten", "", name="Canon-Log")
870 configData['colorSpaces'].append(CanonLog9)
872 CanonLog10 = createCanonLog("DCI-P3 Daylight", "", name="Canon-Log")
873 configData['colorSpaces'].append(CanonLog10)
875 CanonLog11 = createCanonLog("DCI-P3 Tungsten", "", name="Canon-Log")
876 configData['colorSpaces'].append(CanonLog11)
878 CanonLog12 = createCanonLog("Cinema Gamut Daylight", "", name="Canon-Log")
879 configData['colorSpaces'].append(CanonLog12)
881 CanonLog13 = createCanonLog("Cinema Gamut Tungsten", "", name="Canon-Log")
882 configData['colorSpaces'].append(CanonLog13)
887 def createSlog(gamut, transferFunction, name='S-Log3'):
888 name = "%s - %s" % (transferFunction, gamut)
889 if transferFunction == "":
890 name = "Linear - %s" % gamut
892 name = "%s" % transferFunction
894 cs = ColorSpace(name)
895 cs.description = name
896 cs.equalityGroup = ''
900 def sLog1ToLinear(SLog):
906 lin = ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) * 0.9
908 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 5.) * 0.9
911 def sLog2ToLinear(SLog):
917 lin = ( 219. * ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) / 155.) * 0.9
919 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 3.53881278538813) * 0.9
922 def sLog3ToLinear(codeValue):
923 if codeValue >= (171.2102946929):
924 linear = pow(10.0, ((codeValue - 420.0) / 261.5)) * (0.18 + 0.01) - 0.01
926 linear = (codeValue - 95.0)*0.01125000/(171.2102946929 - 95.0)
927 #print( codeValue, linear )
930 cs.toReferenceTransforms = []
932 if transferFunction == "S-Log1":
933 data = array.array('f', "\0" * lutResolution1d * 4)
934 for c in range(lutResolution1d):
935 data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1))
937 lut = "%s_to_linear.spi1d" % transferFunction
938 genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
940 #print( "Writing %s" % lut)
942 cs.toReferenceTransforms.append( {
945 'interpolation':'linear',
946 'direction':'forward'
948 elif transferFunction == "S-Log2":
949 data = array.array('f', "\0" * lutResolution1d * 4)
950 for c in range(lutResolution1d):
951 data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1))
953 lut = "%s_to_linear.spi1d" % transferFunction
954 genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
956 #print( "Writing %s" % lut)
958 cs.toReferenceTransforms.append( {
961 'interpolation':'linear',
962 'direction':'forward'
964 elif transferFunction == "S-Log3":
965 data = array.array('f', "\0" * lutResolution1d * 4)
966 for c in range(lutResolution1d):
967 data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1))
969 lut = "%s_to_linear.spi1d" % transferFunction
970 genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
972 #print( "Writing %s" % lut)
974 cs.toReferenceTransforms.append( {
977 'interpolation':'linear',
978 'direction':'forward'
981 if gamut == 'S-Gamut':
982 cs.toReferenceTransforms.append( {
984 'matrix':mat44FromMat33([0.754338638, 0.133697046, 0.111968437,
985 0.021198141, 1.005410934, -0.026610548,
986 -0.009756991, 0.004508563, 1.005253201]),
987 'direction':'forward'
989 elif gamut == 'S-Gamut Daylight':
990 cs.toReferenceTransforms.append( {
992 'matrix':mat44FromMat33([0.8764457030, 0.0145411681, 0.1090131290,
993 0.0774075345, 0.9529571767, -0.0303647111,
994 0.0573564351, -0.1151066335, 1.0577501984]),
995 'direction':'forward'
997 elif gamut == 'S-Gamut Tungsten':
998 cs.toReferenceTransforms.append( {
1000 'matrix':mat44FromMat33([1.0110238740, -0.1362526051, 0.1252287310,
1001 0.1011994504, 0.9562196265, -0.0574190769,
1002 0.0600766530, -0.1010185315, 1.0409418785]),
1003 'direction':'forward'
1005 elif gamut == 'S-Gamut3.Cine':
1006 cs.toReferenceTransforms.append( {
1008 'matrix':mat44FromMat33([0.6387886672, 0.2723514337, 0.0888598992,
1009 -0.0039159061, 1.0880732308, -0.0841573249,
1010 -0.0299072021, -0.0264325799, 1.0563397820]),
1011 'direction':'forward'
1013 elif gamut == 'S-Gamut3':
1014 cs.toReferenceTransforms.append( {
1016 'matrix':mat44FromMat33([0.7529825954, 0.1433702162, 0.1036471884,
1017 0.0217076974, 1.0153188355, -0.0370265329,
1018 -0.0094160528, 0.0033704179, 1.0060456349]),
1019 'direction':'forward'
1022 cs.fromReferenceTransforms = []
1026 SLog1SGamut = createSlog("S-Gamut", "S-Log1", name="S-Log")
1027 configData['colorSpaces'].append(SLog1SGamut)
1030 SLog2SGamut = createSlog("S-Gamut", "S-Log2", name="S-Log2")
1031 configData['colorSpaces'].append(SLog2SGamut)
1033 SLog2SGamutDaylight = createSlog("S-Gamut Daylight", "S-Log2", name="S-Log2")
1034 configData['colorSpaces'].append(SLog2SGamutDaylight)
1036 SLog2SGamutTungsten = createSlog("S-Gamut Tungsten", "S-Log2", name="S-Log2")
1037 configData['colorSpaces'].append(SLog2SGamutTungsten)
1040 SLog3SGamut3Cine = createSlog("S-Gamut3.Cine", "S-Log3", name="S-Log3")
1041 configData['colorSpaces'].append(SLog3SGamut3Cine)
1043 SLog3SGamut3 = createSlog("S-Gamut3", "S-Log3", name="S-Log3")
1044 configData['colorSpaces'].append(SLog3SGamut3)
1046 # Linearization only
1047 SLog1 = createSlog("", "S-Log1", name="S-Log")
1048 configData['colorSpaces'].append(SLog1)
1050 SLog2 = createSlog("", "S-Log2", name="S-Log2")
1051 configData['colorSpaces'].append(SLog2)
1053 SLog3 = createSlog("", "S-Log3", name="S-Log3")
1054 configData['colorSpaces'].append(SLog3)
1057 SGamut = createSlog("S-Gamut", "", name="S-Log")
1058 configData['colorSpaces'].append(SGamut)
1060 SGamutDaylight = createSlog("S-Gamut Daylight", "", name="S-Log2")
1061 configData['colorSpaces'].append(SGamutDaylight)
1063 SGamutTungsten = createSlog("S-Gamut Tungsten", "", name="S-Log2")
1064 configData['colorSpaces'].append(SGamutTungsten)
1066 SGamut3Cine = createSlog("S-Gamut3.Cine", "", name="S-Log3")
1067 configData['colorSpaces'].append(SGamut3Cine)
1069 SGamut3 = createSlog("S-Gamut3", "", name="S-Log3")
1070 configData['colorSpaces'].append(SGamut3)
1075 def createLogC(gamut, transferFunction, exposureIndex, name='LogC'):
1076 name = "%s (EI%s) - %s" % (transferFunction, exposureIndex, gamut)
1077 if transferFunction == "":
1078 name = "Linear - %s" % gamut
1080 name = "%s (EI%s)" % (transferFunction, exposureIndex)
1082 cs = ColorSpace(name)
1083 cs.description = name
1084 cs.equalityGroup = ''
1089 IDT_maker_version = "0.08"
1092 blackSignal = 0.003907
1093 midGraySignal = 0.01
1094 encodingGain = 0.256598
1095 encodingOffset = 0.391007
1098 return (math.log(EI/nominalEI)/math.log(2) * (0.89 - 1) / 3 + 1) * encodingGain
1100 def LogCInverseParametersForEI(EI) :
1102 slope = 1.0 / (cut * math.log(10))
1103 offset = math.log10(cut) - slope * cut
1104 gain = EI / nominalEI
1105 gray = midGraySignal / gain
1106 # The higher the EI, the lower the gamma
1107 encGain = gainForEI(EI)
1108 encOffset = encodingOffset
1109 for i in range(0,3) :
1110 nz = ((95.0 / 1023.0 - encOffset) / encGain - offset) / slope
1111 encOffset = encodingOffset - math.log10(1 + nz) * encGain
1112 # Calculate some intermediate values
1114 b = nz - blackSignal / gray
1115 e = slope * a * encGain
1116 f = encGain * (slope * b + offset) + encOffset
1117 # Manipulations so we can return relative exposure
1126 'cut' : (cut - b) / a,
1132 def logCtoLinear(codeValue, exposureIndex):
1133 p = LogCInverseParametersForEI(exposureIndex)
1134 breakpoint = p['e'] * p['cut'] + p['f']
1135 if (codeValue > breakpoint):
1136 linear = (pow(10,(codeValue/1023.0 - p['d']) / p['c']) - p['b']) / p['a']
1138 linear = (codeValue/1023.0 - p['f']) / p['e']
1140 #print( codeValue, linear )
1144 cs.toReferenceTransforms = []
1146 if transferFunction == "V3 LogC":
1147 data = array.array('f', "\0" * lutResolution1d * 4)
1148 for c in range(lutResolution1d):
1149 data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex))
1151 lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex))
1152 genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1154 #print( "Writing %s" % lut)
1155 cs.toReferenceTransforms.append( {
1158 'interpolation':'linear',
1159 'direction':'forward'
1162 if gamut == 'Wide Gamut':
1163 cs.toReferenceTransforms.append( {
1165 'matrix':mat44FromMat33([0.680206, 0.236137, 0.083658,
1166 0.085415, 1.017471, -0.102886,
1167 0.002057, -0.062563, 1.060506]),
1168 'direction':'forward'
1171 cs.fromReferenceTransforms = []
1174 transferFunction = "V3 LogC"
1175 gamut = "Wide Gamut"
1176 #EIs = [160.0, 200.0, 250.0, 320.0, 400.0, 500.0, 640.0, 800.0, 1000.0, 1280.0, 1600.0, 2000.0, 2560.0, 3200.0]
1177 EIs = [160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600, 2000, 2560, 3200]
1182 LogCEIfull = createLogC(gamut, transferFunction, EI, name="LogC")
1183 configData['colorSpaces'].append(LogCEIfull)
1185 # Linearization only
1187 LogCEIlinearization = createLogC("", transferFunction, EI, name="LogC")
1188 configData['colorSpaces'].append(LogCEIlinearization)
1191 LogCEIprimaries = createLogC(gamut, "", defaultEI, name="LogC")
1192 configData['colorSpaces'].append(LogCEIprimaries)
1195 # Generic log transform
1197 def createGenericLog(name='log',
1204 lutResolution1d=lutResolution1d):
1205 cs = ColorSpace(name)
1206 cs.description = "The %s color space" % name
1207 cs.equalityGroup = name
1208 cs.family = 'Utility'
1212 #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
1213 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
1215 lut = "%s_to_aces.spi1d" % name
1217 genlut.generate1dLUTFromCTL( lutDir + "/" + lut,
1224 'middleGrey' : middleGrey,
1225 'minExposure' : minExposure,
1226 'maxExposure' : maxExposure
1233 cs.toReferenceTransforms = []
1234 cs.toReferenceTransforms.append( {
1237 'interpolation':'linear',
1238 'direction':'forward'
1241 cs.fromReferenceTransforms = []
1247 def createACESLMT(lmtName,
1250 lutResolution1d=1024,
1253 cs = ColorSpace("%s" % lmtName)
1254 cs.description = "The ACES Look Transform: %s" % lmtName
1255 cs.equalityGroup = ''
1260 pprint.pprint( lmtValues )
1263 # Generate the shaper transform
1265 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1267 shaperLut = "%s_to_aces.spi1d" % shaperName
1268 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1270 shaperToACESCTL % acesCTLReleaseDir
1272 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
1276 1.0/shaperInputScale,
1282 shaperOCIOTransform = {
1285 'interpolation':'linear',
1286 'direction':'inverse'
1290 # Generate the forward transform
1292 cs.fromReferenceTransforms = []
1294 if 'transformCTL' in lmtValues:
1296 shaperToACESCTL % acesCTLReleaseDir,
1297 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
1299 lut = "%s.%s.spi3d" % (shaperName, lmtName)
1301 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
1305 1.0/shaperInputScale,
1311 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1312 cs.fromReferenceTransforms.append( {
1315 'interpolation':'tetrahedral',
1316 'direction':'forward'
1320 # Generate the inverse transform
1322 cs.toReferenceTransforms = []
1324 if 'transformCTLInverse' in lmtValues:
1326 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1327 shaperFromACESCTL % acesCTLReleaseDir
1329 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
1331 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
1341 cs.toReferenceTransforms.append( {
1344 'interpolation':'tetrahedral',
1345 'direction':'forward'
1348 shaperInverse = shaperOCIOTransform.copy()
1349 shaperInverse['direction'] = 'forward'
1350 cs.toReferenceTransforms.append( shaperInverse )
1358 lmtLutResolution1d = max(4096, lutResolution1d)
1359 lmtLutResolution3d = max(65, lutResolution3d)
1362 lmtShaperName = 'LMT Shaper'
1364 'middleGrey' : 0.18,
1365 'minExposure' : -10.0,
1368 lmtShaper = createGenericLog(name=lmtShaperName,
1369 middleGrey=lmtParams['middleGrey'],
1370 minExposure=lmtParams['minExposure'],
1371 maxExposure=lmtParams['maxExposure'],
1372 lutResolution1d=lmtLutResolution1d)
1373 configData['colorSpaces'].append(lmtShaper)
1375 shaperInputScale_genericLog2 = 1.0
1377 # Log 2 shaper name and CTL transforms bundled up
1380 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1381 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1382 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1383 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1384 shaperInputScale_genericLog2,
1388 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
1390 for lmt in sortedLMTs:
1391 (lmtName, lmtValues) = lmt
1393 lmtValues['transformUserName'],
1399 configData['colorSpaces'].append(cs)
1402 # ACES RRT with the supplied ODT
1404 def createACESRRTplusODT(odtName,
1407 lutResolution1d=1024,
1410 cs = ColorSpace("%s" % odtName)
1411 cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
1412 cs.equalityGroup = ''
1413 cs.family = 'Output'
1417 pprint.pprint( odtValues )
1420 # Generate the shaper transform
1422 #if 'shaperCTL' in odtValues:
1423 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1425 if 'legalRange' in odtValues:
1426 shaperParams['legalRange'] = odtValues['legalRange']
1428 shaperParams['legalRange'] = 0
1430 shaperLut = "%s_to_aces.spi1d" % shaperName
1431 if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1433 shaperToACESCTL % acesCTLReleaseDir
1435 genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut,
1439 1.0/shaperInputScale,
1445 shaperOCIOTransform = {
1448 'interpolation':'linear',
1449 'direction':'inverse'
1453 # Generate the forward transform
1455 cs.fromReferenceTransforms = []
1457 if 'transformLUT' in odtValues:
1458 # Copy into the lut dir
1459 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1460 lut = lutDir + "/" + transformLUTFileName
1461 shutil.copy(odtValues['transformLUT'], lut)
1463 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1464 cs.fromReferenceTransforms.append( {
1466 'path': transformLUTFileName,
1467 'interpolation':'tetrahedral',
1468 'direction':'forward'
1470 elif 'transformCTL' in odtValues:
1474 shaperToACESCTL % acesCTLReleaseDir,
1475 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1476 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1478 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1480 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
1485 1.0/shaperInputScale,
1491 cs.fromReferenceTransforms.append( shaperOCIOTransform )
1492 cs.fromReferenceTransforms.append( {
1495 'interpolation':'tetrahedral',
1496 'direction':'forward'
1500 # Generate the inverse transform
1502 cs.toReferenceTransforms = []
1504 if 'transformLUTInverse' in odtValues:
1505 # Copy into the lut dir
1506 transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
1507 lut = lutDir + "/" + transformLUTInverseFileName
1508 shutil.copy(odtValues['transformLUTInverse'], lut)
1510 cs.toReferenceTransforms.append( {
1512 'path': transformLUTInverseFileName,
1513 'interpolation':'tetrahedral',
1514 'direction':'forward'
1517 shaperInverse = shaperOCIOTransform.copy()
1518 shaperInverse['direction'] = 'forward'
1519 cs.toReferenceTransforms.append( shaperInverse )
1520 elif 'transformCTLInverse' in odtValues:
1522 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1523 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1524 shaperFromACESCTL % acesCTLReleaseDir
1526 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1528 genlut.generate3dLUTFromCTL( lutDir + "/" + lut,
1539 cs.toReferenceTransforms.append( {
1542 'interpolation':'tetrahedral',
1543 'direction':'forward'
1546 shaperInverse = shaperOCIOTransform.copy()
1547 shaperInverse['direction'] = 'forward'
1548 cs.toReferenceTransforms.append( shaperInverse )
1553 # RRT/ODT shaper options
1558 log2ShaperName = shaperName
1560 'middleGrey' : 0.18,
1561 'minExposure' : -6.0,
1564 log2Shaper = createGenericLog(name=log2ShaperName,
1565 middleGrey=log2Params['middleGrey'],
1566 minExposure=log2Params['minExposure'],
1567 maxExposure=log2Params['maxExposure'])
1568 configData['colorSpaces'].append(log2Shaper)
1570 shaperInputScale_genericLog2 = 1.0
1572 # Log 2 shaper name and CTL transforms bundled up
1575 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1576 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1577 #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1578 #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1579 shaperInputScale_genericLog2,
1583 shaperData[log2ShaperName] = log2ShaperData
1586 # Shaper that also includes the AP1 primaries
1587 # - Needed for some LUT baking steps
1589 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1590 middleGrey=log2Params['middleGrey'],
1591 minExposure=log2Params['minExposure'],
1592 maxExposure=log2Params['maxExposure'])
1593 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1594 # AP1 primaries to AP0 primaries
1595 log2ShaperAP1.toReferenceTransforms.append( {
1597 'matrix':mat44FromMat33(acesAP1toAP0),
1598 'direction':'forward'
1600 configData['colorSpaces'].append(log2ShaperAP1)
1603 # Choose your shaper
1606 # Shaper name. Should really be automated or made a user choice
1608 # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
1609 #shaperName = 'log2Shaper'
1611 #if shaperName in shaperData:
1612 # rrtShaperName = shaperName
1613 # rrtShaper = shaperData[shaperName]
1616 rrtShaperName = log2ShaperName
1617 rrtShaper = log2ShaperData
1620 # RRT + ODT Combinations
1622 #for odtName, odtValues in odtInfo.iteritems():
1623 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1625 for odt in sortedOdts:
1626 (odtName, odtValues) = odt
1628 # Have to handle ODTs that can generate either legal or full output
1629 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1630 'Academy.Rec709_100nits_dim.a1.0.0',
1631 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1632 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1634 odtNameLegal = odtValues['transformUserName']
1636 odtLegal = odtValues.copy()
1637 odtLegal['legalRange'] = 1
1639 cs = createACESRRTplusODT(
1646 configData['colorSpaces'].append(cs)
1648 # Create a display entry using this color space
1649 configData['displays'][odtNameLegal] = {
1652 'Output Transform':cs }
1654 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1655 'Academy.Rec709_100nits_dim.a1.0.0',
1656 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1658 print( "Generating full range ODT for %s" % odtName)
1660 odtNameFull = "%s - Full" % odtValues['transformUserName']
1661 odtFull = odtValues.copy()
1662 odtFull['legalRange'] = 0
1664 csFull = createACESRRTplusODT(
1671 configData['colorSpaces'].append(csFull)
1673 # Create a display entry using this color space
1674 configData['displays'][odtNameFull] = {
1677 'Output Transform':csFull }
1680 # Generic Matrix transform
1682 def createGenericMatrix(name='matrix',
1683 fromReferenceValues=[],
1684 toReferenceValues=[]):
1685 cs = ColorSpace(name)
1686 cs.description = "The %s color space" % name
1687 cs.equalityGroup = name
1688 cs.family = 'Utility'
1691 cs.toReferenceTransforms = []
1692 if toReferenceValues != []:
1693 for matrix in toReferenceValues:
1694 cs.toReferenceTransforms.append( {
1696 'matrix':mat44FromMat33(matrix),
1697 'direction':'forward'
1700 cs.fromReferenceTransforms = []
1701 if fromReferenceValues != []:
1702 for matrix in fromReferenceValues:
1703 cs.fromReferenceTransforms.append( {
1705 'matrix':mat44FromMat33(matrix),
1706 'direction':'forward'
1711 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1712 configData['colorSpaces'].append(cs)
1714 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1715 configData['colorSpaces'].append(cs)
1717 # ACES to Linear, P3D60 primaries
1718 xyzToP3D60 = [ 2.4027414142, -0.8974841639, -0.3880533700,
1719 -0.8325796487, 1.7692317536, 0.0237127115,
1720 0.0388233815, -0.0824996856, 1.0363685997]
1722 cs = createGenericMatrix('Linear - P3-D60', fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1723 configData['colorSpaces'].append(cs)
1725 # ACES to Linear, P3D60 primaries
1726 xyzToP3DCI = [ 2.7253940305, -1.0180030062, -0.4401631952,
1727 -0.7951680258, 1.6897320548, 0.0226471906,
1728 0.0412418914, -0.0876390192, 1.1009293786]
1730 cs = createGenericMatrix('Linear - P3-DCI', fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1731 configData['colorSpaces'].append(cs)
1733 # ACES to Linear, Rec 709 primaries
1734 xyzToRec709 = [ 3.2409699419, -1.5373831776, -0.4986107603,
1735 -0.9692436363, 1.8759675015, 0.0415550574,
1736 0.0556300797, -0.2039769589, 1.0569715142]
1738 cs = createGenericMatrix('Linear - Rec.709', fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1739 configData['colorSpaces'].append(cs)
1741 # ACES to Linear, Rec 2020 primaries
1742 xyzToRec2020 = [ 1.7166511880, -0.3556707838, -0.2533662814,
1743 -0.6666843518, 1.6164812366, 0.0157685458,
1744 0.0176398574, -0.0427706133, 0.9421031212]
1746 cs = createGenericMatrix('Linear - Rec.2020', fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1747 configData['colorSpaces'].append(cs)
1749 print( "generateLUTs - end" )
1752 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1754 # Add the legal and full variations into this list
1755 odtInfoC = dict(odtInfo)
1756 for odtCTLName, odtValues in odtInfo.iteritems():
1757 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1758 'Academy.Rec709_100nits_dim.a1.0.0',
1759 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1760 odtName = odtValues["transformUserName"]
1762 odtValuesLegal = dict(odtValues)
1763 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1764 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1766 odtValuesFull = dict(odtValues)
1767 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1768 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1770 del( odtInfoC[odtCTLName] )
1772 for odtCTLName, odtValues in odtInfoC.iteritems():
1773 odtPrefix = odtValues["transformUserNamePrefix"]
1774 odtName = odtValues["transformUserName"]
1777 for inputspace in ["ACEScc", "ACESproxy"]:
1778 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1779 args += ["--outputspace", "%s" % odtName ]
1780 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1781 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1782 args += ["--cubesize", str(lutResolution3d) ]
1783 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1785 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1789 for inputspace in ["ACEScc", "ACESproxy"]:
1790 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1791 args += ["--outputspace", "%s" % odtName ]
1792 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1793 args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ]
1794 args += ["--cubesize", str(lutResolution3d) ]
1796 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1797 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1800 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1801 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1805 for inputspace in ["ACEScg", "ACES2065-1"]:
1806 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1807 args += ["--outputspace", "%s" % odtName ]
1808 args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1809 if inputspace == 'ACEScg':
1810 linShaperName = "%s - AP1" % shaperName
1812 linShaperName = shaperName
1813 args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ]
1815 args += ["--cubesize", str(lutResolution3d) ]
1817 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
1818 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
1821 hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
1822 bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
1826 def createConfigDir(configDir, bakeSecondaryLUTs):
1827 dirs = [configDir, "%s/luts" % configDir]
1828 if bakeSecondaryLUTs:
1829 dirs.extend(["%s/baked" % configDir,
1830 "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
1831 "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
1832 "%s/baked/maya" % configDir])
1835 if not os.path.exists(d):
1838 def getTransformInfo(ctlTransform):
1839 fp = open(ctlTransform, 'rb')
1842 lines = fp.readlines()
1844 # Grab transform ID and User Name
1845 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1846 #print( transformID )
1847 transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
1848 transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
1849 #print( transformUserName )
1852 return (transformID, transformUserName, transformUserNamePrefix)
1854 # For versions after WGR9
1855 def getODTInfo(acesCTLReleaseDir):
1856 # Credit to Alex Fry for the original approach here
1857 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1859 for dirName, subdirList, fileList in os.walk(odtDir):
1860 for fname in fileList:
1861 allodt.append((os.path.join(dirName,fname)))
1863 odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1869 for odtCTL in odtCTLs:
1870 odtTokens = os.path.split(odtCTL)
1873 # Handle nested directories
1874 odtPathTokens = os.path.split(odtTokens[-2])
1875 odtDir = odtPathTokens[-1]
1876 while odtPathTokens[-2][-3:] != 'odt':
1877 odtPathTokens = os.path.split(odtPathTokens[-2])
1878 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1881 #print( "odtDir : %s" % odtDir )
1882 transformCTL = odtTokens[-1]
1883 #print( transformCTL )
1884 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1887 # Find id, user name and user name prefix
1888 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1889 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
1892 transformCTLInverse = "InvODT.%s.ctl" % odtName
1893 if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
1894 transformCTLInverse = None
1895 #print( transformCTLInverse )
1897 # Add to list of ODTs
1899 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1900 if transformCTLInverse != None:
1901 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
1903 odts[odtName]['transformID'] = transformID
1904 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1905 odts[odtName]['transformUserName'] = transformUserName
1907 print( "ODT : %s" % odtName )
1908 print( "\tTransform ID : %s" % transformID )
1909 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1910 print( "\tTransform User Name : %s" % transformUserName )
1911 print( "\tForward ctl : %s" % odts[odtName]['transformCTL'])
1912 if 'transformCTLInverse' in odts[odtName]:
1913 print( "\tInverse ctl : %s" % odts[odtName]['transformCTLInverse'])
1915 print( "\tInverse ctl : %s" % "None" )
1921 # For versions after WGR9
1922 def getLMTInfo(acesCTLReleaseDir):
1923 # Credit to Alex Fry for the original approach here
1924 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1926 for dirName, subdirList, fileList in os.walk(lmtDir):
1927 for fname in fileList:
1928 alllmt.append((os.path.join(dirName,fname)))
1930 lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
1936 for lmtCTL in lmtCTLs:
1937 lmtTokens = os.path.split(lmtCTL)
1940 # Handle nested directories
1941 lmtPathTokens = os.path.split(lmtTokens[-2])
1942 lmtDir = lmtPathTokens[-1]
1943 while lmtPathTokens[-2][-3:] != 'ctl':
1944 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1945 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1948 #print( "lmtDir : %s" % lmtDir )
1949 transformCTL = lmtTokens[-1]
1950 #print( transformCTL )
1951 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1954 # Find id, user name and user name prefix
1955 (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1956 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
1959 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1960 if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
1961 transformCTLInverse = None
1962 #print( transformCTLInverse )
1964 # Add to list of LMTs
1966 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1967 if transformCTLInverse != None:
1968 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
1970 lmts[lmtName]['transformID'] = transformID
1971 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1972 lmts[lmtName]['transformUserName'] = transformUserName
1974 print( "LMT : %s" % lmtName )
1975 print( "\tTransform ID : %s" % transformID )
1976 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1977 print( "\tTransform User Name : %s" % transformUserName )
1978 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1979 if 'transformCTLInverse' in lmts[lmtName]:
1980 print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1982 print( "\t Inverse ctl : %s" % "None" )
1989 # Create the ACES config
1991 def createACESConfig(acesCTLReleaseDir,
1993 lutResolution1d=4096,
1995 bakeSecondaryLUTs=True,
1998 # Get ODT names and CTL paths
1999 odtInfo = getODTInfo(acesCTLReleaseDir)
2001 # Get ODT names and CTL paths
2002 lmtInfo = getLMTInfo(acesCTLReleaseDir)
2005 createConfigDir(configDir, bakeSecondaryLUTs)
2007 # Generate config data and LUTs for different transforms
2008 lutDir = "%s/luts" % configDir
2009 shaperName = 'Output Shaper'
2010 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
2012 # Create the config using the generated LUTs
2013 print( "Creating generic config")
2014 config = createConfig(configData)
2017 # Write the config to disk
2018 writeConfig(config, "%s/config.ocio" % configDir )
2020 # Create a config that will work well with Nuke using the previously generated LUTs
2021 print( "Creating Nuke-specific config")
2022 nuke_config = createConfig(configData, nuke=True)
2025 # Write the config to disk
2026 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
2028 # Bake secondary LUTs using the config
2029 if bakeSecondaryLUTs:
2030 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
2038 p = optparse.OptionParser(description='An OCIO config generation script',
2039 prog='createACESConfig',
2040 version='createACESConfig 0.1',
2041 usage='%prog [options]')
2042 p.add_option('--acesCTLDir', '-a', default=None)
2043 p.add_option('--configDir', '-c', default=None)
2044 p.add_option('--lutResolution1d', default=4096)
2045 p.add_option('--lutResolution3d', default=64)
2046 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
2047 p.add_option('--keepTempImages', action="store_true")
2049 options, arguments = p.parse_args()
2054 acesCTLDir = options.acesCTLDir
2055 configDir = options.configDir
2056 lutResolution1d = int(options.lutResolution1d)
2057 lutResolution3d = int(options.lutResolution3d)
2058 bakeSecondaryLUTs = not(options.dontBakeSecondaryLUTs)
2059 cleanupTempImages = not(options.keepTempImages)
2062 argsStart = sys.argv.index('--') + 1
2063 args = sys.argv[argsStart:]
2065 argsStart = len(sys.argv)+1
2068 print( "command line : \n%s\n" % " ".join(sys.argv) )
2070 if configDir == None:
2071 print( "process: No ACES CTL directory specified" )
2075 # Generate the configuration
2077 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
2080 if __name__ == '__main__':