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,
81 if color_picking: config.setRole(OCIO.Constants.ROLE_COLOR_PICKING,
83 if color_timing: config.setRole(OCIO.Constants.ROLE_COLOR_TIMING,
85 if compositing_log: config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG,
87 if data: config.setRole(OCIO.Constants.ROLE_DATA, data)
88 if default: config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
89 if matte_paint: config.setRole(OCIO.Constants.ROLE_MATTE_PAINT,
91 if reference: config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
92 if scene_linear: config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR,
94 if texture_paint: config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT,
98 # Write config to disk
99 def writeConfig(config, configPath, sanityCheck=True):
105 print "Configuration was not written due to a failed Sanity Check"
109 fileHandle = open(configPath, mode='w')
110 fileHandle.write(config.serialize())
114 def generateOCIOTransform(transforms):
115 # print( "Generating transforms")
117 interpolationOptions = {
118 'linear': OCIO.Constants.INTERP_LINEAR,
119 'nearest': OCIO.Constants.INTERP_NEAREST,
120 'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
123 'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
124 'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
129 for transform in transforms:
130 if transform['type'] == 'lutFile':
132 ocioTransform = OCIO.FileTransform(src=transform['path'],
134 interpolationOptions[
135 transform['interpolation']],
136 direction=directionOptions[
137 transform['direction']])
139 ocioTransforms.append(ocioTransform)
140 elif transform['type'] == 'matrix':
141 ocioTransform = OCIO.MatrixTransform()
142 # MatrixTransform member variables can't be initialized directly. Each must be set individually
143 ocioTransform.setMatrix(transform['matrix'])
145 if 'offset' in transform:
146 ocioTransform.setOffset(transform['offset'])
147 if 'direction' in transform:
148 ocioTransform.setDirection(
149 directionOptions[transform['direction']])
151 ocioTransforms.append(ocioTransform)
152 elif transform['type'] == 'exponent':
153 ocioTransform = OCIO.ExponentTransform()
154 ocioTransform.setValue(transform['value'])
156 ocioTransforms.append(ocioTransform)
157 elif transform['type'] == 'log':
158 ocioTransform = OCIO.LogTransform(base=transform['base'],
159 direction=directionOptions[
160 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(name=referenceData.name,
198 bitDepth=referenceData.bitDepth,
199 description=referenceData.description,
200 equalityGroup=referenceData.equalityGroup,
201 family=referenceData.family,
202 isData=referenceData.isData,
203 allocation=referenceData.allocationType,
204 allocationVars=referenceData.allocationVars)
207 config.addColorSpace(reference)
210 # Create the rest of the color spaces
212 for colorspace in sorted(configData['colorSpaces']):
213 print( "Creating new color space : %s" % colorspace.name)
215 ocioColorspace = OCIO.ColorSpace(name=colorspace.name,
216 bitDepth=colorspace.bitDepth,
217 description=colorspace.description,
218 equalityGroup=colorspace.equalityGroup,
219 family=colorspace.family,
220 isData=colorspace.isData,
221 allocation=colorspace.allocationType,
222 allocationVars=colorspace.allocationVars)
224 if colorspace.toReferenceTransforms != []:
225 print( "Generating To-Reference transforms")
226 ocioTransform = generateOCIOTransform(
227 colorspace.toReferenceTransforms)
228 ocioColorspace.setTransform(ocioTransform,
229 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
231 if colorspace.fromReferenceTransforms != []:
232 print( "Generating From-Reference transforms")
233 ocioTransform = generateOCIOTransform(
234 colorspace.fromReferenceTransforms)
235 ocioColorspace.setTransform(ocioTransform,
236 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
238 config.addColorSpace(ocioColorspace)
243 # Define the views and displays
248 # Generic display and view setup
250 for display, viewList in configData['displays'].iteritems():
251 for viewName, colorspace in viewList.iteritems():
252 config.addDisplay(display, viewName, colorspace.name)
253 if not (viewName in views):
254 views.append(viewName)
255 displays.append(display)
256 # A Nuke specific set of views and displays
259 # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
262 for display, viewList in configData['displays'].iteritems():
263 for viewName, colorspace in viewList.iteritems():
264 if ( viewName == 'Output Transform'):
266 config.addDisplay(display, viewName, colorspace.name)
267 if not (viewName in views):
268 views.append(viewName)
269 displays.append(display)
271 config.addDisplay('linear', 'View', 'ACES2065-1')
272 displays.append('linear')
273 config.addDisplay('log', 'View', 'ACEScc')
274 displays.append('log')
276 # Set active displays and views
277 config.setActiveDisplays(','.join(sorted(displays)))
278 config.setActiveViews(','.join(views))
281 # Need to generalize this at some point
285 setConfigDefaultRoles(config,
286 color_picking=reference.getName(),
287 color_timing=reference.getName(),
288 compositing_log=reference.getName(),
289 data=reference.getName(),
290 default=reference.getName(),
291 matte_paint=reference.getName(),
292 reference=reference.getName(),
293 scene_linear=reference.getName(),
294 texture_paint=reference.getName())
296 # Check to make sure we didn't screw something up
303 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
306 # Output is a list of colorspaces and transforms that convert between those
307 # colorspaces and reference color space, ACES
308 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir,
309 lutResolution1d=4096, lutResolution3d=64, cleanup=True):
310 print( "generateLUTs - begin" )
314 # Define the reference color space
316 ACES = ColorSpace('ACES2065-1')
317 ACES.description = "The Academy Color Encoding System reference color space"
318 ACES.equalityGroup = ''
321 ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
322 ACES.allocationVars = [-15, 6]
324 configData['referenceColorSpace'] = ACES
327 # Define the displays
329 configData['displays'] = {}
332 # Define the other color spaces
334 configData['colorSpaces'] = []
336 # Matrix converting ACES AP1 primaries to AP0
337 acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
338 0.0447945634, 0.8596711185, 0.0955343182,
339 -0.0055258826, 0.0040252103, 1.0015006723]
341 # Matrix converting ACES AP0 primaries to XYZ
342 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
343 0.3439664498, 0.7281660966, -0.0721325464,
344 0.0000000000, 0.0000000000, 1.0088251844]
349 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0,
351 cs = ColorSpace(name)
352 cs.description = "The %s color space" % name
353 cs.equalityGroup = ''
358 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
359 # This transform gets back to the AP1 primaries
360 # Useful as the 1d LUT is only covering the transfer function
361 # The primaries switch is covered by the matrix below
362 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
364 lut = "%s_to_ACES.spi1d" % name
366 # Remove spaces and parentheses
367 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
369 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
381 cs.toReferenceTransforms = []
382 cs.toReferenceTransforms.append({
385 'interpolation': 'linear',
386 'direction': 'forward'
389 # AP1 primaries to AP0 primaries
390 cs.toReferenceTransforms.append({
392 'matrix': mat44FromMat33(acesAP1toAP0),
393 'direction': 'forward'
396 cs.fromReferenceTransforms = []
399 ACEScc = createACEScc()
400 configData['colorSpaces'].append(ACEScc)
405 def createACESProxy(name='ACESproxy'):
406 cs = ColorSpace(name)
407 cs.description = "The %s color space" % name
408 cs.equalityGroup = ''
413 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
414 # This transform gets back to the AP1 primaries
415 # Useful as the 1d LUT is only covering the transfer function
416 # The primaries switch is covered by the matrix below
417 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
419 lut = "%s_to_aces.spi1d" % name
421 # Remove spaces and parentheses
422 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
424 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
434 cs.toReferenceTransforms = []
435 cs.toReferenceTransforms.append({
438 'interpolation': 'linear',
439 'direction': 'forward'
442 # AP1 primaries to AP0 primaries
443 cs.toReferenceTransforms.append({
445 'matrix': mat44FromMat33(acesAP1toAP0),
446 'direction': 'forward'
449 cs.fromReferenceTransforms = []
452 ACESproxy = createACESProxy()
453 configData['colorSpaces'].append(ACESproxy)
458 def createACEScg(name='ACEScg'):
459 cs = ColorSpace(name)
460 cs.description = "The %s color space" % name
461 cs.equalityGroup = ''
465 cs.toReferenceTransforms = []
467 # AP1 primaries to AP0 primaries
468 cs.toReferenceTransforms.append({
470 'matrix': mat44FromMat33(acesAP1toAP0),
471 'direction': 'forward'
474 cs.fromReferenceTransforms = []
477 ACEScg = createACEScg()
478 configData['colorSpaces'].append(ACEScg)
483 def createADX(bitdepth=10, name='ADX'):
484 name = "%s%s" % (name, bitdepth)
485 cs = ColorSpace(name)
486 cs.description = "%s color space - used for film scans" % name
487 cs.equalityGroup = ''
492 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
493 adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
494 0.0, 1023.0 / 500.0, 0.0, 0.0,
495 0.0, 0.0, 1023.0 / 500.0, 0.0,
497 offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
499 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
500 adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
501 0.0, 65535.0 / 8000.0, 0.0, 0.0,
502 0.0, 0.0, 65535.0 / 8000.0, 0.0,
504 offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
507 cs.toReferenceTransforms = []
509 # Convert from ADX to Channel-Dependent Density
510 cs.toReferenceTransforms.append({
512 'matrix': adx_to_cdd,
514 'direction': 'forward'
517 # Convert from Channel-Dependent Density to Channel-Independent Density
518 cs.toReferenceTransforms.append({
520 'matrix': [0.75573, 0.22197, 0.02230, 0,
521 0.05901, 0.96928, -0.02829, 0,
522 0.16134, 0.07406, 0.76460, 0,
524 'direction': 'forward'
527 # Copied from Alex Fry's adx_cid_to_rle.py
528 def createCIDtoRLELUT():
529 def interpolate1D(x, xp, fp):
530 return numpy.interp(x, xp, fp)
532 LUT_1D_xp = [-0.190000000000000,
544 LUT_1D_fp = [-6.000000000000000,
556 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(
561 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
562 return (100.0 / 55.0) * x - REF_PT
564 def Fit(value, fromMin, fromMax, toMin, toMax):
565 if fromMin == fromMax:
566 raise ValueError("fromMin == fromMax")
567 return (value - fromMin) / (fromMax - fromMin) * (
568 toMax - toMin) + toMin
570 NUM_SAMPLES = 2 ** 12
573 for i in xrange(NUM_SAMPLES):
574 x = i / (NUM_SAMPLES - 1.0)
575 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
576 data.append(cid_to_rle(x))
578 lut = 'ADX_CID_to_RLE.spi1d'
579 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
584 # Convert Channel Independent Density values to Relative Log Exposure values
585 lut = createCIDtoRLELUT()
586 cs.toReferenceTransforms.append({
589 'interpolation': 'linear',
590 'direction': 'forward'
593 # Convert Relative Log Exposure values to Relative Exposure values
594 cs.toReferenceTransforms.append({
597 'direction': 'inverse'
600 # Convert Relative Exposure values to ACES values
601 cs.toReferenceTransforms.append({
603 'matrix': [0.72286, 0.12630, 0.15084, 0,
604 0.11923, 0.76418, 0.11659, 0,
605 0.01427, 0.08213, 0.90359, 0,
607 'direction': 'forward'
610 cs.fromReferenceTransforms = []
613 ADX10 = createADX(bitdepth=10)
614 configData['colorSpaces'].append(ADX10)
616 ADX16 = createADX(bitdepth=16)
617 configData['colorSpaces'].append(ADX16)
620 # Camera Input Transforms
623 # RED color spaces to ACES
624 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
625 for cs in redColorSpaces:
626 configData['colorSpaces'].append(cs)
629 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
630 for cs in canonColorSpaces:
631 configData['colorSpaces'].append(cs)
634 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
635 for cs in sonyColorSpaces:
636 configData['colorSpaces'].append(cs)
639 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
640 for cs in arriColorSpaces:
641 configData['colorSpaces'].append(cs)
644 # Generic log transform
646 def createGenericLog(name='log',
653 lutResolution1d=lutResolution1d):
654 cs = ColorSpace(name)
655 cs.description = "The %s color space" % name
656 cs.equalityGroup = name
657 cs.family = 'Utility'
661 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
663 lut = "%s_to_aces.spi1d" % name
665 # Remove spaces and parentheses
666 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
668 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
675 'middleGrey': middleGrey,
676 'minExposure': minExposure,
677 'maxExposure': maxExposure
684 cs.toReferenceTransforms = []
685 cs.toReferenceTransforms.append({
688 'interpolation': 'linear',
689 'direction': 'forward'
692 cs.fromReferenceTransforms = []
698 def createACESLMT(lmtName,
701 lutResolution1d=1024,
704 cs = ColorSpace("%s" % lmtName)
705 cs.description = "The ACES Look Transform: %s" % lmtName
706 cs.equalityGroup = ''
712 pprint.pprint(lmtValues)
715 # Generate the shaper transform
717 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale,
718 shaperParams) = shaperInfo
720 shaperLut = "%s_to_aces.spi1d" % shaperName
721 if ( not os.path.exists(lutDir + "/" + shaperLut) ):
723 shaperToACESCTL % acesCTLReleaseDir
726 # Remove spaces and parentheses
727 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace(
730 genlut.generate1dLUTFromCTL(lutDir + "/" + shaperLut,
734 1.0 / shaperInputScale,
740 shaperOCIOTransform = {
743 'interpolation': 'linear',
744 'direction': 'inverse'
748 # Generate the forward transform
750 cs.fromReferenceTransforms = []
752 if 'transformCTL' in lmtValues:
754 shaperToACESCTL % acesCTLReleaseDir,
755 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
757 lut = "%s.%s.spi3d" % (shaperName, lmtName)
759 # Remove spaces and parentheses
760 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
762 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
766 1.0 / shaperInputScale,
772 cs.fromReferenceTransforms.append(shaperOCIOTransform)
773 cs.fromReferenceTransforms.append({
776 'interpolation': 'tetrahedral',
777 'direction': 'forward'
781 # Generate the inverse transform
783 cs.toReferenceTransforms = []
785 if 'transformCTLInverse' in lmtValues:
788 acesCTLReleaseDir, odtValues['transformCTLInverse']),
789 shaperFromACESCTL % acesCTLReleaseDir
791 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
793 # Remove spaces and parentheses
794 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
796 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
806 cs.toReferenceTransforms.append({
809 'interpolation': 'tetrahedral',
810 'direction': 'forward'
813 shaperInverse = shaperOCIOTransform.copy()
814 shaperInverse['direction'] = 'forward'
815 cs.toReferenceTransforms.append(shaperInverse)
823 lmtLutResolution1d = max(4096, lutResolution1d)
824 lmtLutResolution3d = max(65, lutResolution3d)
827 lmtShaperName = 'LMT Shaper'
830 'minExposure': -10.0,
833 lmtShaper = createGenericLog(name=lmtShaperName,
834 middleGrey=lmtParams['middleGrey'],
835 minExposure=lmtParams['minExposure'],
836 maxExposure=lmtParams['maxExposure'],
837 lutResolution1d=lmtLutResolution1d)
838 configData['colorSpaces'].append(lmtShaper)
840 shaperInputScale_genericLog2 = 1.0
842 # Log 2 shaper name and CTL transforms bundled up
845 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
846 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
847 shaperInputScale_genericLog2,
851 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
853 for lmt in sortedLMTs:
854 (lmtName, lmtValues) = lmt
856 lmtValues['transformUserName'],
862 configData['colorSpaces'].append(cs)
865 # ACES RRT with the supplied ODT
867 def createACESRRTplusODT(odtName,
870 lutResolution1d=1024,
873 cs = ColorSpace("%s" % odtName)
874 cs.description = "%s - %s Output Transform" % (
875 odtValues['transformUserNamePrefix'], odtName)
876 cs.equalityGroup = ''
882 pprint.pprint(odtValues)
885 # Generate the shaper transform
887 # if 'shaperCTL' in odtValues:
888 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale,
889 shaperParams) = shaperInfo
891 if 'legalRange' in odtValues:
892 shaperParams['legalRange'] = odtValues['legalRange']
894 shaperParams['legalRange'] = 0
896 shaperLut = "%s_to_aces.spi1d" % shaperName
897 if ( not os.path.exists(lutDir + "/" + shaperLut) ):
899 shaperToACESCTL % acesCTLReleaseDir
902 # Remove spaces and parentheses
903 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace(
906 genlut.generate1dLUTFromCTL(lutDir + "/" + shaperLut,
910 1.0 / shaperInputScale,
916 shaperOCIOTransform = {
919 'interpolation': 'linear',
920 'direction': 'inverse'
924 # Generate the forward transform
926 cs.fromReferenceTransforms = []
928 if 'transformLUT' in odtValues:
929 # Copy into the lut dir
930 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
931 lut = lutDir + "/" + transformLUTFileName
932 shutil.copy(odtValues['transformLUT'], lut)
934 cs.fromReferenceTransforms.append(shaperOCIOTransform)
935 cs.fromReferenceTransforms.append({
937 'path': transformLUTFileName,
938 'interpolation': 'tetrahedral',
939 'direction': 'forward'
941 elif 'transformCTL' in odtValues:
945 shaperToACESCTL % acesCTLReleaseDir,
946 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
947 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
949 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
951 # Remove spaces and parentheses
952 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
954 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
959 1.0 / shaperInputScale,
965 cs.fromReferenceTransforms.append(shaperOCIOTransform)
966 cs.fromReferenceTransforms.append({
969 'interpolation': 'tetrahedral',
970 'direction': 'forward'
974 # Generate the inverse transform
976 cs.toReferenceTransforms = []
978 if 'transformLUTInverse' in odtValues:
979 # Copy into the lut dir
980 transformLUTInverseFileName = os.path.basename(
981 odtValues['transformLUTInverse'])
982 lut = lutDir + "/" + transformLUTInverseFileName
983 shutil.copy(odtValues['transformLUTInverse'], lut)
985 cs.toReferenceTransforms.append({
987 'path': transformLUTInverseFileName,
988 'interpolation': 'tetrahedral',
989 'direction': 'forward'
992 shaperInverse = shaperOCIOTransform.copy()
993 shaperInverse['direction'] = 'forward'
994 cs.toReferenceTransforms.append(shaperInverse)
995 elif 'transformCTLInverse' in odtValues:
998 acesCTLReleaseDir, odtValues['transformCTLInverse']),
999 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1000 shaperFromACESCTL % acesCTLReleaseDir
1002 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1004 # Remove spaces and parentheses
1005 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1007 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1018 cs.toReferenceTransforms.append({
1021 'interpolation': 'tetrahedral',
1022 'direction': 'forward'
1025 shaperInverse = shaperOCIOTransform.copy()
1026 shaperInverse['direction'] = 'forward'
1027 cs.toReferenceTransforms.append(shaperInverse)
1032 # RRT/ODT shaper options
1037 log2ShaperName = shaperName
1040 'minExposure': -6.0,
1043 log2Shaper = createGenericLog(name=log2ShaperName,
1044 middleGrey=log2Params['middleGrey'],
1045 minExposure=log2Params['minExposure'],
1046 maxExposure=log2Params['maxExposure'])
1047 configData['colorSpaces'].append(log2Shaper)
1049 shaperInputScale_genericLog2 = 1.0
1051 # Log 2 shaper name and CTL transforms bundled up
1054 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1055 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1056 shaperInputScale_genericLog2,
1060 shaperData[log2ShaperName] = log2ShaperData
1063 # Shaper that also includes the AP1 primaries
1064 # - Needed for some LUT baking steps
1066 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1067 middleGrey=log2Params['middleGrey'],
1068 minExposure=log2Params['minExposure'],
1069 maxExposure=log2Params['maxExposure'])
1070 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1071 # AP1 primaries to AP0 primaries
1072 log2ShaperAP1.toReferenceTransforms.append({
1074 'matrix': mat44FromMat33(acesAP1toAP0),
1075 'direction': 'forward'
1077 configData['colorSpaces'].append(log2ShaperAP1)
1080 # Choose your shaper
1082 rrtShaperName = log2ShaperName
1083 rrtShaper = log2ShaperData
1086 # RRT + ODT Combinations
1088 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1090 for odt in sortedOdts:
1091 (odtName, odtValues) = odt
1093 # Have to handle ODTs that can generate either legal or full output
1094 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1095 'Academy.Rec709_100nits_dim.a1.0.0',
1096 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1097 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1099 odtNameLegal = odtValues['transformUserName']
1101 odtLegal = odtValues.copy()
1102 odtLegal['legalRange'] = 1
1104 cs = createACESRRTplusODT(
1111 configData['colorSpaces'].append(cs)
1113 # Create a display entry using this color space
1114 configData['displays'][odtNameLegal] = {
1117 'Output Transform': cs}
1119 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1120 'Academy.Rec709_100nits_dim.a1.0.0',
1121 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1122 print( "Generating full range ODT for %s" % odtName)
1124 odtNameFull = "%s - Full" % odtValues['transformUserName']
1125 odtFull = odtValues.copy()
1126 odtFull['legalRange'] = 0
1128 csFull = createACESRRTplusODT(
1135 configData['colorSpaces'].append(csFull)
1137 # Create a display entry using this color space
1138 configData['displays'][odtNameFull] = {
1141 'Output Transform': csFull}
1144 # Generic Matrix transform
1146 def createGenericMatrix(name='matrix',
1147 fromReferenceValues=[],
1148 toReferenceValues=[]):
1149 cs = ColorSpace(name)
1150 cs.description = "The %s color space" % name
1151 cs.equalityGroup = name
1152 cs.family = 'Utility'
1155 cs.toReferenceTransforms = []
1156 if toReferenceValues != []:
1157 for matrix in toReferenceValues:
1158 cs.toReferenceTransforms.append({
1160 'matrix': mat44FromMat33(matrix),
1161 'direction': 'forward'
1164 cs.fromReferenceTransforms = []
1165 if fromReferenceValues != []:
1166 for matrix in fromReferenceValues:
1167 cs.fromReferenceTransforms.append({
1169 'matrix': mat44FromMat33(matrix),
1170 'direction': 'forward'
1175 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1176 configData['colorSpaces'].append(cs)
1178 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1179 configData['colorSpaces'].append(cs)
1181 # ACES to Linear, P3D60 primaries
1182 xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1183 -0.8325796487, 1.7692317536, 0.0237127115,
1184 0.0388233815, -0.0824996856, 1.0363685997]
1186 cs = createGenericMatrix('Linear - P3-D60',
1187 fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1188 configData['colorSpaces'].append(cs)
1190 # ACES to Linear, P3D60 primaries
1191 xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1192 -0.7951680258, 1.6897320548, 0.0226471906,
1193 0.0412418914, -0.0876390192, 1.1009293786]
1195 cs = createGenericMatrix('Linear - P3-DCI',
1196 fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1197 configData['colorSpaces'].append(cs)
1199 # ACES to Linear, Rec 709 primaries
1200 xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1201 -0.9692436363, 1.8759675015, 0.0415550574,
1202 0.0556300797, -0.2039769589, 1.0569715142]
1204 cs = createGenericMatrix('Linear - Rec.709',
1205 fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1206 configData['colorSpaces'].append(cs)
1208 # ACES to Linear, Rec 2020 primaries
1209 xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1210 -0.6666843518, 1.6164812366, 0.0157685458,
1211 0.0176398574, -0.0427706133, 0.9421031212]
1213 cs = createGenericMatrix('Linear - Rec.2020',
1214 fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1215 configData['colorSpaces'].append(cs)
1217 print( "generateLUTs - end" )
1221 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath,
1222 lutResolution1d, lutResolution3d,
1223 lutResolutionShaper=1024):
1224 # Add the legal and full variations into this list
1225 odtInfoC = dict(odtInfo)
1226 for odtCTLName, odtValues in odtInfo.iteritems():
1227 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1228 'Academy.Rec709_100nits_dim.a1.0.0',
1229 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1230 odtName = odtValues["transformUserName"]
1232 odtValuesLegal = dict(odtValues)
1233 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1234 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1236 odtValuesFull = dict(odtValues)
1237 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1238 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1240 del ( odtInfoC[odtCTLName] )
1242 for odtCTLName, odtValues in odtInfoC.iteritems():
1243 odtPrefix = odtValues["transformUserNamePrefix"]
1244 odtName = odtValues["transformUserName"]
1247 for inputspace in ["ACEScc", "ACESproxy"]:
1248 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1249 args += ["--outputspace", "%s" % odtName]
1250 args += ["--description",
1251 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1252 args += ["--shaperspace", shaperName, "--shapersize",
1253 str(lutResolutionShaper)]
1254 args += ["--cubesize", str(lutResolution3d)]
1255 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1256 bakedDir, odtName, inputspace)]
1258 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1263 for inputspace in ["ACEScc", "ACESproxy"]:
1264 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1265 args += ["--outputspace", "%s" % odtName]
1266 args += ["--description",
1267 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1268 args += ["--shaperspace", shaperName, "--shapersize",
1269 str(lutResolutionShaper)]
1270 args += ["--cubesize", str(lutResolution3d)]
1272 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1273 bakedDir, odtName, inputspace)]
1274 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1275 args=(args + fargs))
1278 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1279 bakedDir, odtName, inputspace)]
1280 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1281 args=(args + largs))
1285 for inputspace in ["ACEScg", "ACES2065-1"]:
1286 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1287 args += ["--outputspace", "%s" % odtName]
1288 args += ["--description",
1289 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1290 if inputspace == 'ACEScg':
1291 linShaperName = "%s - AP1" % shaperName
1293 linShaperName = shaperName
1294 args += ["--shaperspace", linShaperName, "--shapersize",
1295 str(lutResolutionShaper)]
1297 args += ["--cubesize", str(lutResolution3d)]
1299 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1300 bakedDir, odtName, inputspace)]
1301 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1302 args=(args + margs))
1305 hargs = ["--format", "houdini",
1306 "%s/houdini/%s for %s Houdini.lut" % (
1307 bakedDir, odtName, inputspace)]
1308 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1309 args=(args + hargs))
1313 def createConfigDir(configDir, bakeSecondaryLUTs):
1314 dirs = [configDir, "%s/luts" % configDir]
1315 if bakeSecondaryLUTs:
1316 dirs.extend(["%s/baked" % configDir,
1317 "%s/baked/flame" % configDir,
1318 "%s/baked/photoshop" % configDir,
1319 "%s/baked/houdini" % configDir,
1320 "%s/baked/lustre" % configDir,
1321 "%s/baked/maya" % configDir])
1324 if not os.path.exists(d):
1328 def getTransformInfo(ctlTransform):
1329 fp = open(ctlTransform, 'rb')
1332 lines = fp.readlines()
1334 # Grab transform ID and User Name
1335 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1336 # print( transformID )
1337 transformUserName = '-'.join(
1338 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1339 1:]).lstrip().rstrip()
1340 transformUserNamePrefix = \
1341 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1342 0].lstrip().rstrip()
1343 # print( transformUserName )
1346 return (transformID, transformUserName, transformUserNamePrefix)
1349 # For versions after WGR9
1350 def getODTInfo(acesCTLReleaseDir):
1351 # Credit to Alex Fry for the original approach here
1352 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1354 for dirName, subdirList, fileList in os.walk(odtDir):
1355 for fname in fileList:
1356 allodt.append((os.path.join(dirName, fname)))
1358 odtCTLs = [x for x in allodt if
1359 ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1365 for odtCTL in odtCTLs:
1366 odtTokens = os.path.split(odtCTL)
1367 # print( odtTokens )
1369 # Handle nested directories
1370 odtPathTokens = os.path.split(odtTokens[-2])
1371 odtDir = odtPathTokens[-1]
1372 while odtPathTokens[-2][-3:] != 'odt':
1373 odtPathTokens = os.path.split(odtPathTokens[-2])
1374 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1377 # print( "odtDir : %s" % odtDir )
1378 transformCTL = odtTokens[-1]
1379 # print( transformCTL )
1380 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1383 # Find id, user name and user name prefix
1384 (transformID, transformUserName,
1385 transformUserNamePrefix) = getTransformInfo(
1386 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1389 transformCTLInverse = "InvODT.%s.ctl" % odtName
1390 if not os.path.exists(
1391 os.path.join(odtTokens[-2], transformCTLInverse)):
1392 transformCTLInverse = None
1393 #print( transformCTLInverse )
1395 # Add to list of ODTs
1397 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1398 if transformCTLInverse != None:
1399 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir,
1400 transformCTLInverse)
1402 odts[odtName]['transformID'] = transformID
1403 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1404 odts[odtName]['transformUserName'] = transformUserName
1406 print( "ODT : %s" % odtName )
1407 print( "\tTransform ID : %s" % transformID )
1408 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1409 print( "\tTransform User Name : %s" % transformUserName )
1411 "\tForward ctl : %s" % odts[odtName][
1413 if 'transformCTLInverse' in odts[odtName]:
1414 print("\tInverse ctl : %s" % odts[odtName][
1415 'transformCTLInverse'])
1417 print( "\tInverse ctl : %s" % "None" )
1424 # For versions after WGR9
1425 def getLMTInfo(acesCTLReleaseDir):
1426 # Credit to Alex Fry for the original approach here
1427 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1429 for dirName, subdirList, fileList in os.walk(lmtDir):
1430 for fname in fileList:
1431 alllmt.append((os.path.join(dirName, fname)))
1433 lmtCTLs = [x for x in alllmt if
1434 ("InvLMT" not in x) and ("README" not in x) and (
1435 os.path.split(x)[-1][0] != '.')]
1441 for lmtCTL in lmtCTLs:
1442 lmtTokens = os.path.split(lmtCTL)
1443 # print( lmtTokens )
1445 # Handle nested directories
1446 lmtPathTokens = os.path.split(lmtTokens[-2])
1447 lmtDir = lmtPathTokens[-1]
1448 while lmtPathTokens[-2][-3:] != 'ctl':
1449 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1450 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1453 # print( "lmtDir : %s" % lmtDir )
1454 transformCTL = lmtTokens[-1]
1455 # print( transformCTL )
1456 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1459 # Find id, user name and user name prefix
1460 (transformID, transformUserName,
1461 transformUserNamePrefix) = getTransformInfo(
1462 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1465 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1466 if not os.path.exists(
1467 os.path.join(lmtTokens[-2], transformCTLInverse)):
1468 transformCTLInverse = None
1469 #print( transformCTLInverse )
1471 # Add to list of LMTs
1473 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1474 if transformCTLInverse != None:
1475 # TODO: Check unresolved *odtName* referemce.
1476 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir,
1477 transformCTLInverse)
1479 lmts[lmtName]['transformID'] = transformID
1480 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1481 lmts[lmtName]['transformUserName'] = transformUserName
1483 print( "LMT : %s" % lmtName )
1484 print( "\tTransform ID : %s" % transformID )
1485 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1486 print( "\tTransform User Name : %s" % transformUserName )
1487 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1488 if 'transformCTLInverse' in lmts[lmtName]:
1490 "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1492 print( "\t Inverse ctl : %s" % "None" )
1500 # Create the ACES config
1502 def createACESConfig(acesCTLReleaseDir,
1504 lutResolution1d=4096,
1506 bakeSecondaryLUTs=True,
1508 # Get ODT names and CTL paths
1509 odtInfo = getODTInfo(acesCTLReleaseDir)
1511 # Get ODT names and CTL paths
1512 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1515 createConfigDir(configDir, bakeSecondaryLUTs)
1517 # Generate config data and LUTs for different transforms
1518 lutDir = "%s/luts" % configDir
1519 shaperName = 'Output Shaper'
1520 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir,
1521 lutDir, lutResolution1d, lutResolution3d,
1524 # Create the config using the generated LUTs
1525 print( "Creating generic config")
1526 config = createConfig(configData)
1529 # Write the config to disk
1530 writeConfig(config, "%s/config.ocio" % configDir)
1532 # Create a config that will work well with Nuke using the previously generated LUTs
1533 print( "Creating Nuke-specific config")
1534 nuke_config = createConfig(configData, nuke=True)
1537 # Write the config to disk
1538 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1540 # Bake secondary LUTs using the config
1541 if bakeSecondaryLUTs:
1542 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir,
1543 "%s/config.ocio" % configDir, lutResolution1d,
1544 lutResolution3d, lutResolution1d)
1555 p = optparse.OptionParser(description='An OCIO config generation script',
1556 prog='createACESConfig',
1557 version='createACESConfig 0.1',
1558 usage='%prog [options]')
1559 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1560 'ACES_OCIO_CTL_DIRECTORY', None))
1561 p.add_option('--configDir', '-c', default=os.environ.get(
1562 'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1563 p.add_option('--lutResolution1d', default=4096)
1564 p.add_option('--lutResolution3d', default=64)
1565 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1566 p.add_option('--keepTempImages', action="store_true")
1568 options, arguments = p.parse_args()
1573 acesCTLDir = options.acesCTLDir
1574 configDir = options.configDir
1575 lutResolution1d = int(options.lutResolution1d)
1576 lutResolution3d = int(options.lutResolution3d)
1577 bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1578 cleanupTempImages = not (options.keepTempImages)
1581 argsStart = sys.argv.index('--') + 1
1582 args = sys.argv[argsStart:]
1584 argsStart = len(sys.argv) + 1
1587 print( "command line : \n%s\n" % " ".join(sys.argv) )
1589 # TODO: Use assertion and mention environment variables.
1591 print( "process: No ACES CTL directory specified" )
1594 print( "process: No configuration directory specified" )
1597 # Generate the configuration
1599 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d,
1600 bakeSecondaryLUTs, cleanupTempImages)
1604 if __name__ == '__main__':