2 # -*- coding: utf-8 -*-
5 Defines objects creating the *ACES* configuration.
15 # TODO: This restores the capability of running the script without having
16 # added the package to PYTHONPATH, this is ugly and should ideally replaced by
17 # dedicated executable in a /bin directory.
18 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
20 import PyOpenColorIO as OCIO
22 import aces_ocio.createARRIColorSpaces as arri
23 import aces_ocio.createCanonColorSpaces as canon
24 import aces_ocio.createREDColorSpaces as red
25 import aces_ocio.createSonyColorSpaces as sony
26 import aces_ocio.generateLUT as genlut
27 from aces_ocio.process import Process
28 from aces_ocio.util import ColorSpace, mat44FromMat33
30 __author__ = 'ACES Developers'
31 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
33 __maintainer__ = 'ACES Developers'
34 __email__ = 'aces@oscars.org'
35 __status__ = 'Production'
37 __all__ = ['ACES_OCIO_CTL_DIRECTORY_ENVIRON',
38 'ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON',
39 'setConfigDefaultRoles',
41 'generateOCIOTransform',
52 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
53 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
56 def setConfigDefaultRoles(config,
67 Sets given *OCIO* configuration default roles.
73 color_picking : str or unicode
74 Color picking role title.
75 color_timing : str or unicode
76 Color timing role title.
77 compositing_log : str or unicode
78 Compositing log role title.
81 default : str or unicode
83 matte_paint : str or unicode
84 Matte painting role title.
85 reference : str or unicode
87 scene_linear : str or unicode
88 Scene linear role title.
89 texture_paint : str or unicode
90 Texture painting role title.
99 config.setRole(OCIO.Constants.ROLE_COLOR_PICKING, color_picking)
101 config.setRole(OCIO.Constants.ROLE_COLOR_TIMING, color_timing)
103 config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log)
105 config.setRole(OCIO.Constants.ROLE_DATA, data)
107 config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
109 config.setRole(OCIO.Constants.ROLE_MATTE_PAINT, matte_paint)
111 config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
113 config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear)
115 config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint)
120 def writeConfig(config, configPath, sanityCheck=True):
122 Writes the configuration to given path.
127 Parameter description.
132 Return value description.
140 print "Configuration was not written due to a failed Sanity Check"
144 fileHandle = open(configPath, mode='w')
145 fileHandle.write(config.serialize())
149 def generateOCIOTransform(transforms):
156 Parameter description.
161 Return value description.
164 # print("Generating transforms")
166 interpolationOptions = {
167 'linear': OCIO.Constants.INTERP_LINEAR,
168 'nearest': OCIO.Constants.INTERP_NEAREST,
169 'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
172 'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
173 'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
178 for transform in transforms:
179 if transform['type'] == 'lutFile':
180 ocioTransform = OCIO.FileTransform(
181 src=transform['path'],
182 interpolation=interpolationOptions[transform['interpolation']],
183 direction=directionOptions[transform['direction']])
184 ocioTransforms.append(ocioTransform)
185 elif transform['type'] == 'matrix':
186 ocioTransform = OCIO.MatrixTransform()
187 # MatrixTransform member variables can't be initialized directly.
188 # Each must be set individually.
189 ocioTransform.setMatrix(transform['matrix'])
191 if 'offset' in transform:
192 ocioTransform.setOffset(transform['offset'])
194 if 'direction' in transform:
195 ocioTransform.setDirection(
196 directionOptions[transform['direction']])
198 ocioTransforms.append(ocioTransform)
199 elif transform['type'] == 'exponent':
200 ocioTransform = OCIO.ExponentTransform()
201 ocioTransform.setValue(transform['value'])
202 ocioTransforms.append(ocioTransform)
203 elif transform['type'] == 'log':
204 ocioTransform = OCIO.LogTransform(
205 base=transform['base'],
206 direction=directionOptions[transform['direction']])
208 ocioTransforms.append(ocioTransform)
210 print("Ignoring unknown transform type : %s" % transform['type'])
212 # Build a group transform if necessary
213 if len(ocioTransforms) > 1:
214 transformG = OCIO.GroupTransform()
215 for transform in ocioTransforms:
216 transformG.push_back(transform)
217 transform = transformG
219 # Or take the first transform from the list
221 transform = ocioTransforms[0]
226 def createConfig(configData, nuke=False):
233 Parameter description.
238 Return value description.
242 config = OCIO.Config()
245 # Set config wide values
247 config.setDescription("An ACES config generated from python")
248 config.setSearchPath("luts")
251 # Define the reference color space
253 referenceData = configData['referenceColorSpace']
254 print("Adding the reference color space : %s" % referenceData.name)
256 # Create a color space
257 reference = OCIO.ColorSpace(
258 name=referenceData.name,
259 bitDepth=referenceData.bitDepth,
260 description=referenceData.description,
261 equalityGroup=referenceData.equalityGroup,
262 family=referenceData.family,
263 isData=referenceData.isData,
264 allocation=referenceData.allocationType,
265 allocationVars=referenceData.allocationVars)
268 config.addColorSpace(reference)
271 # Create the rest of the color spaces
273 for colorspace in sorted(configData['colorSpaces']):
274 print("Creating new color space : %s" % colorspace.name)
276 ocioColorspace = OCIO.ColorSpace(
277 name=colorspace.name,
278 bitDepth=colorspace.bitDepth,
279 description=colorspace.description,
280 equalityGroup=colorspace.equalityGroup,
281 family=colorspace.family,
282 isData=colorspace.isData,
283 allocation=colorspace.allocationType,
284 allocationVars=colorspace.allocationVars)
286 if colorspace.toReferenceTransforms != []:
287 print("Generating To-Reference transforms")
288 ocioTransform = generateOCIOTransform(
289 colorspace.toReferenceTransforms)
290 ocioColorspace.setTransform(
292 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
294 if colorspace.fromReferenceTransforms != []:
295 print("Generating From-Reference transforms")
296 ocioTransform = generateOCIOTransform(
297 colorspace.fromReferenceTransforms)
298 ocioColorspace.setTransform(
300 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
302 config.addColorSpace(ocioColorspace)
307 # Define the views and displays
312 # Generic display and view setup
314 for display, viewList in configData['displays'].iteritems():
315 for viewName, colorspace in viewList.iteritems():
316 config.addDisplay(display, viewName, colorspace.name)
317 if not (viewName in views):
318 views.append(viewName)
319 displays.append(display)
320 # A Nuke specific set of views and displays
323 # A few names: Output Transform, ACES, ACEScc, are hard-coded here.
324 # Would be better to automate.
327 for display, viewList in configData['displays'].iteritems():
328 for viewName, colorspace in viewList.iteritems():
329 if (viewName == 'Output Transform'):
331 config.addDisplay(display, viewName, colorspace.name)
332 if not (viewName in views):
333 views.append(viewName)
334 displays.append(display)
336 config.addDisplay('linear', 'View', 'ACES2065-1')
337 displays.append('linear')
338 config.addDisplay('log', 'View', 'ACEScc')
339 displays.append('log')
341 # Set active displays and views
342 config.setActiveDisplays(','.join(sorted(displays)))
343 config.setActiveViews(','.join(views))
346 # Need to generalize this at some point
350 setConfigDefaultRoles(config,
351 color_picking=reference.getName(),
352 color_timing=reference.getName(),
353 compositing_log=reference.getName(),
354 data=reference.getName(),
355 default=reference.getName(),
356 matte_paint=reference.getName(),
357 reference=reference.getName(),
358 scene_linear=reference.getName(),
359 texture_paint=reference.getName())
361 # Check to make sure we didn't screw something up
367 def generateLUTs(odtInfo,
372 lutResolution1d=4096,
381 Parameter description.
386 Colorspaces and transforms converting between those colorspaces and
387 the reference colorspace, *ACES*.
390 print("generateLUTs - begin")
394 # Define the reference color space
396 ACES = ColorSpace('ACES2065-1')
398 'The Academy Color Encoding System reference color space')
399 ACES.equalityGroup = ''
402 ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
403 ACES.allocationVars = [-15, 6]
405 configData['referenceColorSpace'] = ACES
408 # Define the displays
410 configData['displays'] = {}
413 # Define the other color spaces
415 configData['colorSpaces'] = []
417 # Matrix converting ACES AP1 primaries to AP0
418 acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
419 0.0447945634, 0.8596711185, 0.0955343182,
420 -0.0055258826, 0.0040252103, 1.0015006723]
422 # Matrix converting ACES AP0 primaries to XYZ
423 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
424 0.3439664498, 0.7281660966, -0.0721325464,
425 0.0000000000, 0.0000000000, 1.0088251844]
430 def createACEScc(name='ACEScc',
434 cs = ColorSpace(name)
435 cs.description = "The %s color space" % name
436 cs.equalityGroup = ''
441 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
442 # This transform gets back to the AP1 primaries
443 # Useful as the 1d LUT is only covering the transfer function
444 # The primaries switch is covered by the matrix below
445 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
447 lut = "%s_to_ACES.spi1d" % name
449 # Remove spaces and parentheses
450 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
452 genlut.generate1dLUTFromCTL(
465 cs.toReferenceTransforms = []
466 cs.toReferenceTransforms.append({
469 'interpolation': 'linear',
470 'direction': 'forward'
473 # AP1 primaries to AP0 primaries
474 cs.toReferenceTransforms.append({
476 'matrix': mat44FromMat33(acesAP1toAP0),
477 'direction': 'forward'
480 cs.fromReferenceTransforms = []
483 ACEScc = createACEScc()
484 configData['colorSpaces'].append(ACEScc)
489 def createACESProxy(name='ACESproxy'):
490 cs = ColorSpace(name)
491 cs.description = "The %s color space" % name
492 cs.equalityGroup = ''
497 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % (
499 # This transform gets back to the AP1 primaries
500 # Useful as the 1d LUT is only covering the transfer function
501 # The primaries switch is covered by the matrix below
502 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
504 lut = "%s_to_aces.spi1d" % name
506 # Remove spaces and parentheses
507 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
509 genlut.generate1dLUTFromCTL(
520 cs.toReferenceTransforms = []
521 cs.toReferenceTransforms.append({
524 'interpolation': 'linear',
525 'direction': 'forward'
528 # AP1 primaries to AP0 primaries
529 cs.toReferenceTransforms.append({
531 'matrix': mat44FromMat33(acesAP1toAP0),
532 'direction': 'forward'
535 cs.fromReferenceTransforms = []
538 ACESproxy = createACESProxy()
539 configData['colorSpaces'].append(ACESproxy)
544 def createACEScg(name='ACEScg'):
545 cs = ColorSpace(name)
546 cs.description = "The %s color space" % name
547 cs.equalityGroup = ''
551 cs.toReferenceTransforms = []
553 # AP1 primaries to AP0 primaries
554 cs.toReferenceTransforms.append({
556 'matrix': mat44FromMat33(acesAP1toAP0),
557 'direction': 'forward'
560 cs.fromReferenceTransforms = []
563 ACEScg = createACEScg()
564 configData['colorSpaces'].append(ACEScg)
569 def createADX(bitdepth=10, name='ADX'):
570 name = "%s%s" % (name, bitdepth)
571 cs = ColorSpace(name)
572 cs.description = "%s color space - used for film scans" % name
573 cs.equalityGroup = ''
578 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
579 adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
580 0.0, 1023.0 / 500.0, 0.0, 0.0,
581 0.0, 0.0, 1023.0 / 500.0, 0.0,
583 offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
585 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
586 adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
587 0.0, 65535.0 / 8000.0, 0.0, 0.0,
588 0.0, 0.0, 65535.0 / 8000.0, 0.0,
590 offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
593 cs.toReferenceTransforms = []
595 # Convert from ADX to Channel-Dependent Density
596 cs.toReferenceTransforms.append({
598 'matrix': adx_to_cdd,
600 'direction': 'forward'
603 # Convert from Channel-Dependent Density to Channel-Independent Density
604 cs.toReferenceTransforms.append({
606 'matrix': [0.75573, 0.22197, 0.02230, 0,
607 0.05901, 0.96928, -0.02829, 0,
608 0.16134, 0.07406, 0.76460, 0,
610 'direction': 'forward'
613 # Copied from Alex Fry's adx_cid_to_rle.py
614 def createCIDtoRLELUT():
615 def interpolate1D(x, xp, fp):
616 return numpy.interp(x, xp, fp)
618 LUT_1D_xp = [-0.190000000000000,
630 LUT_1D_fp = [-6.000000000000000,
642 REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
643 math.log(0.18, 10.0))
647 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
648 return (100.0 / 55.0) * x - REF_PT
650 def Fit(value, fromMin, fromMax, toMin, toMax):
651 if fromMin == fromMax:
652 raise ValueError("fromMin == fromMax")
653 return (value - fromMin) / (fromMax - fromMin) * (
654 toMax - toMin) + toMin
656 NUM_SAMPLES = 2 ** 12
659 for i in xrange(NUM_SAMPLES):
660 x = i / (NUM_SAMPLES - 1.0)
661 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
662 data.append(cid_to_rle(x))
664 lut = 'ADX_CID_to_RLE.spi1d'
665 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
670 # Convert Channel Independent Density values to Relative Log Exposure
672 lut = createCIDtoRLELUT()
673 cs.toReferenceTransforms.append({
676 'interpolation': 'linear',
677 'direction': 'forward'
680 # Convert Relative Log Exposure values to Relative Exposure values
681 cs.toReferenceTransforms.append({
684 'direction': 'inverse'
687 # Convert Relative Exposure values to ACES values
688 cs.toReferenceTransforms.append({
690 'matrix': [0.72286, 0.12630, 0.15084, 0,
691 0.11923, 0.76418, 0.11659, 0,
692 0.01427, 0.08213, 0.90359, 0,
694 'direction': 'forward'
697 cs.fromReferenceTransforms = []
700 ADX10 = createADX(bitdepth=10)
701 configData['colorSpaces'].append(ADX10)
703 ADX16 = createADX(bitdepth=16)
704 configData['colorSpaces'].append(ADX16)
707 # Camera Input Transforms
710 # RED color spaces to ACES
711 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
712 for cs in redColorSpaces:
713 configData['colorSpaces'].append(cs)
716 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
717 for cs in canonColorSpaces:
718 configData['colorSpaces'].append(cs)
721 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
722 for cs in sonyColorSpaces:
723 configData['colorSpaces'].append(cs)
726 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
727 for cs in arriColorSpaces:
728 configData['colorSpaces'].append(cs)
731 # Generic log transform
733 def createGenericLog(name='log',
740 lutResolution1d=lutResolution1d):
741 cs = ColorSpace(name)
742 cs.description = "The %s color space" % name
743 cs.equalityGroup = name
744 cs.family = 'Utility'
748 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % (
751 lut = "%s_to_aces.spi1d" % name
753 # Remove spaces and parentheses
754 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
756 genlut.generate1dLUTFromCTL(
764 'middleGrey': middleGrey,
765 'minExposure': minExposure,
766 'maxExposure': maxExposure
773 cs.toReferenceTransforms = []
774 cs.toReferenceTransforms.append({
777 'interpolation': 'linear',
778 'direction': 'forward'
781 cs.fromReferenceTransforms = []
787 def createACESLMT(lmtName,
790 lutResolution1d=1024,
793 cs = ColorSpace("%s" % lmtName)
794 cs.description = "The ACES Look Transform: %s" % lmtName
795 cs.equalityGroup = ''
801 pprint.pprint(lmtValues)
804 # Generate the shaper transform
810 shaperParams) = shaperInfo
812 shaperLut = "%s_to_aces.spi1d" % shaperName
813 if (not os.path.exists(lutDir + "/" + shaperLut)):
815 shaperToACESCTL % acesCTLReleaseDir
818 # Remove spaces and parentheses
819 shaperLut = shaperLut.replace(
820 ' ', '_').replace(')', '_').replace('(', '_')
822 genlut.generate1dLUTFromCTL(
823 lutDir + "/" + shaperLut,
827 1.0 / shaperInputScale,
833 shaperOCIOTransform = {
836 'interpolation': 'linear',
837 'direction': 'inverse'
841 # Generate the forward transform
843 cs.fromReferenceTransforms = []
845 if 'transformCTL' in lmtValues:
847 shaperToACESCTL % acesCTLReleaseDir,
848 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
850 lut = "%s.%s.spi3d" % (shaperName, lmtName)
852 # Remove spaces and parentheses
853 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
855 genlut.generate3dLUTFromCTL(
860 1.0 / shaperInputScale,
866 cs.fromReferenceTransforms.append(shaperOCIOTransform)
867 cs.fromReferenceTransforms.append({
870 'interpolation': 'tetrahedral',
871 'direction': 'forward'
875 # Generate the inverse transform
877 cs.toReferenceTransforms = []
879 if 'transformCTLInverse' in lmtValues:
882 acesCTLReleaseDir, odtValues['transformCTLInverse']),
883 shaperFromACESCTL % acesCTLReleaseDir
885 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
887 # Remove spaces and parentheses
888 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
890 genlut.generate3dLUTFromCTL(
901 cs.toReferenceTransforms.append({
904 'interpolation': 'tetrahedral',
905 'direction': 'forward'
908 shaperInverse = shaperOCIOTransform.copy()
909 shaperInverse['direction'] = 'forward'
910 cs.toReferenceTransforms.append(shaperInverse)
918 lmtLutResolution1d = max(4096, lutResolution1d)
919 lmtLutResolution3d = max(65, lutResolution3d)
922 lmtShaperName = 'LMT Shaper'
925 'minExposure': -10.0,
928 lmtShaper = createGenericLog(name=lmtShaperName,
929 middleGrey=lmtParams['middleGrey'],
930 minExposure=lmtParams['minExposure'],
931 maxExposure=lmtParams['maxExposure'],
932 lutResolution1d=lmtLutResolution1d)
933 configData['colorSpaces'].append(lmtShaper)
935 shaperInputScale_genericLog2 = 1.0
937 # Log 2 shaper name and CTL transforms bundled up
940 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
941 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
942 shaperInputScale_genericLog2,
946 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
948 for lmt in sortedLMTs:
949 (lmtName, lmtValues) = lmt
951 lmtValues['transformUserName'],
957 configData['colorSpaces'].append(cs)
960 # ACES RRT with the supplied ODT
962 def createACESRRTplusODT(odtName,
965 lutResolution1d=1024,
968 cs = ColorSpace("%s" % odtName)
969 cs.description = "%s - %s Output Transform" % (
970 odtValues['transformUserNamePrefix'], odtName)
971 cs.equalityGroup = ''
977 pprint.pprint(odtValues)
980 # Generate the shaper transform
982 # if 'shaperCTL' in odtValues:
987 shaperParams) = shaperInfo
989 if 'legalRange' in odtValues:
990 shaperParams['legalRange'] = odtValues['legalRange']
992 shaperParams['legalRange'] = 0
994 shaperLut = "%s_to_aces.spi1d" % shaperName
995 if (not os.path.exists(lutDir + "/" + shaperLut)):
997 shaperToACESCTL % acesCTLReleaseDir
1000 # Remove spaces and parentheses
1001 shaperLut = shaperLut.replace(
1002 ' ', '_').replace(')', '_').replace('(', '_')
1004 genlut.generate1dLUTFromCTL(
1005 lutDir + "/" + shaperLut,
1009 1.0 / shaperInputScale,
1015 shaperOCIOTransform = {
1018 'interpolation': 'linear',
1019 'direction': 'inverse'
1023 # Generate the forward transform
1025 cs.fromReferenceTransforms = []
1027 if 'transformLUT' in odtValues:
1028 # Copy into the lut dir
1029 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1030 lut = lutDir + "/" + transformLUTFileName
1031 shutil.copy(odtValues['transformLUT'], lut)
1033 cs.fromReferenceTransforms.append(shaperOCIOTransform)
1034 cs.fromReferenceTransforms.append({
1036 'path': transformLUTFileName,
1037 'interpolation': 'tetrahedral',
1038 'direction': 'forward'
1040 elif 'transformCTL' in odtValues:
1044 shaperToACESCTL % acesCTLReleaseDir,
1045 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1046 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1048 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1050 # Remove spaces and parentheses
1051 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1053 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1058 1.0 / shaperInputScale,
1064 cs.fromReferenceTransforms.append(shaperOCIOTransform)
1065 cs.fromReferenceTransforms.append({
1068 'interpolation': 'tetrahedral',
1069 'direction': 'forward'
1073 # Generate the inverse transform
1075 cs.toReferenceTransforms = []
1077 if 'transformLUTInverse' in odtValues:
1078 # Copy into the lut dir
1079 transformLUTInverseFileName = os.path.basename(
1080 odtValues['transformLUTInverse'])
1081 lut = lutDir + "/" + transformLUTInverseFileName
1082 shutil.copy(odtValues['transformLUTInverse'], lut)
1084 cs.toReferenceTransforms.append({
1086 'path': transformLUTInverseFileName,
1087 'interpolation': 'tetrahedral',
1088 'direction': 'forward'
1091 shaperInverse = shaperOCIOTransform.copy()
1092 shaperInverse['direction'] = 'forward'
1093 cs.toReferenceTransforms.append(shaperInverse)
1094 elif 'transformCTLInverse' in odtValues:
1097 acesCTLReleaseDir, odtValues['transformCTLInverse']),
1098 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1099 shaperFromACESCTL % acesCTLReleaseDir
1101 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1103 # Remove spaces and parentheses
1104 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1106 genlut.generate3dLUTFromCTL(
1118 cs.toReferenceTransforms.append({
1121 'interpolation': 'tetrahedral',
1122 'direction': 'forward'
1125 shaperInverse = shaperOCIOTransform.copy()
1126 shaperInverse['direction'] = 'forward'
1127 cs.toReferenceTransforms.append(shaperInverse)
1132 # RRT/ODT shaper options
1137 log2ShaperName = shaperName
1140 'minExposure': -6.0,
1143 log2Shaper = createGenericLog(name=log2ShaperName,
1144 middleGrey=log2Params['middleGrey'],
1145 minExposure=log2Params['minExposure'],
1146 maxExposure=log2Params['maxExposure'])
1147 configData['colorSpaces'].append(log2Shaper)
1149 shaperInputScale_genericLog2 = 1.0
1151 # Log 2 shaper name and CTL transforms bundled up
1154 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1155 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1156 shaperInputScale_genericLog2,
1160 shaperData[log2ShaperName] = log2ShaperData
1163 # Shaper that also includes the AP1 primaries
1164 # - Needed for some LUT baking steps
1166 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1167 middleGrey=log2Params['middleGrey'],
1168 minExposure=log2Params['minExposure'],
1169 maxExposure=log2Params['maxExposure'])
1170 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1171 # AP1 primaries to AP0 primaries
1172 log2ShaperAP1.toReferenceTransforms.append({
1174 'matrix': mat44FromMat33(acesAP1toAP0),
1175 'direction': 'forward'
1177 configData['colorSpaces'].append(log2ShaperAP1)
1180 # Choose your shaper
1182 rrtShaperName = log2ShaperName
1183 rrtShaper = log2ShaperData
1186 # RRT + ODT Combinations
1188 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1190 for odt in sortedOdts:
1191 (odtName, odtValues) = odt
1193 # Have to handle ODTs that can generate either legal or full output
1194 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1195 'Academy.Rec709_100nits_dim.a1.0.0',
1196 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1197 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1199 odtNameLegal = odtValues['transformUserName']
1201 odtLegal = odtValues.copy()
1202 odtLegal['legalRange'] = 1
1204 cs = createACESRRTplusODT(
1211 configData['colorSpaces'].append(cs)
1213 # Create a display entry using this color space
1214 configData['displays'][odtNameLegal] = {
1217 'Output Transform': cs}
1219 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1220 'Academy.Rec709_100nits_dim.a1.0.0',
1221 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1222 print("Generating full range ODT for %s" % odtName)
1224 odtNameFull = "%s - Full" % odtValues['transformUserName']
1225 odtFull = odtValues.copy()
1226 odtFull['legalRange'] = 0
1228 csFull = createACESRRTplusODT(
1235 configData['colorSpaces'].append(csFull)
1237 # Create a display entry using this color space
1238 configData['displays'][odtNameFull] = {
1241 'Output Transform': csFull}
1244 # Generic Matrix transform
1246 def createGenericMatrix(name='matrix',
1247 fromReferenceValues=[],
1248 toReferenceValues=[]):
1249 cs = ColorSpace(name)
1250 cs.description = "The %s color space" % name
1251 cs.equalityGroup = name
1252 cs.family = 'Utility'
1255 cs.toReferenceTransforms = []
1256 if toReferenceValues != []:
1257 for matrix in toReferenceValues:
1258 cs.toReferenceTransforms.append({
1260 'matrix': mat44FromMat33(matrix),
1261 'direction': 'forward'
1264 cs.fromReferenceTransforms = []
1265 if fromReferenceValues != []:
1266 for matrix in fromReferenceValues:
1267 cs.fromReferenceTransforms.append({
1269 'matrix': mat44FromMat33(matrix),
1270 'direction': 'forward'
1275 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1276 configData['colorSpaces'].append(cs)
1278 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1279 configData['colorSpaces'].append(cs)
1281 # ACES to Linear, P3D60 primaries
1282 xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1283 -0.8325796487, 1.7692317536, 0.0237127115,
1284 0.0388233815, -0.0824996856, 1.0363685997]
1286 cs = createGenericMatrix('Linear - P3-D60',
1287 fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1288 configData['colorSpaces'].append(cs)
1290 # ACES to Linear, P3D60 primaries
1291 xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1292 -0.7951680258, 1.6897320548, 0.0226471906,
1293 0.0412418914, -0.0876390192, 1.1009293786]
1295 cs = createGenericMatrix('Linear - P3-DCI',
1296 fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1297 configData['colorSpaces'].append(cs)
1299 # ACES to Linear, Rec 709 primaries
1300 xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1301 -0.9692436363, 1.8759675015, 0.0415550574,
1302 0.0556300797, -0.2039769589, 1.0569715142]
1304 cs = createGenericMatrix('Linear - Rec.709',
1305 fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1306 configData['colorSpaces'].append(cs)
1308 # ACES to Linear, Rec 2020 primaries
1309 xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1310 -0.6666843518, 1.6164812366, 0.0157685458,
1311 0.0176398574, -0.0427706133, 0.9421031212]
1313 cs = createGenericMatrix('Linear - Rec.2020',
1314 fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1315 configData['colorSpaces'].append(cs)
1317 print("generateLUTs - end")
1321 def generateBakedLUTs(odtInfo,
1327 lutResolutionShaper=1024):
1334 Parameter description.
1339 Return value description.
1342 # Add the legal and full variations into this list
1343 odtInfoC = dict(odtInfo)
1344 for odtCTLName, odtValues in odtInfo.iteritems():
1345 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1346 'Academy.Rec709_100nits_dim.a1.0.0',
1347 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1348 odtName = odtValues["transformUserName"]
1350 odtValuesLegal = dict(odtValues)
1351 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1352 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1354 odtValuesFull = dict(odtValues)
1355 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1356 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1358 del (odtInfoC[odtCTLName])
1360 for odtCTLName, odtValues in odtInfoC.iteritems():
1361 odtPrefix = odtValues["transformUserNamePrefix"]
1362 odtName = odtValues["transformUserName"]
1365 for inputspace in ["ACEScc", "ACESproxy"]:
1366 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1367 args += ["--outputspace", "%s" % odtName]
1368 args += ["--description",
1369 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1370 args += ["--shaperspace", shaperName, "--shapersize",
1371 str(lutResolutionShaper)]
1372 args += ["--cubesize", str(lutResolution3d)]
1373 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1374 bakedDir, odtName, inputspace)]
1376 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1381 for inputspace in ["ACEScc", "ACESproxy"]:
1382 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1383 args += ["--outputspace", "%s" % odtName]
1384 args += ["--description",
1385 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1386 args += ["--shaperspace", shaperName, "--shapersize",
1387 str(lutResolutionShaper)]
1388 args += ["--cubesize", str(lutResolution3d)]
1390 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1391 bakedDir, odtName, inputspace)]
1392 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1393 args=(args + fargs))
1396 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1397 bakedDir, odtName, inputspace)]
1398 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1399 args=(args + largs))
1403 for inputspace in ["ACEScg", "ACES2065-1"]:
1404 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1405 args += ["--outputspace", "%s" % odtName]
1406 args += ["--description",
1407 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1408 if inputspace == 'ACEScg':
1409 linShaperName = "%s - AP1" % shaperName
1411 linShaperName = shaperName
1412 args += ["--shaperspace", linShaperName, "--shapersize",
1413 str(lutResolutionShaper)]
1415 args += ["--cubesize", str(lutResolution3d)]
1417 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1418 bakedDir, odtName, inputspace)]
1419 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1420 args=(args + margs))
1423 hargs = ["--format", "houdini",
1424 "%s/houdini/%s for %s Houdini.lut" % (
1425 bakedDir, odtName, inputspace)]
1426 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1427 args=(args + hargs))
1431 def createConfigDir(configDir, bakeSecondaryLUTs):
1438 Parameter description.
1443 Return value description.
1446 dirs = [configDir, "%s/luts" % configDir]
1447 if bakeSecondaryLUTs:
1448 dirs.extend(["%s/baked" % configDir,
1449 "%s/baked/flame" % configDir,
1450 "%s/baked/photoshop" % configDir,
1451 "%s/baked/houdini" % configDir,
1452 "%s/baked/lustre" % configDir,
1453 "%s/baked/maya" % configDir])
1456 if not os.path.exists(d):
1460 def getTransformInfo(ctlTransform):
1467 Parameter description.
1472 Return value description.
1475 fp = open(ctlTransform, 'rb')
1478 lines = fp.readlines()
1480 # Grab transform ID and User Name
1481 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1482 # print(transformID)
1483 transformUserName = '-'.join(
1484 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1485 1:]).lstrip().rstrip()
1486 transformUserNamePrefix = \
1487 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1488 0].lstrip().rstrip()
1489 # print(transformUserName)
1492 return transformID, transformUserName, transformUserNamePrefix
1495 def getODTInfo(acesCTLReleaseDir):
1499 For versions after WGR9.
1504 Parameter description.
1509 Return value description.
1512 # Credit to Alex Fry for the original approach here
1513 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1515 for dirName, subdirList, fileList in os.walk(odtDir):
1516 for fname in fileList:
1517 allodt.append((os.path.join(dirName, fname)))
1519 odtCTLs = [x for x in allodt if
1520 ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1526 for odtCTL in odtCTLs:
1527 odtTokens = os.path.split(odtCTL)
1530 # Handle nested directories
1531 odtPathTokens = os.path.split(odtTokens[-2])
1532 odtDir = odtPathTokens[-1]
1533 while odtPathTokens[-2][-3:] != 'odt':
1534 odtPathTokens = os.path.split(odtPathTokens[-2])
1535 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1538 # print("odtDir : %s" % odtDir)
1539 transformCTL = odtTokens[-1]
1540 # print(transformCTL)
1541 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1544 # Find id, user name and user name prefix
1545 (transformID, transformUserName,
1546 transformUserNamePrefix) = getTransformInfo(
1547 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1550 transformCTLInverse = "InvODT.%s.ctl" % odtName
1551 if not os.path.exists(
1552 os.path.join(odtTokens[-2], transformCTLInverse)):
1553 transformCTLInverse = None
1554 # print(transformCTLInverse)
1556 # Add to list of ODTs
1558 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1559 if transformCTLInverse != None:
1560 odts[odtName]['transformCTLInverse'] = os.path.join(
1561 odtDir, transformCTLInverse)
1563 odts[odtName]['transformID'] = transformID
1564 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1565 odts[odtName]['transformUserName'] = transformUserName
1567 print("ODT : %s" % odtName)
1568 print("\tTransform ID : %s" % transformID)
1569 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1570 print("\tTransform User Name : %s" % transformUserName)
1571 print("\tForward ctl : %s" % (
1572 odts[odtName]['transformCTL']))
1573 if 'transformCTLInverse' in odts[odtName]:
1574 print("\tInverse ctl : %s" % (
1575 odts[odtName]['transformCTLInverse']))
1577 print("\tInverse ctl : %s" % "None")
1584 def getLMTInfo(acesCTLReleaseDir):
1588 For versions after WGR9.
1593 Parameter description.
1598 Return value description.
1601 # Credit to Alex Fry for the original approach here
1602 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1604 for dirName, subdirList, fileList in os.walk(lmtDir):
1605 for fname in fileList:
1606 alllmt.append((os.path.join(dirName, fname)))
1608 lmtCTLs = [x for x in alllmt if
1609 ("InvLMT" not in x) and ("README" not in x) and (
1610 os.path.split(x)[-1][0] != '.')]
1616 for lmtCTL in lmtCTLs:
1617 lmtTokens = os.path.split(lmtCTL)
1620 # Handle nested directories
1621 lmtPathTokens = os.path.split(lmtTokens[-2])
1622 lmtDir = lmtPathTokens[-1]
1623 while lmtPathTokens[-2][-3:] != 'ctl':
1624 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1625 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1628 # print("lmtDir : %s" % lmtDir)
1629 transformCTL = lmtTokens[-1]
1630 # print(transformCTL)
1631 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1634 # Find id, user name and user name prefix
1635 (transformID, transformUserName,
1636 transformUserNamePrefix) = getTransformInfo(
1637 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1640 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1641 if not os.path.exists(
1642 os.path.join(lmtTokens[-2], transformCTLInverse)):
1643 transformCTLInverse = None
1644 # print(transformCTLInverse)
1646 # Add to list of LMTs
1648 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1649 if transformCTLInverse != None:
1650 # TODO: Check unresolved *odtName* referemce.
1651 lmts[odtName]['transformCTLInverse'] = os.path.join(
1652 lmtDir, transformCTLInverse)
1654 lmts[lmtName]['transformID'] = transformID
1655 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1656 lmts[lmtName]['transformUserName'] = transformUserName
1658 print("LMT : %s" % lmtName)
1659 print("\tTransform ID : %s" % transformID)
1660 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1661 print("\tTransform User Name : %s" % transformUserName)
1662 print("\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1663 if 'transformCTLInverse' in lmts[lmtName]:
1664 print("\t Inverse ctl : %s" % (
1665 lmts[lmtName]['transformCTLInverse']))
1667 print("\t Inverse ctl : %s" % "None")
1674 def createACESConfig(acesCTLReleaseDir,
1676 lutResolution1d=4096,
1678 bakeSecondaryLUTs=True,
1681 Creates the ACES configuration.
1686 Parameter description.
1691 Return value description.
1694 # Get ODT names and CTL paths
1695 odtInfo = getODTInfo(acesCTLReleaseDir)
1697 # Get ODT names and CTL paths
1698 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1701 createConfigDir(configDir, bakeSecondaryLUTs)
1703 # Generate config data and LUTs for different transforms
1704 lutDir = "%s/luts" % configDir
1705 shaperName = 'Output Shaper'
1706 configData = generateLUTs(odtInfo,
1715 # Create the config using the generated LUTs
1716 print("Creating generic config")
1717 config = createConfig(configData)
1720 # Write the config to disk
1721 writeConfig(config, "%s/config.ocio" % configDir)
1723 # Create a config that will work well with Nuke using the previously
1725 print("Creating Nuke-specific config")
1726 nuke_config = createConfig(configData, nuke=True)
1729 # Write the config to disk
1730 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1732 # Bake secondary LUTs using the config
1733 if bakeSecondaryLUTs:
1734 generateBakedLUTs(odtInfo,
1736 "%s/baked" % configDir,
1737 "%s/config.ocio" % configDir,
1752 Parameter description.
1757 Return value description.
1762 p = optparse.OptionParser(description='An OCIO config generation script',
1763 prog='createACESConfig',
1764 version='createACESConfig 0.1',
1765 usage='%prog [options]')
1766 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1767 'ACES_OCIO_CTL_DIRECTORY', None))
1768 p.add_option('--configDir', '-c', default=os.environ.get(
1769 'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1770 p.add_option('--lutResolution1d', default=4096)
1771 p.add_option('--lutResolution3d', default=64)
1772 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1773 p.add_option('--keepTempImages', action="store_true")
1775 options, arguments = p.parse_args()
1780 acesCTLDir = options.acesCTLDir
1781 configDir = options.configDir
1782 lutResolution1d = int(options.lutResolution1d)
1783 lutResolution3d = int(options.lutResolution3d)
1784 bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1785 cleanupTempImages = not (options.keepTempImages)
1788 argsStart = sys.argv.index('--') + 1
1789 args = sys.argv[argsStart:]
1791 argsStart = len(sys.argv) + 1
1794 print("command line : \n%s\n" % " ".join(sys.argv))
1796 # TODO: Use assertion and mention environment variables.
1798 print("process: No ACES CTL directory specified")
1801 print("process: No configuration directory specified")
1804 # Generate the configuration
1806 return createACESConfig(acesCTLDir,
1814 if __name__ == '__main__':