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
46 # TODO: This restores the capability of running the script without having
47 # added the package to PYTHONPATH, this is ugly and should ideally replaced by
48 # dedicated executable in a /bin directory.
49 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
51 import PyOpenColorIO as OCIO
53 import aces_ocio.createARRIColorSpaces as arri
54 import aces_ocio.createCanonColorSpaces as canon
55 import aces_ocio.createREDColorSpaces as red
56 import aces_ocio.createSonyColorSpaces as sony
57 import aces_ocio.generateLUT as genlut
58 from aces_ocio.process import Process
59 from aces_ocio.util import ColorSpace, mat44FromMat33
61 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
62 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
67 def setConfigDefaultRoles(config,
78 if color_picking: config.setRole(OCIO.Constants.ROLE_COLOR_PICKING,
80 if color_timing: config.setRole(OCIO.Constants.ROLE_COLOR_TIMING,
82 if compositing_log: config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG,
84 if data: config.setRole(OCIO.Constants.ROLE_DATA, data)
85 if default: config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
86 if matte_paint: config.setRole(OCIO.Constants.ROLE_MATTE_PAINT,
88 if reference: config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
89 if scene_linear: config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR,
91 if texture_paint: config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT,
95 # Write config to disk
96 def writeConfig(config, configPath, sanityCheck=True):
102 print "Configuration was not written due to a failed Sanity Check"
106 fileHandle = open(configPath, mode='w')
107 fileHandle.write(config.serialize())
111 def generateOCIOTransform(transforms):
112 # print( "Generating transforms")
114 interpolationOptions = {
115 'linear': OCIO.Constants.INTERP_LINEAR,
116 'nearest': OCIO.Constants.INTERP_NEAREST,
117 'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
120 'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
121 'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
126 for transform in transforms:
127 if transform['type'] == 'lutFile':
129 ocioTransform = OCIO.FileTransform(src=transform['path'],
131 interpolationOptions[
132 transform['interpolation']],
133 direction=directionOptions[
134 transform['direction']])
136 ocioTransforms.append(ocioTransform)
137 elif transform['type'] == 'matrix':
138 ocioTransform = OCIO.MatrixTransform()
139 # MatrixTransform member variables can't be initialized directly. Each must be set individually
140 ocioTransform.setMatrix(transform['matrix'])
142 if 'offset' in transform:
143 ocioTransform.setOffset(transform['offset'])
144 if 'direction' in transform:
145 ocioTransform.setDirection(
146 directionOptions[transform['direction']])
148 ocioTransforms.append(ocioTransform)
149 elif transform['type'] == 'exponent':
150 ocioTransform = OCIO.ExponentTransform()
151 ocioTransform.setValue(transform['value'])
153 ocioTransforms.append(ocioTransform)
154 elif transform['type'] == 'log':
155 ocioTransform = OCIO.LogTransform(base=transform['base'],
156 direction=directionOptions[
157 transform['direction']])
159 ocioTransforms.append(ocioTransform)
161 print( "Ignoring unknown transform type : %s" % transform['type'] )
163 # Build a group transform if necessary
164 if len(ocioTransforms) > 1:
165 transformG = OCIO.GroupTransform()
166 for transform in ocioTransforms:
167 transformG.push_back(transform)
168 transform = transformG
170 # Or take the first transform from the list
172 transform = ocioTransforms[0]
177 def createConfig(configData, nuke=False):
179 config = OCIO.Config()
182 # Set config wide values
184 config.setDescription("An ACES config generated from python")
185 config.setSearchPath("luts")
188 # Define the reference color space
190 referenceData = configData['referenceColorSpace']
191 print( "Adding the reference color space : %s" % referenceData.name)
193 # Create a color space
194 reference = OCIO.ColorSpace(name=referenceData.name,
195 bitDepth=referenceData.bitDepth,
196 description=referenceData.description,
197 equalityGroup=referenceData.equalityGroup,
198 family=referenceData.family,
199 isData=referenceData.isData,
200 allocation=referenceData.allocationType,
201 allocationVars=referenceData.allocationVars)
204 config.addColorSpace(reference)
207 # Create the rest of the color spaces
209 for colorspace in sorted(configData['colorSpaces']):
210 print( "Creating new color space : %s" % colorspace.name)
212 ocioColorspace = OCIO.ColorSpace(name=colorspace.name,
213 bitDepth=colorspace.bitDepth,
214 description=colorspace.description,
215 equalityGroup=colorspace.equalityGroup,
216 family=colorspace.family,
217 isData=colorspace.isData,
218 allocation=colorspace.allocationType,
219 allocationVars=colorspace.allocationVars)
221 if colorspace.toReferenceTransforms != []:
222 print( "Generating To-Reference transforms")
223 ocioTransform = generateOCIOTransform(
224 colorspace.toReferenceTransforms)
225 ocioColorspace.setTransform(ocioTransform,
226 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
228 if colorspace.fromReferenceTransforms != []:
229 print( "Generating From-Reference transforms")
230 ocioTransform = generateOCIOTransform(
231 colorspace.fromReferenceTransforms)
232 ocioColorspace.setTransform(ocioTransform,
233 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
235 config.addColorSpace(ocioColorspace)
240 # Define the views and displays
245 # Generic display and view setup
247 for display, viewList in configData['displays'].iteritems():
248 for viewName, colorspace in viewList.iteritems():
249 config.addDisplay(display, viewName, colorspace.name)
250 if not (viewName in views):
251 views.append(viewName)
252 displays.append(display)
253 # A Nuke specific set of views and displays
256 # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
259 for display, viewList in configData['displays'].iteritems():
260 for viewName, colorspace in viewList.iteritems():
261 if ( viewName == 'Output Transform'):
263 config.addDisplay(display, viewName, colorspace.name)
264 if not (viewName in views):
265 views.append(viewName)
266 displays.append(display)
268 config.addDisplay('linear', 'View', 'ACES2065-1')
269 displays.append('linear')
270 config.addDisplay('log', 'View', 'ACEScc')
271 displays.append('log')
273 # Set active displays and views
274 config.setActiveDisplays(','.join(sorted(displays)))
275 config.setActiveViews(','.join(views))
278 # Need to generalize this at some point
282 setConfigDefaultRoles(config,
283 color_picking=reference.getName(),
284 color_timing=reference.getName(),
285 compositing_log=reference.getName(),
286 data=reference.getName(),
287 default=reference.getName(),
288 matte_paint=reference.getName(),
289 reference=reference.getName(),
290 scene_linear=reference.getName(),
291 texture_paint=reference.getName())
293 # Check to make sure we didn't screw something up
300 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
303 # Output is a list of colorspaces and transforms that convert between those
304 # colorspaces and reference color space, ACES
305 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir,
306 lutResolution1d=4096, lutResolution3d=64, cleanup=True):
307 print( "generateLUTs - begin" )
311 # Define the reference color space
313 ACES = ColorSpace('ACES2065-1')
314 ACES.description = "The Academy Color Encoding System reference color space"
315 ACES.equalityGroup = ''
318 ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
319 ACES.allocationVars = [-15, 6]
321 configData['referenceColorSpace'] = ACES
324 # Define the displays
326 configData['displays'] = {}
329 # Define the other color spaces
331 configData['colorSpaces'] = []
333 # Matrix converting ACES AP1 primaries to AP0
334 acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
335 0.0447945634, 0.8596711185, 0.0955343182,
336 -0.0055258826, 0.0040252103, 1.0015006723]
338 # Matrix converting ACES AP0 primaries to XYZ
339 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
340 0.3439664498, 0.7281660966, -0.0721325464,
341 0.0000000000, 0.0000000000, 1.0088251844]
346 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0,
348 cs = ColorSpace(name)
349 cs.description = "The %s color space" % name
350 cs.equalityGroup = ''
355 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
356 # This transform gets back to the AP1 primaries
357 # Useful as the 1d LUT is only covering the transfer function
358 # The primaries switch is covered by the matrix below
359 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
361 lut = "%s_to_ACES.spi1d" % name
363 # Remove spaces and parentheses
364 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
366 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
378 cs.toReferenceTransforms = []
379 cs.toReferenceTransforms.append({
382 'interpolation': 'linear',
383 'direction': 'forward'
386 # AP1 primaries to AP0 primaries
387 cs.toReferenceTransforms.append({
389 'matrix': mat44FromMat33(acesAP1toAP0),
390 'direction': 'forward'
393 cs.fromReferenceTransforms = []
396 ACEScc = createACEScc()
397 configData['colorSpaces'].append(ACEScc)
402 def createACESProxy(name='ACESproxy'):
403 cs = ColorSpace(name)
404 cs.description = "The %s color space" % name
405 cs.equalityGroup = ''
410 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
411 # This transform gets back to the AP1 primaries
412 # Useful as the 1d LUT is only covering the transfer function
413 # The primaries switch is covered by the matrix below
414 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
416 lut = "%s_to_aces.spi1d" % name
418 # Remove spaces and parentheses
419 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
421 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
431 cs.toReferenceTransforms = []
432 cs.toReferenceTransforms.append({
435 'interpolation': 'linear',
436 'direction': 'forward'
439 # AP1 primaries to AP0 primaries
440 cs.toReferenceTransforms.append({
442 'matrix': mat44FromMat33(acesAP1toAP0),
443 'direction': 'forward'
446 cs.fromReferenceTransforms = []
449 ACESproxy = createACESProxy()
450 configData['colorSpaces'].append(ACESproxy)
455 def createACEScg(name='ACEScg'):
456 cs = ColorSpace(name)
457 cs.description = "The %s color space" % name
458 cs.equalityGroup = ''
462 cs.toReferenceTransforms = []
464 # AP1 primaries to AP0 primaries
465 cs.toReferenceTransforms.append({
467 'matrix': mat44FromMat33(acesAP1toAP0),
468 'direction': 'forward'
471 cs.fromReferenceTransforms = []
474 ACEScg = createACEScg()
475 configData['colorSpaces'].append(ACEScg)
480 def createADX(bitdepth=10, name='ADX'):
481 name = "%s%s" % (name, bitdepth)
482 cs = ColorSpace(name)
483 cs.description = "%s color space - used for film scans" % name
484 cs.equalityGroup = ''
489 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
490 adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
491 0.0, 1023.0 / 500.0, 0.0, 0.0,
492 0.0, 0.0, 1023.0 / 500.0, 0.0,
494 offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
496 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
497 adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
498 0.0, 65535.0 / 8000.0, 0.0, 0.0,
499 0.0, 0.0, 65535.0 / 8000.0, 0.0,
501 offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
504 cs.toReferenceTransforms = []
506 # Convert from ADX to Channel-Dependent Density
507 cs.toReferenceTransforms.append({
509 'matrix': adx_to_cdd,
511 'direction': 'forward'
514 # Convert from Channel-Dependent Density to Channel-Independent Density
515 cs.toReferenceTransforms.append({
517 'matrix': [0.75573, 0.22197, 0.02230, 0,
518 0.05901, 0.96928, -0.02829, 0,
519 0.16134, 0.07406, 0.76460, 0,
521 'direction': 'forward'
524 # Copied from Alex Fry's adx_cid_to_rle.py
525 def createCIDtoRLELUT():
526 def interpolate1D(x, xp, fp):
527 return numpy.interp(x, xp, fp)
529 LUT_1D_xp = [-0.190000000000000,
541 LUT_1D_fp = [-6.000000000000000,
553 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(
558 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
559 return (100.0 / 55.0) * x - REF_PT
561 def Fit(value, fromMin, fromMax, toMin, toMax):
562 if fromMin == fromMax:
563 raise ValueError("fromMin == fromMax")
564 return (value - fromMin) / (fromMax - fromMin) * (
565 toMax - toMin) + toMin
567 NUM_SAMPLES = 2 ** 12
570 for i in xrange(NUM_SAMPLES):
571 x = i / (NUM_SAMPLES - 1.0)
572 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
573 data.append(cid_to_rle(x))
575 lut = 'ADX_CID_to_RLE.spi1d'
576 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
581 # Convert Channel Independent Density values to Relative Log Exposure values
582 lut = createCIDtoRLELUT()
583 cs.toReferenceTransforms.append({
586 'interpolation': 'linear',
587 'direction': 'forward'
590 # Convert Relative Log Exposure values to Relative Exposure values
591 cs.toReferenceTransforms.append({
594 'direction': 'inverse'
597 # Convert Relative Exposure values to ACES values
598 cs.toReferenceTransforms.append({
600 'matrix': [0.72286, 0.12630, 0.15084, 0,
601 0.11923, 0.76418, 0.11659, 0,
602 0.01427, 0.08213, 0.90359, 0,
604 'direction': 'forward'
607 cs.fromReferenceTransforms = []
610 ADX10 = createADX(bitdepth=10)
611 configData['colorSpaces'].append(ADX10)
613 ADX16 = createADX(bitdepth=16)
614 configData['colorSpaces'].append(ADX16)
617 # Camera Input Transforms
620 # RED color spaces to ACES
621 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
622 for cs in redColorSpaces:
623 configData['colorSpaces'].append(cs)
626 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
627 for cs in canonColorSpaces:
628 configData['colorSpaces'].append(cs)
631 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
632 for cs in sonyColorSpaces:
633 configData['colorSpaces'].append(cs)
636 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
637 for cs in arriColorSpaces:
638 configData['colorSpaces'].append(cs)
641 # Generic log transform
643 def createGenericLog(name='log',
650 lutResolution1d=lutResolution1d):
651 cs = ColorSpace(name)
652 cs.description = "The %s color space" % name
653 cs.equalityGroup = name
654 cs.family = 'Utility'
658 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
660 lut = "%s_to_aces.spi1d" % name
662 # Remove spaces and parentheses
663 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
665 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
672 'middleGrey': middleGrey,
673 'minExposure': minExposure,
674 'maxExposure': maxExposure
681 cs.toReferenceTransforms = []
682 cs.toReferenceTransforms.append({
685 'interpolation': 'linear',
686 'direction': 'forward'
689 cs.fromReferenceTransforms = []
695 def createACESLMT(lmtName,
698 lutResolution1d=1024,
701 cs = ColorSpace("%s" % lmtName)
702 cs.description = "The ACES Look Transform: %s" % lmtName
703 cs.equalityGroup = ''
709 pprint.pprint(lmtValues)
712 # Generate the shaper transform
714 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale,
715 shaperParams) = shaperInfo
717 shaperLut = "%s_to_aces.spi1d" % shaperName
718 if ( not os.path.exists(lutDir + "/" + shaperLut) ):
720 shaperToACESCTL % acesCTLReleaseDir
723 # Remove spaces and parentheses
724 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace(
727 genlut.generate1dLUTFromCTL(lutDir + "/" + shaperLut,
731 1.0 / shaperInputScale,
737 shaperOCIOTransform = {
740 'interpolation': 'linear',
741 'direction': 'inverse'
745 # Generate the forward transform
747 cs.fromReferenceTransforms = []
749 if 'transformCTL' in lmtValues:
751 shaperToACESCTL % acesCTLReleaseDir,
752 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
754 lut = "%s.%s.spi3d" % (shaperName, lmtName)
756 # Remove spaces and parentheses
757 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
759 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
763 1.0 / shaperInputScale,
769 cs.fromReferenceTransforms.append(shaperOCIOTransform)
770 cs.fromReferenceTransforms.append({
773 'interpolation': 'tetrahedral',
774 'direction': 'forward'
778 # Generate the inverse transform
780 cs.toReferenceTransforms = []
782 if 'transformCTLInverse' in lmtValues:
785 acesCTLReleaseDir, odtValues['transformCTLInverse']),
786 shaperFromACESCTL % acesCTLReleaseDir
788 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
790 # Remove spaces and parentheses
791 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
793 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
803 cs.toReferenceTransforms.append({
806 'interpolation': 'tetrahedral',
807 'direction': 'forward'
810 shaperInverse = shaperOCIOTransform.copy()
811 shaperInverse['direction'] = 'forward'
812 cs.toReferenceTransforms.append(shaperInverse)
820 lmtLutResolution1d = max(4096, lutResolution1d)
821 lmtLutResolution3d = max(65, lutResolution3d)
824 lmtShaperName = 'LMT Shaper'
827 'minExposure': -10.0,
830 lmtShaper = createGenericLog(name=lmtShaperName,
831 middleGrey=lmtParams['middleGrey'],
832 minExposure=lmtParams['minExposure'],
833 maxExposure=lmtParams['maxExposure'],
834 lutResolution1d=lmtLutResolution1d)
835 configData['colorSpaces'].append(lmtShaper)
837 shaperInputScale_genericLog2 = 1.0
839 # Log 2 shaper name and CTL transforms bundled up
842 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
843 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
844 shaperInputScale_genericLog2,
848 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
850 for lmt in sortedLMTs:
851 (lmtName, lmtValues) = lmt
853 lmtValues['transformUserName'],
859 configData['colorSpaces'].append(cs)
862 # ACES RRT with the supplied ODT
864 def createACESRRTplusODT(odtName,
867 lutResolution1d=1024,
870 cs = ColorSpace("%s" % odtName)
871 cs.description = "%s - %s Output Transform" % (
872 odtValues['transformUserNamePrefix'], odtName)
873 cs.equalityGroup = ''
879 pprint.pprint(odtValues)
882 # Generate the shaper transform
884 # if 'shaperCTL' in odtValues:
885 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale,
886 shaperParams) = shaperInfo
888 if 'legalRange' in odtValues:
889 shaperParams['legalRange'] = odtValues['legalRange']
891 shaperParams['legalRange'] = 0
893 shaperLut = "%s_to_aces.spi1d" % shaperName
894 if ( not os.path.exists(lutDir + "/" + shaperLut) ):
896 shaperToACESCTL % acesCTLReleaseDir
899 # Remove spaces and parentheses
900 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace(
903 genlut.generate1dLUTFromCTL(lutDir + "/" + shaperLut,
907 1.0 / shaperInputScale,
913 shaperOCIOTransform = {
916 'interpolation': 'linear',
917 'direction': 'inverse'
921 # Generate the forward transform
923 cs.fromReferenceTransforms = []
925 if 'transformLUT' in odtValues:
926 # Copy into the lut dir
927 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
928 lut = lutDir + "/" + transformLUTFileName
929 shutil.copy(odtValues['transformLUT'], lut)
931 cs.fromReferenceTransforms.append(shaperOCIOTransform)
932 cs.fromReferenceTransforms.append({
934 'path': transformLUTFileName,
935 'interpolation': 'tetrahedral',
936 'direction': 'forward'
938 elif 'transformCTL' in odtValues:
942 shaperToACESCTL % acesCTLReleaseDir,
943 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
944 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
946 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
948 # Remove spaces and parentheses
949 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
951 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
956 1.0 / shaperInputScale,
962 cs.fromReferenceTransforms.append(shaperOCIOTransform)
963 cs.fromReferenceTransforms.append({
966 'interpolation': 'tetrahedral',
967 'direction': 'forward'
971 # Generate the inverse transform
973 cs.toReferenceTransforms = []
975 if 'transformLUTInverse' in odtValues:
976 # Copy into the lut dir
977 transformLUTInverseFileName = os.path.basename(
978 odtValues['transformLUTInverse'])
979 lut = lutDir + "/" + transformLUTInverseFileName
980 shutil.copy(odtValues['transformLUTInverse'], lut)
982 cs.toReferenceTransforms.append({
984 'path': transformLUTInverseFileName,
985 'interpolation': 'tetrahedral',
986 'direction': 'forward'
989 shaperInverse = shaperOCIOTransform.copy()
990 shaperInverse['direction'] = 'forward'
991 cs.toReferenceTransforms.append(shaperInverse)
992 elif 'transformCTLInverse' in odtValues:
995 acesCTLReleaseDir, odtValues['transformCTLInverse']),
996 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
997 shaperFromACESCTL % acesCTLReleaseDir
999 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1001 # Remove spaces and parentheses
1002 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1004 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1015 cs.toReferenceTransforms.append({
1018 'interpolation': 'tetrahedral',
1019 'direction': 'forward'
1022 shaperInverse = shaperOCIOTransform.copy()
1023 shaperInverse['direction'] = 'forward'
1024 cs.toReferenceTransforms.append(shaperInverse)
1029 # RRT/ODT shaper options
1034 log2ShaperName = shaperName
1037 'minExposure': -6.0,
1040 log2Shaper = createGenericLog(name=log2ShaperName,
1041 middleGrey=log2Params['middleGrey'],
1042 minExposure=log2Params['minExposure'],
1043 maxExposure=log2Params['maxExposure'])
1044 configData['colorSpaces'].append(log2Shaper)
1046 shaperInputScale_genericLog2 = 1.0
1048 # Log 2 shaper name and CTL transforms bundled up
1051 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1052 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1053 shaperInputScale_genericLog2,
1057 shaperData[log2ShaperName] = log2ShaperData
1060 # Shaper that also includes the AP1 primaries
1061 # - Needed for some LUT baking steps
1063 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1064 middleGrey=log2Params['middleGrey'],
1065 minExposure=log2Params['minExposure'],
1066 maxExposure=log2Params['maxExposure'])
1067 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1068 # AP1 primaries to AP0 primaries
1069 log2ShaperAP1.toReferenceTransforms.append({
1071 'matrix': mat44FromMat33(acesAP1toAP0),
1072 'direction': 'forward'
1074 configData['colorSpaces'].append(log2ShaperAP1)
1077 # Choose your shaper
1079 rrtShaperName = log2ShaperName
1080 rrtShaper = log2ShaperData
1083 # RRT + ODT Combinations
1085 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1087 for odt in sortedOdts:
1088 (odtName, odtValues) = odt
1090 # Have to handle ODTs that can generate either legal or full output
1091 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1092 'Academy.Rec709_100nits_dim.a1.0.0',
1093 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1094 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1096 odtNameLegal = odtValues['transformUserName']
1098 odtLegal = odtValues.copy()
1099 odtLegal['legalRange'] = 1
1101 cs = createACESRRTplusODT(
1108 configData['colorSpaces'].append(cs)
1110 # Create a display entry using this color space
1111 configData['displays'][odtNameLegal] = {
1114 'Output Transform': cs}
1116 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1117 'Academy.Rec709_100nits_dim.a1.0.0',
1118 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1119 print( "Generating full range ODT for %s" % odtName)
1121 odtNameFull = "%s - Full" % odtValues['transformUserName']
1122 odtFull = odtValues.copy()
1123 odtFull['legalRange'] = 0
1125 csFull = createACESRRTplusODT(
1132 configData['colorSpaces'].append(csFull)
1134 # Create a display entry using this color space
1135 configData['displays'][odtNameFull] = {
1138 'Output Transform': csFull}
1141 # Generic Matrix transform
1143 def createGenericMatrix(name='matrix',
1144 fromReferenceValues=[],
1145 toReferenceValues=[]):
1146 cs = ColorSpace(name)
1147 cs.description = "The %s color space" % name
1148 cs.equalityGroup = name
1149 cs.family = 'Utility'
1152 cs.toReferenceTransforms = []
1153 if toReferenceValues != []:
1154 for matrix in toReferenceValues:
1155 cs.toReferenceTransforms.append({
1157 'matrix': mat44FromMat33(matrix),
1158 'direction': 'forward'
1161 cs.fromReferenceTransforms = []
1162 if fromReferenceValues != []:
1163 for matrix in fromReferenceValues:
1164 cs.fromReferenceTransforms.append({
1166 'matrix': mat44FromMat33(matrix),
1167 'direction': 'forward'
1172 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1173 configData['colorSpaces'].append(cs)
1175 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1176 configData['colorSpaces'].append(cs)
1178 # ACES to Linear, P3D60 primaries
1179 xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1180 -0.8325796487, 1.7692317536, 0.0237127115,
1181 0.0388233815, -0.0824996856, 1.0363685997]
1183 cs = createGenericMatrix('Linear - P3-D60',
1184 fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1185 configData['colorSpaces'].append(cs)
1187 # ACES to Linear, P3D60 primaries
1188 xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1189 -0.7951680258, 1.6897320548, 0.0226471906,
1190 0.0412418914, -0.0876390192, 1.1009293786]
1192 cs = createGenericMatrix('Linear - P3-DCI',
1193 fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1194 configData['colorSpaces'].append(cs)
1196 # ACES to Linear, Rec 709 primaries
1197 xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1198 -0.9692436363, 1.8759675015, 0.0415550574,
1199 0.0556300797, -0.2039769589, 1.0569715142]
1201 cs = createGenericMatrix('Linear - Rec.709',
1202 fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1203 configData['colorSpaces'].append(cs)
1205 # ACES to Linear, Rec 2020 primaries
1206 xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1207 -0.6666843518, 1.6164812366, 0.0157685458,
1208 0.0176398574, -0.0427706133, 0.9421031212]
1210 cs = createGenericMatrix('Linear - Rec.2020',
1211 fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1212 configData['colorSpaces'].append(cs)
1214 print( "generateLUTs - end" )
1218 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath,
1219 lutResolution1d, lutResolution3d,
1220 lutResolutionShaper=1024):
1221 # Add the legal and full variations into this list
1222 odtInfoC = dict(odtInfo)
1223 for odtCTLName, odtValues in odtInfo.iteritems():
1224 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1225 'Academy.Rec709_100nits_dim.a1.0.0',
1226 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1227 odtName = odtValues["transformUserName"]
1229 odtValuesLegal = dict(odtValues)
1230 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1231 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1233 odtValuesFull = dict(odtValues)
1234 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1235 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1237 del ( odtInfoC[odtCTLName] )
1239 for odtCTLName, odtValues in odtInfoC.iteritems():
1240 odtPrefix = odtValues["transformUserNamePrefix"]
1241 odtName = odtValues["transformUserName"]
1244 for inputspace in ["ACEScc", "ACESproxy"]:
1245 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1246 args += ["--outputspace", "%s" % odtName]
1247 args += ["--description",
1248 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1249 args += ["--shaperspace", shaperName, "--shapersize",
1250 str(lutResolutionShaper)]
1251 args += ["--cubesize", str(lutResolution3d)]
1252 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1253 bakedDir, odtName, inputspace)]
1255 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1260 for inputspace in ["ACEScc", "ACESproxy"]:
1261 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1262 args += ["--outputspace", "%s" % odtName]
1263 args += ["--description",
1264 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1265 args += ["--shaperspace", shaperName, "--shapersize",
1266 str(lutResolutionShaper)]
1267 args += ["--cubesize", str(lutResolution3d)]
1269 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1270 bakedDir, odtName, inputspace)]
1271 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1272 args=(args + fargs))
1275 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1276 bakedDir, odtName, inputspace)]
1277 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1278 args=(args + largs))
1282 for inputspace in ["ACEScg", "ACES2065-1"]:
1283 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1284 args += ["--outputspace", "%s" % odtName]
1285 args += ["--description",
1286 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1287 if inputspace == 'ACEScg':
1288 linShaperName = "%s - AP1" % shaperName
1290 linShaperName = shaperName
1291 args += ["--shaperspace", linShaperName, "--shapersize",
1292 str(lutResolutionShaper)]
1294 args += ["--cubesize", str(lutResolution3d)]
1296 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1297 bakedDir, odtName, inputspace)]
1298 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1299 args=(args + margs))
1302 hargs = ["--format", "houdini",
1303 "%s/houdini/%s for %s Houdini.lut" % (
1304 bakedDir, odtName, inputspace)]
1305 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1306 args=(args + hargs))
1310 def createConfigDir(configDir, bakeSecondaryLUTs):
1311 dirs = [configDir, "%s/luts" % configDir]
1312 if bakeSecondaryLUTs:
1313 dirs.extend(["%s/baked" % configDir,
1314 "%s/baked/flame" % configDir,
1315 "%s/baked/photoshop" % configDir,
1316 "%s/baked/houdini" % configDir,
1317 "%s/baked/lustre" % configDir,
1318 "%s/baked/maya" % configDir])
1321 if not os.path.exists(d):
1325 def getTransformInfo(ctlTransform):
1326 fp = open(ctlTransform, 'rb')
1329 lines = fp.readlines()
1331 # Grab transform ID and User Name
1332 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1333 # print( transformID )
1334 transformUserName = '-'.join(
1335 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1336 1:]).lstrip().rstrip()
1337 transformUserNamePrefix = \
1338 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1339 0].lstrip().rstrip()
1340 # print( transformUserName )
1343 return (transformID, transformUserName, transformUserNamePrefix)
1346 # For versions after WGR9
1347 def getODTInfo(acesCTLReleaseDir):
1348 # Credit to Alex Fry for the original approach here
1349 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1351 for dirName, subdirList, fileList in os.walk(odtDir):
1352 for fname in fileList:
1353 allodt.append((os.path.join(dirName, fname)))
1355 odtCTLs = [x for x in allodt if
1356 ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1362 for odtCTL in odtCTLs:
1363 odtTokens = os.path.split(odtCTL)
1364 # print( odtTokens )
1366 # Handle nested directories
1367 odtPathTokens = os.path.split(odtTokens[-2])
1368 odtDir = odtPathTokens[-1]
1369 while odtPathTokens[-2][-3:] != 'odt':
1370 odtPathTokens = os.path.split(odtPathTokens[-2])
1371 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1374 # print( "odtDir : %s" % odtDir )
1375 transformCTL = odtTokens[-1]
1376 # print( transformCTL )
1377 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1380 # Find id, user name and user name prefix
1381 (transformID, transformUserName,
1382 transformUserNamePrefix) = getTransformInfo(
1383 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1386 transformCTLInverse = "InvODT.%s.ctl" % odtName
1387 if not os.path.exists(
1388 os.path.join(odtTokens[-2], transformCTLInverse)):
1389 transformCTLInverse = None
1390 #print( transformCTLInverse )
1392 # Add to list of ODTs
1394 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1395 if transformCTLInverse != None:
1396 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir,
1397 transformCTLInverse)
1399 odts[odtName]['transformID'] = transformID
1400 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1401 odts[odtName]['transformUserName'] = transformUserName
1403 print( "ODT : %s" % odtName )
1404 print( "\tTransform ID : %s" % transformID )
1405 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1406 print( "\tTransform User Name : %s" % transformUserName )
1408 "\tForward ctl : %s" % odts[odtName][
1410 if 'transformCTLInverse' in odts[odtName]:
1411 print("\tInverse ctl : %s" % odts[odtName][
1412 'transformCTLInverse'])
1414 print( "\tInverse ctl : %s" % "None" )
1421 # For versions after WGR9
1422 def getLMTInfo(acesCTLReleaseDir):
1423 # Credit to Alex Fry for the original approach here
1424 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1426 for dirName, subdirList, fileList in os.walk(lmtDir):
1427 for fname in fileList:
1428 alllmt.append((os.path.join(dirName, fname)))
1430 lmtCTLs = [x for x in alllmt if
1431 ("InvLMT" not in x) and ("README" not in x) and (
1432 os.path.split(x)[-1][0] != '.')]
1438 for lmtCTL in lmtCTLs:
1439 lmtTokens = os.path.split(lmtCTL)
1440 # print( lmtTokens )
1442 # Handle nested directories
1443 lmtPathTokens = os.path.split(lmtTokens[-2])
1444 lmtDir = lmtPathTokens[-1]
1445 while lmtPathTokens[-2][-3:] != 'ctl':
1446 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1447 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1450 # print( "lmtDir : %s" % lmtDir )
1451 transformCTL = lmtTokens[-1]
1452 # print( transformCTL )
1453 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1456 # Find id, user name and user name prefix
1457 (transformID, transformUserName,
1458 transformUserNamePrefix) = getTransformInfo(
1459 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1462 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1463 if not os.path.exists(
1464 os.path.join(lmtTokens[-2], transformCTLInverse)):
1465 transformCTLInverse = None
1466 #print( transformCTLInverse )
1468 # Add to list of LMTs
1470 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1471 if transformCTLInverse != None:
1472 # TODO: Check unresolved *odtName* referemce.
1473 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir,
1474 transformCTLInverse)
1476 lmts[lmtName]['transformID'] = transformID
1477 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1478 lmts[lmtName]['transformUserName'] = transformUserName
1480 print( "LMT : %s" % lmtName )
1481 print( "\tTransform ID : %s" % transformID )
1482 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1483 print( "\tTransform User Name : %s" % transformUserName )
1484 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1485 if 'transformCTLInverse' in lmts[lmtName]:
1487 "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1489 print( "\t Inverse ctl : %s" % "None" )
1497 # Create the ACES config
1499 def createACESConfig(acesCTLReleaseDir,
1501 lutResolution1d=4096,
1503 bakeSecondaryLUTs=True,
1505 # Get ODT names and CTL paths
1506 odtInfo = getODTInfo(acesCTLReleaseDir)
1508 # Get ODT names and CTL paths
1509 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1512 createConfigDir(configDir, bakeSecondaryLUTs)
1514 # Generate config data and LUTs for different transforms
1515 lutDir = "%s/luts" % configDir
1516 shaperName = 'Output Shaper'
1517 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir,
1518 lutDir, lutResolution1d, lutResolution3d,
1521 # Create the config using the generated LUTs
1522 print( "Creating generic config")
1523 config = createConfig(configData)
1526 # Write the config to disk
1527 writeConfig(config, "%s/config.ocio" % configDir)
1529 # Create a config that will work well with Nuke using the previously generated LUTs
1530 print( "Creating Nuke-specific config")
1531 nuke_config = createConfig(configData, nuke=True)
1534 # Write the config to disk
1535 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1537 # Bake secondary LUTs using the config
1538 if bakeSecondaryLUTs:
1539 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir,
1540 "%s/config.ocio" % configDir, lutResolution1d,
1541 lutResolution3d, lutResolution1d)
1552 p = optparse.OptionParser(description='An OCIO config generation script',
1553 prog='createACESConfig',
1554 version='createACESConfig 0.1',
1555 usage='%prog [options]')
1556 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1557 'ACES_OCIO_CTL_DIRECTORY', None))
1558 p.add_option('--configDir', '-c', default=os.environ.get(
1559 'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1560 p.add_option('--lutResolution1d', default=4096)
1561 p.add_option('--lutResolution3d', default=64)
1562 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1563 p.add_option('--keepTempImages', action="store_true")
1565 options, arguments = p.parse_args()
1570 acesCTLDir = options.acesCTLDir
1571 configDir = options.configDir
1572 lutResolution1d = int(options.lutResolution1d)
1573 lutResolution3d = int(options.lutResolution3d)
1574 bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1575 cleanupTempImages = not (options.keepTempImages)
1578 argsStart = sys.argv.index('--') + 1
1579 args = sys.argv[argsStart:]
1581 argsStart = len(sys.argv) + 1
1584 print( "command line : \n%s\n" % " ".join(sys.argv) )
1586 # TODO: Use assertion and mention environment variables.
1588 print( "process: No ACES CTL directory specified" )
1591 print( "process: No configuration directory specified" )
1594 # Generate the configuration
1596 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d,
1597 bakeSecondaryLUTs, cleanupTempImages)
1601 if __name__ == '__main__':