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
50 # TODO: This restores the capability of running the script without having
51 # added the package to PYTHONPATH, this is ugly and should ideally replaced by
52 # dedicated executable in a /bin directory.
53 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
55 import PyOpenColorIO as OCIO
57 import aces_ocio.createARRIColorSpaces as arri
58 import aces_ocio.createCanonColorSpaces as canon
59 import aces_ocio.createREDColorSpaces as red
60 import aces_ocio.createSonyColorSpaces as sony
61 import aces_ocio.generateLUT as genlut
62 from aces_ocio.process import Process
63 from aces_ocio.util import ColorSpace, mat44FromMat33
65 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
66 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
71 def setConfigDefaultRoles(config,
82 if color_picking: config.setRole(OCIO.Constants.ROLE_COLOR_PICKING,
84 if color_timing: config.setRole(OCIO.Constants.ROLE_COLOR_TIMING,
86 if compositing_log: config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG,
88 if data: config.setRole(OCIO.Constants.ROLE_DATA, data)
89 if default: config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
90 if matte_paint: config.setRole(OCIO.Constants.ROLE_MATTE_PAINT,
92 if reference: config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
93 if scene_linear: config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR,
95 if texture_paint: config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT,
99 # Write config to disk
100 def writeConfig(config, configPath, sanityCheck=True):
106 print "Configuration was not written due to a failed Sanity Check"
110 fileHandle = open(configPath, mode='w')
111 fileHandle.write(config.serialize())
115 def generateOCIOTransform(transforms):
116 # print( "Generating transforms")
118 interpolationOptions = {
119 'linear': OCIO.Constants.INTERP_LINEAR,
120 'nearest': OCIO.Constants.INTERP_NEAREST,
121 'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
124 'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
125 'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
130 for transform in transforms:
131 if transform['type'] == 'lutFile':
133 ocioTransform = OCIO.FileTransform(src=transform['path'],
135 interpolationOptions[
136 transform['interpolation']],
137 direction=directionOptions[
138 transform['direction']])
140 ocioTransforms.append(ocioTransform)
141 elif transform['type'] == 'matrix':
142 ocioTransform = OCIO.MatrixTransform()
143 # MatrixTransform member variables can't be initialized directly. Each must be set individually
144 ocioTransform.setMatrix(transform['matrix'])
146 if 'offset' in transform:
147 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'])
157 ocioTransforms.append(ocioTransform)
158 elif transform['type'] == 'log':
159 ocioTransform = OCIO.LogTransform(base=transform['base'],
160 direction=directionOptions[
161 transform['direction']])
163 ocioTransforms.append(ocioTransform)
165 print( "Ignoring unknown transform type : %s" % transform['type'] )
167 # Build a group transform if necessary
168 if len(ocioTransforms) > 1:
169 transformG = OCIO.GroupTransform()
170 for transform in ocioTransforms:
171 transformG.push_back(transform)
172 transform = transformG
174 # Or take the first transform from the list
176 transform = ocioTransforms[0]
181 def createConfig(configData, nuke=False):
183 config = OCIO.Config()
186 # Set config wide values
188 config.setDescription("An ACES config generated from python")
189 config.setSearchPath("luts")
192 # Define the reference color space
194 referenceData = configData['referenceColorSpace']
195 print( "Adding the reference color space : %s" % referenceData.name)
197 # Create a color space
198 reference = OCIO.ColorSpace(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(name=colorspace.name,
217 bitDepth=colorspace.bitDepth,
218 description=colorspace.description,
219 equalityGroup=colorspace.equalityGroup,
220 family=colorspace.family,
221 isData=colorspace.isData,
222 allocation=colorspace.allocationType,
223 allocationVars=colorspace.allocationVars)
225 if colorspace.toReferenceTransforms != []:
226 print( "Generating To-Reference transforms")
227 ocioTransform = generateOCIOTransform(
228 colorspace.toReferenceTransforms)
229 ocioColorspace.setTransform(ocioTransform,
230 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
232 if colorspace.fromReferenceTransforms != []:
233 print( "Generating From-Reference transforms")
234 ocioTransform = generateOCIOTransform(
235 colorspace.fromReferenceTransforms)
236 ocioColorspace.setTransform(ocioTransform,
237 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
239 config.addColorSpace(ocioColorspace)
244 # Define the views and displays
249 # Generic display and view setup
251 for display, viewList in configData['displays'].iteritems():
252 for viewName, colorspace in viewList.iteritems():
253 config.addDisplay(display, viewName, colorspace.name)
254 if not (viewName in views):
255 views.append(viewName)
256 displays.append(display)
257 # A Nuke specific set of views and displays
260 # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
263 for display, viewList in configData['displays'].iteritems():
264 for viewName, colorspace in viewList.iteritems():
265 if ( viewName == 'Output Transform'):
267 config.addDisplay(display, viewName, colorspace.name)
268 if not (viewName in views):
269 views.append(viewName)
270 displays.append(display)
272 config.addDisplay('linear', 'View', 'ACES2065-1')
273 displays.append('linear')
274 config.addDisplay('log', 'View', 'ACEScc')
275 displays.append('log')
277 # Set active displays and views
278 config.setActiveDisplays(','.join(sorted(displays)))
279 config.setActiveViews(','.join(views))
282 # Need to generalize this at some point
286 setConfigDefaultRoles(config,
287 color_picking=reference.getName(),
288 color_timing=reference.getName(),
289 compositing_log=reference.getName(),
290 data=reference.getName(),
291 default=reference.getName(),
292 matte_paint=reference.getName(),
293 reference=reference.getName(),
294 scene_linear=reference.getName(),
295 texture_paint=reference.getName())
297 # Check to make sure we didn't screw something up
304 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
307 # Output is a list of colorspaces and transforms that convert between those
308 # colorspaces and reference color space, ACES
309 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir,
310 lutResolution1d=4096, lutResolution3d=64, cleanup=True):
311 print( "generateLUTs - begin" )
315 # Define the reference color space
317 ACES = ColorSpace('ACES2065-1')
318 ACES.description = "The Academy Color Encoding System reference color space"
319 ACES.equalityGroup = ''
322 ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
323 ACES.allocationVars = [-15, 6]
325 configData['referenceColorSpace'] = ACES
328 # Define the displays
330 configData['displays'] = {}
333 # Define the other color spaces
335 configData['colorSpaces'] = []
337 # Matrix converting ACES AP1 primaries to AP0
338 acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
339 0.0447945634, 0.8596711185, 0.0955343182,
340 -0.0055258826, 0.0040252103, 1.0015006723]
342 # Matrix converting ACES AP0 primaries to XYZ
343 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
344 0.3439664498, 0.7281660966, -0.0721325464,
345 0.0000000000, 0.0000000000, 1.0088251844]
350 def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0,
352 cs = ColorSpace(name)
353 cs.description = "The %s color space" % name
354 cs.equalityGroup = ''
359 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
360 # This transform gets back to the AP1 primaries
361 # Useful as the 1d LUT is only covering the transfer function
362 # The primaries switch is covered by the matrix below
363 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
365 lut = "%s_to_ACES.spi1d" % name
367 # Remove spaces and parentheses
368 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
370 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
382 cs.toReferenceTransforms = []
383 cs.toReferenceTransforms.append({
386 'interpolation': 'linear',
387 'direction': 'forward'
390 # AP1 primaries to AP0 primaries
391 cs.toReferenceTransforms.append({
393 'matrix': mat44FromMat33(acesAP1toAP0),
394 'direction': 'forward'
397 cs.fromReferenceTransforms = []
400 ACEScc = createACEScc()
401 configData['colorSpaces'].append(ACEScc)
406 def createACESProxy(name='ACESproxy'):
407 cs = ColorSpace(name)
408 cs.description = "The %s color space" % name
409 cs.equalityGroup = ''
414 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
415 # This transform gets back to the AP1 primaries
416 # Useful as the 1d LUT is only covering the transfer function
417 # The primaries switch is covered by the matrix below
418 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
420 lut = "%s_to_aces.spi1d" % name
422 # Remove spaces and parentheses
423 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
425 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
435 cs.toReferenceTransforms = []
436 cs.toReferenceTransforms.append({
439 'interpolation': 'linear',
440 'direction': 'forward'
443 # AP1 primaries to AP0 primaries
444 cs.toReferenceTransforms.append({
446 'matrix': mat44FromMat33(acesAP1toAP0),
447 'direction': 'forward'
450 cs.fromReferenceTransforms = []
453 ACESproxy = createACESProxy()
454 configData['colorSpaces'].append(ACESproxy)
459 def createACEScg(name='ACEScg'):
460 cs = ColorSpace(name)
461 cs.description = "The %s color space" % name
462 cs.equalityGroup = ''
466 cs.toReferenceTransforms = []
468 # AP1 primaries to AP0 primaries
469 cs.toReferenceTransforms.append({
471 'matrix': mat44FromMat33(acesAP1toAP0),
472 'direction': 'forward'
475 cs.fromReferenceTransforms = []
478 ACEScg = createACEScg()
479 configData['colorSpaces'].append(ACEScg)
484 def createADX(bitdepth=10, name='ADX'):
485 name = "%s%s" % (name, bitdepth)
486 cs = ColorSpace(name)
487 cs.description = "%s color space - used for film scans" % name
488 cs.equalityGroup = ''
493 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
494 adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
495 0.0, 1023.0 / 500.0, 0.0, 0.0,
496 0.0, 0.0, 1023.0 / 500.0, 0.0,
498 offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
500 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
501 adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
502 0.0, 65535.0 / 8000.0, 0.0, 0.0,
503 0.0, 0.0, 65535.0 / 8000.0, 0.0,
505 offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
508 cs.toReferenceTransforms = []
510 # Convert from ADX to Channel-Dependent Density
511 cs.toReferenceTransforms.append({
513 'matrix': adx_to_cdd,
515 'direction': 'forward'
518 # Convert from Channel-Dependent Density to Channel-Independent Density
519 cs.toReferenceTransforms.append({
521 'matrix': [0.75573, 0.22197, 0.02230, 0,
522 0.05901, 0.96928, -0.02829, 0,
523 0.16134, 0.07406, 0.76460, 0,
525 'direction': 'forward'
528 # Copied from Alex Fry's adx_cid_to_rle.py
529 def createCIDtoRLELUT():
530 def interpolate1D(x, xp, fp):
531 return numpy.interp(x, xp, fp)
533 LUT_1D_xp = [-0.190000000000000,
545 LUT_1D_fp = [-6.000000000000000,
557 REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(
562 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
563 return (100.0 / 55.0) * x - REF_PT
565 def Fit(value, fromMin, fromMax, toMin, toMax):
566 if fromMin == fromMax:
567 raise ValueError("fromMin == fromMax")
568 return (value - fromMin) / (fromMax - fromMin) * (
569 toMax - toMin) + toMin
571 NUM_SAMPLES = 2 ** 12
574 for i in xrange(NUM_SAMPLES):
575 x = i / (NUM_SAMPLES - 1.0)
576 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
577 data.append(cid_to_rle(x))
579 lut = 'ADX_CID_to_RLE.spi1d'
580 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
585 # Convert Channel Independent Density values to Relative Log Exposure values
586 lut = createCIDtoRLELUT()
587 cs.toReferenceTransforms.append({
590 'interpolation': 'linear',
591 'direction': 'forward'
594 # Convert Relative Log Exposure values to Relative Exposure values
595 cs.toReferenceTransforms.append({
598 'direction': 'inverse'
601 # Convert Relative Exposure values to ACES values
602 cs.toReferenceTransforms.append({
604 'matrix': [0.72286, 0.12630, 0.15084, 0,
605 0.11923, 0.76418, 0.11659, 0,
606 0.01427, 0.08213, 0.90359, 0,
608 'direction': 'forward'
611 cs.fromReferenceTransforms = []
614 ADX10 = createADX(bitdepth=10)
615 configData['colorSpaces'].append(ADX10)
617 ADX16 = createADX(bitdepth=16)
618 configData['colorSpaces'].append(ADX16)
621 # Camera Input Transforms
624 # RED color spaces to ACES
625 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
626 for cs in redColorSpaces:
627 configData['colorSpaces'].append(cs)
630 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
631 for cs in canonColorSpaces:
632 configData['colorSpaces'].append(cs)
635 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
636 for cs in sonyColorSpaces:
637 configData['colorSpaces'].append(cs)
640 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
641 for cs in arriColorSpaces:
642 configData['colorSpaces'].append(cs)
645 # Generic log transform
647 def createGenericLog(name='log',
654 lutResolution1d=lutResolution1d):
655 cs = ColorSpace(name)
656 cs.description = "The %s color space" % name
657 cs.equalityGroup = name
658 cs.family = 'Utility'
662 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
664 lut = "%s_to_aces.spi1d" % name
666 # Remove spaces and parentheses
667 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
669 genlut.generate1dLUTFromCTL(lutDir + "/" + lut,
676 'middleGrey': middleGrey,
677 'minExposure': minExposure,
678 'maxExposure': maxExposure
685 cs.toReferenceTransforms = []
686 cs.toReferenceTransforms.append({
689 'interpolation': 'linear',
690 'direction': 'forward'
693 cs.fromReferenceTransforms = []
699 def createACESLMT(lmtName,
702 lutResolution1d=1024,
705 cs = ColorSpace("%s" % lmtName)
706 cs.description = "The ACES Look Transform: %s" % lmtName
707 cs.equalityGroup = ''
713 pprint.pprint(lmtValues)
716 # Generate the shaper transform
718 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale,
719 shaperParams) = shaperInfo
721 shaperLut = "%s_to_aces.spi1d" % shaperName
722 if ( not os.path.exists(lutDir + "/" + shaperLut) ):
724 shaperToACESCTL % acesCTLReleaseDir
727 # Remove spaces and parentheses
728 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace(
731 genlut.generate1dLUTFromCTL(lutDir + "/" + shaperLut,
735 1.0 / shaperInputScale,
741 shaperOCIOTransform = {
744 'interpolation': 'linear',
745 'direction': 'inverse'
749 # Generate the forward transform
751 cs.fromReferenceTransforms = []
753 if 'transformCTL' in lmtValues:
755 shaperToACESCTL % acesCTLReleaseDir,
756 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
758 lut = "%s.%s.spi3d" % (shaperName, lmtName)
760 # Remove spaces and parentheses
761 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
763 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
767 1.0 / shaperInputScale,
773 cs.fromReferenceTransforms.append(shaperOCIOTransform)
774 cs.fromReferenceTransforms.append({
777 'interpolation': 'tetrahedral',
778 'direction': 'forward'
782 # Generate the inverse transform
784 cs.toReferenceTransforms = []
786 if 'transformCTLInverse' in lmtValues:
789 acesCTLReleaseDir, odtValues['transformCTLInverse']),
790 shaperFromACESCTL % acesCTLReleaseDir
792 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
794 # Remove spaces and parentheses
795 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
797 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
807 cs.toReferenceTransforms.append({
810 'interpolation': 'tetrahedral',
811 'direction': 'forward'
814 shaperInverse = shaperOCIOTransform.copy()
815 shaperInverse['direction'] = 'forward'
816 cs.toReferenceTransforms.append(shaperInverse)
824 lmtLutResolution1d = max(4096, lutResolution1d)
825 lmtLutResolution3d = max(65, lutResolution3d)
828 lmtShaperName = 'LMT Shaper'
831 'minExposure': -10.0,
834 lmtShaper = createGenericLog(name=lmtShaperName,
835 middleGrey=lmtParams['middleGrey'],
836 minExposure=lmtParams['minExposure'],
837 maxExposure=lmtParams['maxExposure'],
838 lutResolution1d=lmtLutResolution1d)
839 configData['colorSpaces'].append(lmtShaper)
841 shaperInputScale_genericLog2 = 1.0
843 # Log 2 shaper name and CTL transforms bundled up
846 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
847 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
848 shaperInputScale_genericLog2,
852 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
854 for lmt in sortedLMTs:
855 (lmtName, lmtValues) = lmt
857 lmtValues['transformUserName'],
863 configData['colorSpaces'].append(cs)
866 # ACES RRT with the supplied ODT
868 def createACESRRTplusODT(odtName,
871 lutResolution1d=1024,
874 cs = ColorSpace("%s" % odtName)
875 cs.description = "%s - %s Output Transform" % (
876 odtValues['transformUserNamePrefix'], odtName)
877 cs.equalityGroup = ''
883 pprint.pprint(odtValues)
886 # Generate the shaper transform
888 # if 'shaperCTL' in odtValues:
889 (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale,
890 shaperParams) = shaperInfo
892 if 'legalRange' in odtValues:
893 shaperParams['legalRange'] = odtValues['legalRange']
895 shaperParams['legalRange'] = 0
897 shaperLut = "%s_to_aces.spi1d" % shaperName
898 if ( not os.path.exists(lutDir + "/" + shaperLut) ):
900 shaperToACESCTL % acesCTLReleaseDir
903 # Remove spaces and parentheses
904 shaperLut = shaperLut.replace(' ', '_').replace(')', '_').replace(
907 genlut.generate1dLUTFromCTL(lutDir + "/" + shaperLut,
911 1.0 / shaperInputScale,
917 shaperOCIOTransform = {
920 'interpolation': 'linear',
921 'direction': 'inverse'
925 # Generate the forward transform
927 cs.fromReferenceTransforms = []
929 if 'transformLUT' in odtValues:
930 # Copy into the lut dir
931 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
932 lut = lutDir + "/" + transformLUTFileName
933 shutil.copy(odtValues['transformLUT'], lut)
935 cs.fromReferenceTransforms.append(shaperOCIOTransform)
936 cs.fromReferenceTransforms.append({
938 'path': transformLUTFileName,
939 'interpolation': 'tetrahedral',
940 'direction': 'forward'
942 elif 'transformCTL' in odtValues:
946 shaperToACESCTL % acesCTLReleaseDir,
947 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
948 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
950 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
952 # Remove spaces and parentheses
953 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
955 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
960 1.0 / shaperInputScale,
966 cs.fromReferenceTransforms.append(shaperOCIOTransform)
967 cs.fromReferenceTransforms.append({
970 'interpolation': 'tetrahedral',
971 'direction': 'forward'
975 # Generate the inverse transform
977 cs.toReferenceTransforms = []
979 if 'transformLUTInverse' in odtValues:
980 # Copy into the lut dir
981 transformLUTInverseFileName = os.path.basename(
982 odtValues['transformLUTInverse'])
983 lut = lutDir + "/" + transformLUTInverseFileName
984 shutil.copy(odtValues['transformLUTInverse'], lut)
986 cs.toReferenceTransforms.append({
988 'path': transformLUTInverseFileName,
989 'interpolation': 'tetrahedral',
990 'direction': 'forward'
993 shaperInverse = shaperOCIOTransform.copy()
994 shaperInverse['direction'] = 'forward'
995 cs.toReferenceTransforms.append(shaperInverse)
996 elif 'transformCTLInverse' in odtValues:
999 acesCTLReleaseDir, odtValues['transformCTLInverse']),
1000 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1001 shaperFromACESCTL % acesCTLReleaseDir
1003 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1005 # Remove spaces and parentheses
1006 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1008 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1019 cs.toReferenceTransforms.append({
1022 'interpolation': 'tetrahedral',
1023 'direction': 'forward'
1026 shaperInverse = shaperOCIOTransform.copy()
1027 shaperInverse['direction'] = 'forward'
1028 cs.toReferenceTransforms.append(shaperInverse)
1033 # RRT/ODT shaper options
1038 log2ShaperName = shaperName
1041 'minExposure': -6.0,
1044 log2Shaper = createGenericLog(name=log2ShaperName,
1045 middleGrey=log2Params['middleGrey'],
1046 minExposure=log2Params['minExposure'],
1047 maxExposure=log2Params['maxExposure'])
1048 configData['colorSpaces'].append(log2Shaper)
1050 shaperInputScale_genericLog2 = 1.0
1052 # Log 2 shaper name and CTL transforms bundled up
1055 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1056 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1057 shaperInputScale_genericLog2,
1061 shaperData[log2ShaperName] = log2ShaperData
1064 # Shaper that also includes the AP1 primaries
1065 # - Needed for some LUT baking steps
1067 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1068 middleGrey=log2Params['middleGrey'],
1069 minExposure=log2Params['minExposure'],
1070 maxExposure=log2Params['maxExposure'])
1071 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1072 # AP1 primaries to AP0 primaries
1073 log2ShaperAP1.toReferenceTransforms.append({
1075 'matrix': mat44FromMat33(acesAP1toAP0),
1076 'direction': 'forward'
1078 configData['colorSpaces'].append(log2ShaperAP1)
1081 # Choose your shaper
1083 rrtShaperName = log2ShaperName
1084 rrtShaper = log2ShaperData
1087 # RRT + ODT Combinations
1089 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1091 for odt in sortedOdts:
1092 (odtName, odtValues) = odt
1094 # Have to handle ODTs that can generate either legal or full output
1095 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1096 'Academy.Rec709_100nits_dim.a1.0.0',
1097 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1098 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1100 odtNameLegal = odtValues['transformUserName']
1102 odtLegal = odtValues.copy()
1103 odtLegal['legalRange'] = 1
1105 cs = createACESRRTplusODT(
1112 configData['colorSpaces'].append(cs)
1114 # Create a display entry using this color space
1115 configData['displays'][odtNameLegal] = {
1118 'Output Transform': cs}
1120 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1121 'Academy.Rec709_100nits_dim.a1.0.0',
1122 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1123 print( "Generating full range ODT for %s" % odtName)
1125 odtNameFull = "%s - Full" % odtValues['transformUserName']
1126 odtFull = odtValues.copy()
1127 odtFull['legalRange'] = 0
1129 csFull = createACESRRTplusODT(
1136 configData['colorSpaces'].append(csFull)
1138 # Create a display entry using this color space
1139 configData['displays'][odtNameFull] = {
1142 'Output Transform': csFull}
1145 # Generic Matrix transform
1147 def createGenericMatrix(name='matrix',
1148 fromReferenceValues=[],
1149 toReferenceValues=[]):
1150 cs = ColorSpace(name)
1151 cs.description = "The %s color space" % name
1152 cs.equalityGroup = name
1153 cs.family = 'Utility'
1156 cs.toReferenceTransforms = []
1157 if toReferenceValues != []:
1158 for matrix in toReferenceValues:
1159 cs.toReferenceTransforms.append({
1161 'matrix': mat44FromMat33(matrix),
1162 'direction': 'forward'
1165 cs.fromReferenceTransforms = []
1166 if fromReferenceValues != []:
1167 for matrix in fromReferenceValues:
1168 cs.fromReferenceTransforms.append({
1170 'matrix': mat44FromMat33(matrix),
1171 'direction': 'forward'
1176 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1177 configData['colorSpaces'].append(cs)
1179 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1180 configData['colorSpaces'].append(cs)
1182 # ACES to Linear, P3D60 primaries
1183 xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1184 -0.8325796487, 1.7692317536, 0.0237127115,
1185 0.0388233815, -0.0824996856, 1.0363685997]
1187 cs = createGenericMatrix('Linear - P3-D60',
1188 fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1189 configData['colorSpaces'].append(cs)
1191 # ACES to Linear, P3D60 primaries
1192 xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1193 -0.7951680258, 1.6897320548, 0.0226471906,
1194 0.0412418914, -0.0876390192, 1.1009293786]
1196 cs = createGenericMatrix('Linear - P3-DCI',
1197 fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1198 configData['colorSpaces'].append(cs)
1200 # ACES to Linear, Rec 709 primaries
1201 xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1202 -0.9692436363, 1.8759675015, 0.0415550574,
1203 0.0556300797, -0.2039769589, 1.0569715142]
1205 cs = createGenericMatrix('Linear - Rec.709',
1206 fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1207 configData['colorSpaces'].append(cs)
1209 # ACES to Linear, Rec 2020 primaries
1210 xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1211 -0.6666843518, 1.6164812366, 0.0157685458,
1212 0.0176398574, -0.0427706133, 0.9421031212]
1214 cs = createGenericMatrix('Linear - Rec.2020',
1215 fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1216 configData['colorSpaces'].append(cs)
1218 print( "generateLUTs - end" )
1222 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath,
1223 lutResolution1d, lutResolution3d,
1224 lutResolutionShaper=1024):
1225 # Add the legal and full variations into this list
1226 odtInfoC = dict(odtInfo)
1227 for odtCTLName, odtValues in odtInfo.iteritems():
1228 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1229 'Academy.Rec709_100nits_dim.a1.0.0',
1230 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1231 odtName = odtValues["transformUserName"]
1233 odtValuesLegal = dict(odtValues)
1234 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1235 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1237 odtValuesFull = dict(odtValues)
1238 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1239 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1241 del ( odtInfoC[odtCTLName] )
1243 for odtCTLName, odtValues in odtInfoC.iteritems():
1244 odtPrefix = odtValues["transformUserNamePrefix"]
1245 odtName = odtValues["transformUserName"]
1248 for inputspace in ["ACEScc", "ACESproxy"]:
1249 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1250 args += ["--outputspace", "%s" % odtName]
1251 args += ["--description",
1252 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1253 args += ["--shaperspace", shaperName, "--shapersize",
1254 str(lutResolutionShaper)]
1255 args += ["--cubesize", str(lutResolution3d)]
1256 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1257 bakedDir, odtName, inputspace)]
1259 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1264 for inputspace in ["ACEScc", "ACESproxy"]:
1265 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1266 args += ["--outputspace", "%s" % odtName]
1267 args += ["--description",
1268 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1269 args += ["--shaperspace", shaperName, "--shapersize",
1270 str(lutResolutionShaper)]
1271 args += ["--cubesize", str(lutResolution3d)]
1273 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1274 bakedDir, odtName, inputspace)]
1275 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1276 args=(args + fargs))
1279 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1280 bakedDir, odtName, inputspace)]
1281 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1282 args=(args + largs))
1286 for inputspace in ["ACEScg", "ACES2065-1"]:
1287 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1288 args += ["--outputspace", "%s" % odtName]
1289 args += ["--description",
1290 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1291 if inputspace == 'ACEScg':
1292 linShaperName = "%s - AP1" % shaperName
1294 linShaperName = shaperName
1295 args += ["--shaperspace", linShaperName, "--shapersize",
1296 str(lutResolutionShaper)]
1298 args += ["--cubesize", str(lutResolution3d)]
1300 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1301 bakedDir, odtName, inputspace)]
1302 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1303 args=(args + margs))
1306 hargs = ["--format", "houdini",
1307 "%s/houdini/%s for %s Houdini.lut" % (
1308 bakedDir, odtName, inputspace)]
1309 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1310 args=(args + hargs))
1314 def createConfigDir(configDir, bakeSecondaryLUTs):
1315 dirs = [configDir, "%s/luts" % configDir]
1316 if bakeSecondaryLUTs:
1317 dirs.extend(["%s/baked" % configDir,
1318 "%s/baked/flame" % configDir,
1319 "%s/baked/photoshop" % configDir,
1320 "%s/baked/houdini" % configDir,
1321 "%s/baked/lustre" % configDir,
1322 "%s/baked/maya" % configDir])
1325 if not os.path.exists(d):
1329 def getTransformInfo(ctlTransform):
1330 fp = open(ctlTransform, 'rb')
1333 lines = fp.readlines()
1335 # Grab transform ID and User Name
1336 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1337 # print( transformID )
1338 transformUserName = '-'.join(
1339 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1340 1:]).lstrip().rstrip()
1341 transformUserNamePrefix = \
1342 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1343 0].lstrip().rstrip()
1344 # print( transformUserName )
1347 return (transformID, transformUserName, transformUserNamePrefix)
1350 # For versions after WGR9
1351 def getODTInfo(acesCTLReleaseDir):
1352 # Credit to Alex Fry for the original approach here
1353 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1355 for dirName, subdirList, fileList in os.walk(odtDir):
1356 for fname in fileList:
1357 allodt.append((os.path.join(dirName, fname)))
1359 odtCTLs = [x for x in allodt if
1360 ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1366 for odtCTL in odtCTLs:
1367 odtTokens = os.path.split(odtCTL)
1368 # print( odtTokens )
1370 # Handle nested directories
1371 odtPathTokens = os.path.split(odtTokens[-2])
1372 odtDir = odtPathTokens[-1]
1373 while odtPathTokens[-2][-3:] != 'odt':
1374 odtPathTokens = os.path.split(odtPathTokens[-2])
1375 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1378 # print( "odtDir : %s" % odtDir )
1379 transformCTL = odtTokens[-1]
1380 # print( transformCTL )
1381 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1384 # Find id, user name and user name prefix
1385 (transformID, transformUserName,
1386 transformUserNamePrefix) = getTransformInfo(
1387 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1390 transformCTLInverse = "InvODT.%s.ctl" % odtName
1391 if not os.path.exists(
1392 os.path.join(odtTokens[-2], transformCTLInverse)):
1393 transformCTLInverse = None
1394 #print( transformCTLInverse )
1396 # Add to list of ODTs
1398 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1399 if transformCTLInverse != None:
1400 odts[odtName]['transformCTLInverse'] = os.path.join(odtDir,
1401 transformCTLInverse)
1403 odts[odtName]['transformID'] = transformID
1404 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1405 odts[odtName]['transformUserName'] = transformUserName
1407 print( "ODT : %s" % odtName )
1408 print( "\tTransform ID : %s" % transformID )
1409 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1410 print( "\tTransform User Name : %s" % transformUserName )
1412 "\tForward ctl : %s" % odts[odtName][
1414 if 'transformCTLInverse' in odts[odtName]:
1415 print("\tInverse ctl : %s" % odts[odtName][
1416 'transformCTLInverse'])
1418 print( "\tInverse ctl : %s" % "None" )
1425 # For versions after WGR9
1426 def getLMTInfo(acesCTLReleaseDir):
1427 # Credit to Alex Fry for the original approach here
1428 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1430 for dirName, subdirList, fileList in os.walk(lmtDir):
1431 for fname in fileList:
1432 alllmt.append((os.path.join(dirName, fname)))
1434 lmtCTLs = [x for x in alllmt if
1435 ("InvLMT" not in x) and ("README" not in x) and (
1436 os.path.split(x)[-1][0] != '.')]
1442 for lmtCTL in lmtCTLs:
1443 lmtTokens = os.path.split(lmtCTL)
1444 # print( lmtTokens )
1446 # Handle nested directories
1447 lmtPathTokens = os.path.split(lmtTokens[-2])
1448 lmtDir = lmtPathTokens[-1]
1449 while lmtPathTokens[-2][-3:] != 'ctl':
1450 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1451 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1454 # print( "lmtDir : %s" % lmtDir )
1455 transformCTL = lmtTokens[-1]
1456 # print( transformCTL )
1457 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1460 # Find id, user name and user name prefix
1461 (transformID, transformUserName,
1462 transformUserNamePrefix) = getTransformInfo(
1463 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1466 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1467 if not os.path.exists(
1468 os.path.join(lmtTokens[-2], transformCTLInverse)):
1469 transformCTLInverse = None
1470 #print( transformCTLInverse )
1472 # Add to list of LMTs
1474 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1475 if transformCTLInverse != None:
1476 # TODO: Check unresolved *odtName* referemce.
1477 lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir,
1478 transformCTLInverse)
1480 lmts[lmtName]['transformID'] = transformID
1481 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1482 lmts[lmtName]['transformUserName'] = transformUserName
1484 print( "LMT : %s" % lmtName )
1485 print( "\tTransform ID : %s" % transformID )
1486 print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1487 print( "\tTransform User Name : %s" % transformUserName )
1488 print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1489 if 'transformCTLInverse' in lmts[lmtName]:
1491 "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1493 print( "\t Inverse ctl : %s" % "None" )
1501 # Create the ACES config
1503 def createACESConfig(acesCTLReleaseDir,
1505 lutResolution1d=4096,
1507 bakeSecondaryLUTs=True,
1509 # Get ODT names and CTL paths
1510 odtInfo = getODTInfo(acesCTLReleaseDir)
1512 # Get ODT names and CTL paths
1513 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1516 createConfigDir(configDir, bakeSecondaryLUTs)
1518 # Generate config data and LUTs for different transforms
1519 lutDir = "%s/luts" % configDir
1520 shaperName = 'Output Shaper'
1521 configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir,
1522 lutDir, lutResolution1d, lutResolution3d,
1525 # Create the config using the generated LUTs
1526 print( "Creating generic config")
1527 config = createConfig(configData)
1530 # Write the config to disk
1531 writeConfig(config, "%s/config.ocio" % configDir)
1533 # Create a config that will work well with Nuke using the previously generated LUTs
1534 print( "Creating Nuke-specific config")
1535 nuke_config = createConfig(configData, nuke=True)
1538 # Write the config to disk
1539 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1541 # Bake secondary LUTs using the config
1542 if bakeSecondaryLUTs:
1543 generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir,
1544 "%s/config.ocio" % configDir, lutResolution1d,
1545 lutResolution3d, lutResolution1d)
1556 p = optparse.OptionParser(description='An OCIO config generation script',
1557 prog='createACESConfig',
1558 version='createACESConfig 0.1',
1559 usage='%prog [options]')
1560 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1561 'ACES_OCIO_CTL_DIRECTORY', None))
1562 p.add_option('--configDir', '-c', default=os.environ.get(
1563 'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1564 p.add_option('--lutResolution1d', default=4096)
1565 p.add_option('--lutResolution3d', default=64)
1566 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1567 p.add_option('--keepTempImages', action="store_true")
1569 options, arguments = p.parse_args()
1574 acesCTLDir = options.acesCTLDir
1575 configDir = options.configDir
1576 lutResolution1d = int(options.lutResolution1d)
1577 lutResolution3d = int(options.lutResolution3d)
1578 bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1579 cleanupTempImages = not (options.keepTempImages)
1582 argsStart = sys.argv.index('--') + 1
1583 args = sys.argv[argsStart:]
1585 argsStart = len(sys.argv) + 1
1588 print( "command line : \n%s\n" % " ".join(sys.argv) )
1590 # TODO: Use assertion and mention environment variables.
1592 print( "process: No ACES CTL directory specified" )
1595 print( "process: No configuration directory specified" )
1598 # Generate the configuration
1600 createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d,
1601 bakeSecondaryLUTs, cleanupTempImages)
1605 if __name__ == '__main__':