2 # -*- coding: utf-8 -*-
8 sys.path.append("/path/to/script")
9 import create_aces_config as cac
10 acesReleaseCTLDir = "/path/to/github/checkout/releases/v0.7.1/transforms/ctl"
11 configDir = "/path/to/config/dir"
12 cac.createACESConfig(acesReleaseCTLDir, configDir, 1024, 33, True)
14 usage from command line, from the directory with 'create_aces_config.py'
15 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
19 build instructions for osx for needed packages.
22 brew install -vd opencolorio --with-python
25 brew tap homebrew/science
28 brew install -vd libRaw
29 brew install -vd OpenCV
31 brew install -vd openimageio --with-python
37 # this time, 'ociolutimage' will build because openimageio is installed
38 brew uninstall -vd opencolorio
39 brew install -vd opencolorio --with-python
49 # TODO: This restores the capability of running the script without having
50 # added the package to PYTHONPATH, this is ugly and should ideally replaced by
51 # dedicated executable in a /bin directory.
52 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
54 import PyOpenColorIO as OCIO
56 import aces_ocio.createARRIColorSpaces as arri
57 import aces_ocio.createCanonColorSpaces as canon
58 import aces_ocio.createREDColorSpaces as red
59 import aces_ocio.createSonyColorSpaces as sony
60 import aces_ocio.generateLUT as genlut
61 from aces_ocio.process import Process
62 from aces_ocio.util import ColorSpace, mat44FromMat33
64 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
65 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
70 def setConfigDefaultRoles(config,
82 config.setRole(OCIO.Constants.ROLE_COLOR_PICKING, color_picking)
84 config.setRole(OCIO.Constants.ROLE_COLOR_TIMING, color_timing)
86 config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log)
88 config.setRole(OCIO.Constants.ROLE_DATA, data)
90 config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
92 config.setRole(OCIO.Constants.ROLE_MATTE_PAINT, matte_paint)
94 config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
96 config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear)
98 config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint)
101 # Write config to disk
102 def writeConfig(config, configPath, sanityCheck=True):
108 print "Configuration was not written due to a failed Sanity Check"
112 fileHandle = open(configPath, mode='w')
113 fileHandle.write(config.serialize())
117 def generateOCIOTransform(transforms):
118 # print("Generating transforms")
120 interpolationOptions = {
121 'linear': OCIO.Constants.INTERP_LINEAR,
122 'nearest': OCIO.Constants.INTERP_NEAREST,
123 'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
126 'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
127 'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
132 for transform in transforms:
133 if transform['type'] == 'lutFile':
134 ocioTransform = OCIO.FileTransform(
135 src=transform['path'],
136 interpolation=interpolationOptions[transform['interpolation']],
137 direction=directionOptions[transform['direction']])
138 ocioTransforms.append(ocioTransform)
139 elif transform['type'] == 'matrix':
140 ocioTransform = OCIO.MatrixTransform()
141 # MatrixTransform member variables can't be initialized directly.
142 # Each must be set individually.
143 ocioTransform.setMatrix(transform['matrix'])
145 if 'offset' in transform:
146 ocioTransform.setOffset(transform['offset'])
148 if 'direction' in transform:
149 ocioTransform.setDirection(
150 directionOptions[transform['direction']])
152 ocioTransforms.append(ocioTransform)
153 elif transform['type'] == 'exponent':
154 ocioTransform = OCIO.ExponentTransform()
155 ocioTransform.setValue(transform['value'])
156 ocioTransforms.append(ocioTransform)
157 elif transform['type'] == 'log':
158 ocioTransform = OCIO.LogTransform(
159 base=transform['base'],
160 direction=directionOptions[transform['direction']])
162 ocioTransforms.append(ocioTransform)
164 print("Ignoring unknown transform type : %s" % transform['type'])
166 # Build a group transform if necessary
167 if len(ocioTransforms) > 1:
168 transformG = OCIO.GroupTransform()
169 for transform in ocioTransforms:
170 transformG.push_back(transform)
171 transform = transformG
173 # Or take the first transform from the list
175 transform = ocioTransforms[0]
180 def createConfig(configData, nuke=False):
182 config = OCIO.Config()
185 # Set config wide values
187 config.setDescription("An ACES config generated from python")
188 config.setSearchPath("luts")
191 # Define the reference color space
193 referenceData = configData['referenceColorSpace']
194 print("Adding the reference color space : %s" % referenceData.name)
196 # Create a color space
197 reference = OCIO.ColorSpace(
198 name=referenceData.name,
199 bitDepth=referenceData.bitDepth,
200 description=referenceData.description,
201 equalityGroup=referenceData.equalityGroup,
202 family=referenceData.family,
203 isData=referenceData.isData,
204 allocation=referenceData.allocationType,
205 allocationVars=referenceData.allocationVars)
208 config.addColorSpace(reference)
211 # Create the rest of the color spaces
213 for colorspace in sorted(configData['colorSpaces']):
214 print("Creating new color space : %s" % colorspace.name)
216 ocioColorspace = OCIO.ColorSpace(
217 name=colorspace.name,
218 bitDepth=colorspace.bitDepth,
219 description=colorspace.description,
220 equalityGroup=colorspace.equalityGroup,
221 family=colorspace.family,
222 isData=colorspace.isData,
223 allocation=colorspace.allocationType,
224 allocationVars=colorspace.allocationVars)
226 if colorspace.toReferenceTransforms != []:
227 print("Generating To-Reference transforms")
228 ocioTransform = generateOCIOTransform(
229 colorspace.toReferenceTransforms)
230 ocioColorspace.setTransform(
232 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
234 if colorspace.fromReferenceTransforms != []:
235 print("Generating From-Reference transforms")
236 ocioTransform = generateOCIOTransform(
237 colorspace.fromReferenceTransforms)
238 ocioColorspace.setTransform(
240 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
242 config.addColorSpace(ocioColorspace)
247 # Define the views and displays
252 # Generic display and view setup
254 for display, viewList in configData['displays'].iteritems():
255 for viewName, colorspace in viewList.iteritems():
256 config.addDisplay(display, viewName, colorspace.name)
257 if not (viewName in views):
258 views.append(viewName)
259 displays.append(display)
260 # A Nuke specific set of views and displays
263 # A few names: Output Transform, ACES, ACEScc, are hard-coded here.
264 # Would be better to automate.
267 for display, viewList in configData['displays'].iteritems():
268 for viewName, colorspace in viewList.iteritems():
269 if (viewName == 'Output Transform'):
271 config.addDisplay(display, viewName, colorspace.name)
272 if not (viewName in views):
273 views.append(viewName)
274 displays.append(display)
276 config.addDisplay('linear', 'View', 'ACES2065-1')
277 displays.append('linear')
278 config.addDisplay('log', 'View', 'ACEScc')
279 displays.append('log')
281 # Set active displays and views
282 config.setActiveDisplays(','.join(sorted(displays)))
283 config.setActiveViews(','.join(views))
286 # Need to generalize this at some point
290 setConfigDefaultRoles(config,
291 color_picking=reference.getName(),
292 color_timing=reference.getName(),
293 compositing_log=reference.getName(),
294 data=reference.getName(),
295 default=reference.getName(),
296 matte_paint=reference.getName(),
297 reference=reference.getName(),
298 scene_linear=reference.getName(),
299 texture_paint=reference.getName())
301 # Check to make sure we didn't screw something up
308 # Functions to generate color space definitions and LUTs for transforms for a
309 # specific ACES release.
312 # Output is a list of colorspaces and transforms that convert between those
313 # colorspaces and reference color space, ACES
314 def generateLUTs(odtInfo,
319 lutResolution1d=4096,
322 print("generateLUTs - begin")
326 # Define the reference color space
328 ACES = ColorSpace('ACES2065-1')
330 'The Academy Color Encoding System reference color space')
331 ACES.equalityGroup = ''
334 ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
335 ACES.allocationVars = [-15, 6]
337 configData['referenceColorSpace'] = ACES
340 # Define the displays
342 configData['displays'] = {}
345 # Define the other color spaces
347 configData['colorSpaces'] = []
349 # Matrix converting ACES AP1 primaries to AP0
350 acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
351 0.0447945634, 0.8596711185, 0.0955343182,
352 -0.0055258826, 0.0040252103, 1.0015006723]
354 # Matrix converting ACES AP0 primaries to XYZ
355 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
356 0.3439664498, 0.7281660966, -0.0721325464,
357 0.0000000000, 0.0000000000, 1.0088251844]
362 def createACEScc(name='ACEScc',
366 cs = ColorSpace(name)
367 cs.description = "The %s color space" % name
368 cs.equalityGroup = ''
373 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
374 # This transform gets back to the AP1 primaries
375 # Useful as the 1d LUT is only covering the transfer function
376 # The primaries switch is covered by the matrix below
377 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
379 lut = "%s_to_ACES.spi1d" % name
381 # Remove spaces and parentheses
382 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
384 genlut.generate1dLUTFromCTL(
397 cs.toReferenceTransforms = []
398 cs.toReferenceTransforms.append({
401 'interpolation': 'linear',
402 'direction': 'forward'
405 # AP1 primaries to AP0 primaries
406 cs.toReferenceTransforms.append({
408 'matrix': mat44FromMat33(acesAP1toAP0),
409 'direction': 'forward'
412 cs.fromReferenceTransforms = []
415 ACEScc = createACEScc()
416 configData['colorSpaces'].append(ACEScc)
421 def createACESProxy(name='ACESproxy'):
422 cs = ColorSpace(name)
423 cs.description = "The %s color space" % name
424 cs.equalityGroup = ''
429 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % (
431 # This transform gets back to the AP1 primaries
432 # Useful as the 1d LUT is only covering the transfer function
433 # The primaries switch is covered by the matrix below
434 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
436 lut = "%s_to_aces.spi1d" % name
438 # Remove spaces and parentheses
439 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
441 genlut.generate1dLUTFromCTL(
452 cs.toReferenceTransforms = []
453 cs.toReferenceTransforms.append({
456 'interpolation': 'linear',
457 'direction': 'forward'
460 # AP1 primaries to AP0 primaries
461 cs.toReferenceTransforms.append({
463 'matrix': mat44FromMat33(acesAP1toAP0),
464 'direction': 'forward'
467 cs.fromReferenceTransforms = []
470 ACESproxy = createACESProxy()
471 configData['colorSpaces'].append(ACESproxy)
476 def createACEScg(name='ACEScg'):
477 cs = ColorSpace(name)
478 cs.description = "The %s color space" % name
479 cs.equalityGroup = ''
483 cs.toReferenceTransforms = []
485 # AP1 primaries to AP0 primaries
486 cs.toReferenceTransforms.append({
488 'matrix': mat44FromMat33(acesAP1toAP0),
489 'direction': 'forward'
492 cs.fromReferenceTransforms = []
495 ACEScg = createACEScg()
496 configData['colorSpaces'].append(ACEScg)
501 def createADX(bitdepth=10, name='ADX'):
502 name = "%s%s" % (name, bitdepth)
503 cs = ColorSpace(name)
504 cs.description = "%s color space - used for film scans" % name
505 cs.equalityGroup = ''
510 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
511 adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
512 0.0, 1023.0 / 500.0, 0.0, 0.0,
513 0.0, 0.0, 1023.0 / 500.0, 0.0,
515 offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
517 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
518 adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
519 0.0, 65535.0 / 8000.0, 0.0, 0.0,
520 0.0, 0.0, 65535.0 / 8000.0, 0.0,
522 offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
525 cs.toReferenceTransforms = []
527 # Convert from ADX to Channel-Dependent Density
528 cs.toReferenceTransforms.append({
530 'matrix': adx_to_cdd,
532 'direction': 'forward'
535 # Convert from Channel-Dependent Density to Channel-Independent Density
536 cs.toReferenceTransforms.append({
538 'matrix': [0.75573, 0.22197, 0.02230, 0,
539 0.05901, 0.96928, -0.02829, 0,
540 0.16134, 0.07406, 0.76460, 0,
542 'direction': 'forward'
545 # Copied from Alex Fry's adx_cid_to_rle.py
546 def createCIDtoRLELUT():
547 def interpolate1D(x, xp, fp):
548 return numpy.interp(x, xp, fp)
550 LUT_1D_xp = [-0.190000000000000,
562 LUT_1D_fp = [-6.000000000000000,
574 REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
575 math.log(0.18, 10.0))
579 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
580 return (100.0 / 55.0) * x - REF_PT
582 def Fit(value, fromMin, fromMax, toMin, toMax):
583 if fromMin == fromMax:
584 raise ValueError("fromMin == fromMax")
585 return (value - fromMin) / (fromMax - fromMin) * (
586 toMax - toMin) + toMin
588 NUM_SAMPLES = 2 ** 12
591 for i in xrange(NUM_SAMPLES):
592 x = i / (NUM_SAMPLES - 1.0)
593 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
594 data.append(cid_to_rle(x))
596 lut = 'ADX_CID_to_RLE.spi1d'
597 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
602 # Convert Channel Independent Density values to Relative Log Exposure
604 lut = createCIDtoRLELUT()
605 cs.toReferenceTransforms.append({
608 'interpolation': 'linear',
609 'direction': 'forward'
612 # Convert Relative Log Exposure values to Relative Exposure values
613 cs.toReferenceTransforms.append({
616 'direction': 'inverse'
619 # Convert Relative Exposure values to ACES values
620 cs.toReferenceTransforms.append({
622 'matrix': [0.72286, 0.12630, 0.15084, 0,
623 0.11923, 0.76418, 0.11659, 0,
624 0.01427, 0.08213, 0.90359, 0,
626 'direction': 'forward'
629 cs.fromReferenceTransforms = []
632 ADX10 = createADX(bitdepth=10)
633 configData['colorSpaces'].append(ADX10)
635 ADX16 = createADX(bitdepth=16)
636 configData['colorSpaces'].append(ADX16)
639 # Camera Input Transforms
642 # RED color spaces to ACES
643 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
644 for cs in redColorSpaces:
645 configData['colorSpaces'].append(cs)
648 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
649 for cs in canonColorSpaces:
650 configData['colorSpaces'].append(cs)
653 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
654 for cs in sonyColorSpaces:
655 configData['colorSpaces'].append(cs)
658 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
659 for cs in arriColorSpaces:
660 configData['colorSpaces'].append(cs)
663 # Generic log transform
665 def createGenericLog(name='log',
672 lutResolution1d=lutResolution1d):
673 cs = ColorSpace(name)
674 cs.description = "The %s color space" % name
675 cs.equalityGroup = name
676 cs.family = 'Utility'
680 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % (
683 lut = "%s_to_aces.spi1d" % name
685 # Remove spaces and parentheses
686 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
688 genlut.generate1dLUTFromCTL(
696 'middleGrey': middleGrey,
697 'minExposure': minExposure,
698 'maxExposure': maxExposure
705 cs.toReferenceTransforms = []
706 cs.toReferenceTransforms.append({
709 'interpolation': 'linear',
710 'direction': 'forward'
713 cs.fromReferenceTransforms = []
719 def createACESLMT(lmtName,
722 lutResolution1d=1024,
725 cs = ColorSpace("%s" % lmtName)
726 cs.description = "The ACES Look Transform: %s" % lmtName
727 cs.equalityGroup = ''
733 pprint.pprint(lmtValues)
736 # Generate the shaper transform
742 shaperParams) = shaperInfo
744 shaperLut = "%s_to_aces.spi1d" % shaperName
745 if (not os.path.exists(lutDir + "/" + shaperLut)):
747 shaperToACESCTL % acesCTLReleaseDir
750 # Remove spaces and parentheses
751 shaperLut = shaperLut.replace(
752 ' ', '_').replace(')', '_').replace('(', '_')
754 genlut.generate1dLUTFromCTL(
755 lutDir + "/" + shaperLut,
759 1.0 / shaperInputScale,
765 shaperOCIOTransform = {
768 'interpolation': 'linear',
769 'direction': 'inverse'
773 # Generate the forward transform
775 cs.fromReferenceTransforms = []
777 if 'transformCTL' in lmtValues:
779 shaperToACESCTL % acesCTLReleaseDir,
780 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
782 lut = "%s.%s.spi3d" % (shaperName, lmtName)
784 # Remove spaces and parentheses
785 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
787 genlut.generate3dLUTFromCTL(
792 1.0 / shaperInputScale,
798 cs.fromReferenceTransforms.append(shaperOCIOTransform)
799 cs.fromReferenceTransforms.append({
802 'interpolation': 'tetrahedral',
803 'direction': 'forward'
807 # Generate the inverse transform
809 cs.toReferenceTransforms = []
811 if 'transformCTLInverse' in lmtValues:
814 acesCTLReleaseDir, odtValues['transformCTLInverse']),
815 shaperFromACESCTL % acesCTLReleaseDir
817 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
819 # Remove spaces and parentheses
820 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
822 genlut.generate3dLUTFromCTL(
833 cs.toReferenceTransforms.append({
836 'interpolation': 'tetrahedral',
837 'direction': 'forward'
840 shaperInverse = shaperOCIOTransform.copy()
841 shaperInverse['direction'] = 'forward'
842 cs.toReferenceTransforms.append(shaperInverse)
850 lmtLutResolution1d = max(4096, lutResolution1d)
851 lmtLutResolution3d = max(65, lutResolution3d)
854 lmtShaperName = 'LMT Shaper'
857 'minExposure': -10.0,
860 lmtShaper = createGenericLog(name=lmtShaperName,
861 middleGrey=lmtParams['middleGrey'],
862 minExposure=lmtParams['minExposure'],
863 maxExposure=lmtParams['maxExposure'],
864 lutResolution1d=lmtLutResolution1d)
865 configData['colorSpaces'].append(lmtShaper)
867 shaperInputScale_genericLog2 = 1.0
869 # Log 2 shaper name and CTL transforms bundled up
872 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
873 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
874 shaperInputScale_genericLog2,
878 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
880 for lmt in sortedLMTs:
881 (lmtName, lmtValues) = lmt
883 lmtValues['transformUserName'],
889 configData['colorSpaces'].append(cs)
892 # ACES RRT with the supplied ODT
894 def createACESRRTplusODT(odtName,
897 lutResolution1d=1024,
900 cs = ColorSpace("%s" % odtName)
901 cs.description = "%s - %s Output Transform" % (
902 odtValues['transformUserNamePrefix'], odtName)
903 cs.equalityGroup = ''
909 pprint.pprint(odtValues)
912 # Generate the shaper transform
914 # if 'shaperCTL' in odtValues:
919 shaperParams) = shaperInfo
921 if 'legalRange' in odtValues:
922 shaperParams['legalRange'] = odtValues['legalRange']
924 shaperParams['legalRange'] = 0
926 shaperLut = "%s_to_aces.spi1d" % shaperName
927 if (not os.path.exists(lutDir + "/" + shaperLut)):
929 shaperToACESCTL % acesCTLReleaseDir
932 # Remove spaces and parentheses
933 shaperLut = shaperLut.replace(
934 ' ', '_').replace(')', '_').replace('(', '_')
936 genlut.generate1dLUTFromCTL(
937 lutDir + "/" + shaperLut,
941 1.0 / shaperInputScale,
947 shaperOCIOTransform = {
950 'interpolation': 'linear',
951 'direction': 'inverse'
955 # Generate the forward transform
957 cs.fromReferenceTransforms = []
959 if 'transformLUT' in odtValues:
960 # Copy into the lut dir
961 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
962 lut = lutDir + "/" + transformLUTFileName
963 shutil.copy(odtValues['transformLUT'], lut)
965 cs.fromReferenceTransforms.append(shaperOCIOTransform)
966 cs.fromReferenceTransforms.append({
968 'path': transformLUTFileName,
969 'interpolation': 'tetrahedral',
970 'direction': 'forward'
972 elif 'transformCTL' in odtValues:
976 shaperToACESCTL % acesCTLReleaseDir,
977 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
978 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
980 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
982 # Remove spaces and parentheses
983 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
985 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
990 1.0 / shaperInputScale,
996 cs.fromReferenceTransforms.append(shaperOCIOTransform)
997 cs.fromReferenceTransforms.append({
1000 'interpolation': 'tetrahedral',
1001 'direction': 'forward'
1005 # Generate the inverse transform
1007 cs.toReferenceTransforms = []
1009 if 'transformLUTInverse' in odtValues:
1010 # Copy into the lut dir
1011 transformLUTInverseFileName = os.path.basename(
1012 odtValues['transformLUTInverse'])
1013 lut = lutDir + "/" + transformLUTInverseFileName
1014 shutil.copy(odtValues['transformLUTInverse'], lut)
1016 cs.toReferenceTransforms.append({
1018 'path': transformLUTInverseFileName,
1019 'interpolation': 'tetrahedral',
1020 'direction': 'forward'
1023 shaperInverse = shaperOCIOTransform.copy()
1024 shaperInverse['direction'] = 'forward'
1025 cs.toReferenceTransforms.append(shaperInverse)
1026 elif 'transformCTLInverse' in odtValues:
1029 acesCTLReleaseDir, odtValues['transformCTLInverse']),
1030 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1031 shaperFromACESCTL % acesCTLReleaseDir
1033 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1035 # Remove spaces and parentheses
1036 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1038 genlut.generate3dLUTFromCTL(
1050 cs.toReferenceTransforms.append({
1053 'interpolation': 'tetrahedral',
1054 'direction': 'forward'
1057 shaperInverse = shaperOCIOTransform.copy()
1058 shaperInverse['direction'] = 'forward'
1059 cs.toReferenceTransforms.append(shaperInverse)
1064 # RRT/ODT shaper options
1069 log2ShaperName = shaperName
1072 'minExposure': -6.0,
1075 log2Shaper = createGenericLog(name=log2ShaperName,
1076 middleGrey=log2Params['middleGrey'],
1077 minExposure=log2Params['minExposure'],
1078 maxExposure=log2Params['maxExposure'])
1079 configData['colorSpaces'].append(log2Shaper)
1081 shaperInputScale_genericLog2 = 1.0
1083 # Log 2 shaper name and CTL transforms bundled up
1086 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1087 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1088 shaperInputScale_genericLog2,
1092 shaperData[log2ShaperName] = log2ShaperData
1095 # Shaper that also includes the AP1 primaries
1096 # - Needed for some LUT baking steps
1098 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1099 middleGrey=log2Params['middleGrey'],
1100 minExposure=log2Params['minExposure'],
1101 maxExposure=log2Params['maxExposure'])
1102 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1103 # AP1 primaries to AP0 primaries
1104 log2ShaperAP1.toReferenceTransforms.append({
1106 'matrix': mat44FromMat33(acesAP1toAP0),
1107 'direction': 'forward'
1109 configData['colorSpaces'].append(log2ShaperAP1)
1112 # Choose your shaper
1114 rrtShaperName = log2ShaperName
1115 rrtShaper = log2ShaperData
1118 # RRT + ODT Combinations
1120 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1122 for odt in sortedOdts:
1123 (odtName, odtValues) = odt
1125 # Have to handle ODTs that can generate either legal or full output
1126 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1127 'Academy.Rec709_100nits_dim.a1.0.0',
1128 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1129 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1131 odtNameLegal = odtValues['transformUserName']
1133 odtLegal = odtValues.copy()
1134 odtLegal['legalRange'] = 1
1136 cs = createACESRRTplusODT(
1143 configData['colorSpaces'].append(cs)
1145 # Create a display entry using this color space
1146 configData['displays'][odtNameLegal] = {
1149 'Output Transform': cs}
1151 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1152 'Academy.Rec709_100nits_dim.a1.0.0',
1153 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1154 print("Generating full range ODT for %s" % odtName)
1156 odtNameFull = "%s - Full" % odtValues['transformUserName']
1157 odtFull = odtValues.copy()
1158 odtFull['legalRange'] = 0
1160 csFull = createACESRRTplusODT(
1167 configData['colorSpaces'].append(csFull)
1169 # Create a display entry using this color space
1170 configData['displays'][odtNameFull] = {
1173 'Output Transform': csFull}
1176 # Generic Matrix transform
1178 def createGenericMatrix(name='matrix',
1179 fromReferenceValues=[],
1180 toReferenceValues=[]):
1181 cs = ColorSpace(name)
1182 cs.description = "The %s color space" % name
1183 cs.equalityGroup = name
1184 cs.family = 'Utility'
1187 cs.toReferenceTransforms = []
1188 if toReferenceValues != []:
1189 for matrix in toReferenceValues:
1190 cs.toReferenceTransforms.append({
1192 'matrix': mat44FromMat33(matrix),
1193 'direction': 'forward'
1196 cs.fromReferenceTransforms = []
1197 if fromReferenceValues != []:
1198 for matrix in fromReferenceValues:
1199 cs.fromReferenceTransforms.append({
1201 'matrix': mat44FromMat33(matrix),
1202 'direction': 'forward'
1207 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1208 configData['colorSpaces'].append(cs)
1210 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1211 configData['colorSpaces'].append(cs)
1213 # ACES to Linear, P3D60 primaries
1214 xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1215 -0.8325796487, 1.7692317536, 0.0237127115,
1216 0.0388233815, -0.0824996856, 1.0363685997]
1218 cs = createGenericMatrix('Linear - P3-D60',
1219 fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1220 configData['colorSpaces'].append(cs)
1222 # ACES to Linear, P3D60 primaries
1223 xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1224 -0.7951680258, 1.6897320548, 0.0226471906,
1225 0.0412418914, -0.0876390192, 1.1009293786]
1227 cs = createGenericMatrix('Linear - P3-DCI',
1228 fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1229 configData['colorSpaces'].append(cs)
1231 # ACES to Linear, Rec 709 primaries
1232 xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1233 -0.9692436363, 1.8759675015, 0.0415550574,
1234 0.0556300797, -0.2039769589, 1.0569715142]
1236 cs = createGenericMatrix('Linear - Rec.709',
1237 fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1238 configData['colorSpaces'].append(cs)
1240 # ACES to Linear, Rec 2020 primaries
1241 xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1242 -0.6666843518, 1.6164812366, 0.0157685458,
1243 0.0176398574, -0.0427706133, 0.9421031212]
1245 cs = createGenericMatrix('Linear - Rec.2020',
1246 fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1247 configData['colorSpaces'].append(cs)
1249 print("generateLUTs - end")
1253 def generateBakedLUTs(odtInfo,
1259 lutResolutionShaper=1024):
1260 # Add the legal and full variations into this list
1261 odtInfoC = dict(odtInfo)
1262 for odtCTLName, odtValues in odtInfo.iteritems():
1263 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1264 'Academy.Rec709_100nits_dim.a1.0.0',
1265 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1266 odtName = odtValues["transformUserName"]
1268 odtValuesLegal = dict(odtValues)
1269 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1270 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1272 odtValuesFull = dict(odtValues)
1273 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1274 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1276 del (odtInfoC[odtCTLName])
1278 for odtCTLName, odtValues in odtInfoC.iteritems():
1279 odtPrefix = odtValues["transformUserNamePrefix"]
1280 odtName = odtValues["transformUserName"]
1283 for inputspace in ["ACEScc", "ACESproxy"]:
1284 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1285 args += ["--outputspace", "%s" % odtName]
1286 args += ["--description",
1287 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1288 args += ["--shaperspace", shaperName, "--shapersize",
1289 str(lutResolutionShaper)]
1290 args += ["--cubesize", str(lutResolution3d)]
1291 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1292 bakedDir, odtName, inputspace)]
1294 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1299 for inputspace in ["ACEScc", "ACESproxy"]:
1300 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1301 args += ["--outputspace", "%s" % odtName]
1302 args += ["--description",
1303 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1304 args += ["--shaperspace", shaperName, "--shapersize",
1305 str(lutResolutionShaper)]
1306 args += ["--cubesize", str(lutResolution3d)]
1308 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1309 bakedDir, odtName, inputspace)]
1310 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1311 args=(args + fargs))
1314 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1315 bakedDir, odtName, inputspace)]
1316 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1317 args=(args + largs))
1321 for inputspace in ["ACEScg", "ACES2065-1"]:
1322 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1323 args += ["--outputspace", "%s" % odtName]
1324 args += ["--description",
1325 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1326 if inputspace == 'ACEScg':
1327 linShaperName = "%s - AP1" % shaperName
1329 linShaperName = shaperName
1330 args += ["--shaperspace", linShaperName, "--shapersize",
1331 str(lutResolutionShaper)]
1333 args += ["--cubesize", str(lutResolution3d)]
1335 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1336 bakedDir, odtName, inputspace)]
1337 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1338 args=(args + margs))
1341 hargs = ["--format", "houdini",
1342 "%s/houdini/%s for %s Houdini.lut" % (
1343 bakedDir, odtName, inputspace)]
1344 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1345 args=(args + hargs))
1349 def createConfigDir(configDir, bakeSecondaryLUTs):
1350 dirs = [configDir, "%s/luts" % configDir]
1351 if bakeSecondaryLUTs:
1352 dirs.extend(["%s/baked" % configDir,
1353 "%s/baked/flame" % configDir,
1354 "%s/baked/photoshop" % configDir,
1355 "%s/baked/houdini" % configDir,
1356 "%s/baked/lustre" % configDir,
1357 "%s/baked/maya" % configDir])
1360 if not os.path.exists(d):
1364 def getTransformInfo(ctlTransform):
1365 fp = open(ctlTransform, 'rb')
1368 lines = fp.readlines()
1370 # Grab transform ID and User Name
1371 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1372 # print(transformID)
1373 transformUserName = '-'.join(
1374 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1375 1:]).lstrip().rstrip()
1376 transformUserNamePrefix = \
1377 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1378 0].lstrip().rstrip()
1379 # print(transformUserName)
1382 return transformID, transformUserName, transformUserNamePrefix
1385 # For versions after WGR9
1386 def getODTInfo(acesCTLReleaseDir):
1387 # Credit to Alex Fry for the original approach here
1388 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1390 for dirName, subdirList, fileList in os.walk(odtDir):
1391 for fname in fileList:
1392 allodt.append((os.path.join(dirName, fname)))
1394 odtCTLs = [x for x in allodt if
1395 ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1401 for odtCTL in odtCTLs:
1402 odtTokens = os.path.split(odtCTL)
1405 # Handle nested directories
1406 odtPathTokens = os.path.split(odtTokens[-2])
1407 odtDir = odtPathTokens[-1]
1408 while odtPathTokens[-2][-3:] != 'odt':
1409 odtPathTokens = os.path.split(odtPathTokens[-2])
1410 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1413 # print("odtDir : %s" % odtDir)
1414 transformCTL = odtTokens[-1]
1415 # print(transformCTL)
1416 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1419 # Find id, user name and user name prefix
1420 (transformID, transformUserName,
1421 transformUserNamePrefix) = getTransformInfo(
1422 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1425 transformCTLInverse = "InvODT.%s.ctl" % odtName
1426 if not os.path.exists(
1427 os.path.join(odtTokens[-2], transformCTLInverse)):
1428 transformCTLInverse = None
1429 # print(transformCTLInverse)
1431 # Add to list of ODTs
1433 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1434 if transformCTLInverse != None:
1435 odts[odtName]['transformCTLInverse'] = os.path.join(
1436 odtDir, transformCTLInverse)
1438 odts[odtName]['transformID'] = transformID
1439 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1440 odts[odtName]['transformUserName'] = transformUserName
1442 print("ODT : %s" % odtName)
1443 print("\tTransform ID : %s" % transformID)
1444 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1445 print("\tTransform User Name : %s" % transformUserName)
1446 print("\tForward ctl : %s" % (
1447 odts[odtName]['transformCTL']))
1448 if 'transformCTLInverse' in odts[odtName]:
1449 print("\tInverse ctl : %s" % (
1450 odts[odtName]['transformCTLInverse']))
1452 print("\tInverse ctl : %s" % "None")
1459 # For versions after WGR9
1460 def getLMTInfo(acesCTLReleaseDir):
1461 # Credit to Alex Fry for the original approach here
1462 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1464 for dirName, subdirList, fileList in os.walk(lmtDir):
1465 for fname in fileList:
1466 alllmt.append((os.path.join(dirName, fname)))
1468 lmtCTLs = [x for x in alllmt if
1469 ("InvLMT" not in x) and ("README" not in x) and (
1470 os.path.split(x)[-1][0] != '.')]
1476 for lmtCTL in lmtCTLs:
1477 lmtTokens = os.path.split(lmtCTL)
1480 # Handle nested directories
1481 lmtPathTokens = os.path.split(lmtTokens[-2])
1482 lmtDir = lmtPathTokens[-1]
1483 while lmtPathTokens[-2][-3:] != 'ctl':
1484 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1485 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1488 # print("lmtDir : %s" % lmtDir)
1489 transformCTL = lmtTokens[-1]
1490 # print(transformCTL)
1491 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1494 # Find id, user name and user name prefix
1495 (transformID, transformUserName,
1496 transformUserNamePrefix) = getTransformInfo(
1497 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1500 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1501 if not os.path.exists(
1502 os.path.join(lmtTokens[-2], transformCTLInverse)):
1503 transformCTLInverse = None
1504 # print(transformCTLInverse)
1506 # Add to list of LMTs
1508 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1509 if transformCTLInverse != None:
1510 # TODO: Check unresolved *odtName* referemce.
1511 lmts[odtName]['transformCTLInverse'] = os.path.join(
1512 lmtDir, transformCTLInverse)
1514 lmts[lmtName]['transformID'] = transformID
1515 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1516 lmts[lmtName]['transformUserName'] = transformUserName
1518 print("LMT : %s" % lmtName)
1519 print("\tTransform ID : %s" % transformID)
1520 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1521 print("\tTransform User Name : %s" % transformUserName)
1522 print("\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1523 if 'transformCTLInverse' in lmts[lmtName]:
1524 print("\t Inverse ctl : %s" % (
1525 lmts[lmtName]['transformCTLInverse']))
1527 print("\t Inverse ctl : %s" % "None")
1535 # Create the ACES config
1537 def createACESConfig(acesCTLReleaseDir,
1539 lutResolution1d=4096,
1541 bakeSecondaryLUTs=True,
1543 # Get ODT names and CTL paths
1544 odtInfo = getODTInfo(acesCTLReleaseDir)
1546 # Get ODT names and CTL paths
1547 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1550 createConfigDir(configDir, bakeSecondaryLUTs)
1552 # Generate config data and LUTs for different transforms
1553 lutDir = "%s/luts" % configDir
1554 shaperName = 'Output Shaper'
1555 configData = generateLUTs(odtInfo,
1564 # Create the config using the generated LUTs
1565 print("Creating generic config")
1566 config = createConfig(configData)
1569 # Write the config to disk
1570 writeConfig(config, "%s/config.ocio" % configDir)
1572 # Create a config that will work well with Nuke using the previously
1574 print("Creating Nuke-specific config")
1575 nuke_config = createConfig(configData, nuke=True)
1578 # Write the config to disk
1579 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1581 # Bake secondary LUTs using the config
1582 if bakeSecondaryLUTs:
1583 generateBakedLUTs(odtInfo,
1585 "%s/baked" % configDir,
1586 "%s/config.ocio" % configDir,
1600 p = optparse.OptionParser(description='An OCIO config generation script',
1601 prog='createACESConfig',
1602 version='createACESConfig 0.1',
1603 usage='%prog [options]')
1604 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1605 'ACES_OCIO_CTL_DIRECTORY', None))
1606 p.add_option('--configDir', '-c', default=os.environ.get(
1607 'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1608 p.add_option('--lutResolution1d', default=4096)
1609 p.add_option('--lutResolution3d', default=64)
1610 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1611 p.add_option('--keepTempImages', action="store_true")
1613 options, arguments = p.parse_args()
1618 acesCTLDir = options.acesCTLDir
1619 configDir = options.configDir
1620 lutResolution1d = int(options.lutResolution1d)
1621 lutResolution3d = int(options.lutResolution3d)
1622 bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1623 cleanupTempImages = not (options.keepTempImages)
1626 argsStart = sys.argv.index('--') + 1
1627 args = sys.argv[argsStart:]
1629 argsStart = len(sys.argv) + 1
1632 print("command line : \n%s\n" % " ".join(sys.argv))
1634 # TODO: Use assertion and mention environment variables.
1636 print("process: No ACES CTL directory specified")
1639 print("process: No configuration directory specified")
1642 # Generate the configuration
1644 return createACESConfig(acesCTLDir,
1653 if __name__ == '__main__':