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':
16 $ 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 __author__ = 'ACES Developers'
65 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
67 __maintainer__ = 'ACES Developers'
68 __email__ = 'aces@oscars.org'
69 __status__ = 'Production'
71 __all__ = ['ACES_OCIO_CTL_DIRECTORY_ENVIRON',
72 'ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON',
73 'setConfigDefaultRoles',
75 'generateOCIOTransform',
86 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
87 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
90 def setConfigDefaultRoles(config,
101 Sets given *OCIO* configuration default roles.
106 *OCIO* configuration.
107 color_picking : str or unicode
108 Color picking role title.
109 color_timing : str or unicode
110 Color timing role title.
111 compositing_log : str or unicode
112 Compositing log role title.
113 data : str or unicode
115 default : str or unicode
117 matte_paint : str or unicode
118 Matte painting role title.
119 reference : str or unicode
120 Reference role title.
121 scene_linear : str or unicode
122 Scene linear role title.
123 texture_paint : str or unicode
124 Texture painting role title.
133 config.setRole(OCIO.Constants.ROLE_COLOR_PICKING, color_picking)
135 config.setRole(OCIO.Constants.ROLE_COLOR_TIMING, color_timing)
137 config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log)
139 config.setRole(OCIO.Constants.ROLE_DATA, data)
141 config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
143 config.setRole(OCIO.Constants.ROLE_MATTE_PAINT, matte_paint)
145 config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
147 config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear)
149 config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint)
154 def writeConfig(config, configPath, sanityCheck=True):
156 Writes the configuration to given path.
161 Parameter description.
166 Return value description.
174 print "Configuration was not written due to a failed Sanity Check"
178 fileHandle = open(configPath, mode='w')
179 fileHandle.write(config.serialize())
183 def generateOCIOTransform(transforms):
190 Parameter description.
195 Return value description.
198 # print("Generating transforms")
200 interpolationOptions = {
201 'linear': OCIO.Constants.INTERP_LINEAR,
202 'nearest': OCIO.Constants.INTERP_NEAREST,
203 'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
206 'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
207 'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
212 for transform in transforms:
213 if transform['type'] == 'lutFile':
214 ocioTransform = OCIO.FileTransform(
215 src=transform['path'],
216 interpolation=interpolationOptions[transform['interpolation']],
217 direction=directionOptions[transform['direction']])
218 ocioTransforms.append(ocioTransform)
219 elif transform['type'] == 'matrix':
220 ocioTransform = OCIO.MatrixTransform()
221 # MatrixTransform member variables can't be initialized directly.
222 # Each must be set individually.
223 ocioTransform.setMatrix(transform['matrix'])
225 if 'offset' in transform:
226 ocioTransform.setOffset(transform['offset'])
228 if 'direction' in transform:
229 ocioTransform.setDirection(
230 directionOptions[transform['direction']])
232 ocioTransforms.append(ocioTransform)
233 elif transform['type'] == 'exponent':
234 ocioTransform = OCIO.ExponentTransform()
235 ocioTransform.setValue(transform['value'])
236 ocioTransforms.append(ocioTransform)
237 elif transform['type'] == 'log':
238 ocioTransform = OCIO.LogTransform(
239 base=transform['base'],
240 direction=directionOptions[transform['direction']])
242 ocioTransforms.append(ocioTransform)
244 print("Ignoring unknown transform type : %s" % transform['type'])
246 # Build a group transform if necessary
247 if len(ocioTransforms) > 1:
248 transformG = OCIO.GroupTransform()
249 for transform in ocioTransforms:
250 transformG.push_back(transform)
251 transform = transformG
253 # Or take the first transform from the list
255 transform = ocioTransforms[0]
260 def createConfig(configData, nuke=False):
267 Parameter description.
272 Return value description.
276 config = OCIO.Config()
279 # Set config wide values
281 config.setDescription("An ACES config generated from python")
282 config.setSearchPath("luts")
285 # Define the reference color space
287 referenceData = configData['referenceColorSpace']
288 print("Adding the reference color space : %s" % referenceData.name)
290 # Create a color space
291 reference = OCIO.ColorSpace(
292 name=referenceData.name,
293 bitDepth=referenceData.bitDepth,
294 description=referenceData.description,
295 equalityGroup=referenceData.equalityGroup,
296 family=referenceData.family,
297 isData=referenceData.isData,
298 allocation=referenceData.allocationType,
299 allocationVars=referenceData.allocationVars)
302 config.addColorSpace(reference)
305 # Create the rest of the color spaces
307 for colorspace in sorted(configData['colorSpaces']):
308 print("Creating new color space : %s" % colorspace.name)
310 ocioColorspace = OCIO.ColorSpace(
311 name=colorspace.name,
312 bitDepth=colorspace.bitDepth,
313 description=colorspace.description,
314 equalityGroup=colorspace.equalityGroup,
315 family=colorspace.family,
316 isData=colorspace.isData,
317 allocation=colorspace.allocationType,
318 allocationVars=colorspace.allocationVars)
320 if colorspace.toReferenceTransforms != []:
321 print("Generating To-Reference transforms")
322 ocioTransform = generateOCIOTransform(
323 colorspace.toReferenceTransforms)
324 ocioColorspace.setTransform(
326 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
328 if colorspace.fromReferenceTransforms != []:
329 print("Generating From-Reference transforms")
330 ocioTransform = generateOCIOTransform(
331 colorspace.fromReferenceTransforms)
332 ocioColorspace.setTransform(
334 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
336 config.addColorSpace(ocioColorspace)
341 # Define the views and displays
346 # Generic display and view setup
348 for display, viewList in configData['displays'].iteritems():
349 for viewName, colorspace in viewList.iteritems():
350 config.addDisplay(display, viewName, colorspace.name)
351 if not (viewName in views):
352 views.append(viewName)
353 displays.append(display)
354 # A Nuke specific set of views and displays
357 # A few names: Output Transform, ACES, ACEScc, are hard-coded here.
358 # Would be better to automate.
361 for display, viewList in configData['displays'].iteritems():
362 for viewName, colorspace in viewList.iteritems():
363 if (viewName == 'Output Transform'):
365 config.addDisplay(display, viewName, colorspace.name)
366 if not (viewName in views):
367 views.append(viewName)
368 displays.append(display)
370 config.addDisplay('linear', 'View', 'ACES2065-1')
371 displays.append('linear')
372 config.addDisplay('log', 'View', 'ACEScc')
373 displays.append('log')
375 # Set active displays and views
376 config.setActiveDisplays(','.join(sorted(displays)))
377 config.setActiveViews(','.join(views))
380 # Need to generalize this at some point
384 setConfigDefaultRoles(config,
385 color_picking=reference.getName(),
386 color_timing=reference.getName(),
387 compositing_log=reference.getName(),
388 data=reference.getName(),
389 default=reference.getName(),
390 matte_paint=reference.getName(),
391 reference=reference.getName(),
392 scene_linear=reference.getName(),
393 texture_paint=reference.getName())
395 # Check to make sure we didn't screw something up
401 def generateLUTs(odtInfo,
406 lutResolution1d=4096,
415 Parameter description.
420 Colorspaces and transforms converting between those colorspaces and
421 the reference colorspace, *ACES*.
424 print("generateLUTs - begin")
428 # Define the reference color space
430 ACES = ColorSpace('ACES2065-1')
432 'The Academy Color Encoding System reference color space')
433 ACES.equalityGroup = ''
436 ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
437 ACES.allocationVars = [-15, 6]
439 configData['referenceColorSpace'] = ACES
442 # Define the displays
444 configData['displays'] = {}
447 # Define the other color spaces
449 configData['colorSpaces'] = []
451 # Matrix converting ACES AP1 primaries to AP0
452 acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
453 0.0447945634, 0.8596711185, 0.0955343182,
454 -0.0055258826, 0.0040252103, 1.0015006723]
456 # Matrix converting ACES AP0 primaries to XYZ
457 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
458 0.3439664498, 0.7281660966, -0.0721325464,
459 0.0000000000, 0.0000000000, 1.0088251844]
464 def createACEScc(name='ACEScc',
468 cs = ColorSpace(name)
469 cs.description = "The %s color space" % name
470 cs.equalityGroup = ''
475 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
476 # This transform gets back to the AP1 primaries
477 # Useful as the 1d LUT is only covering the transfer function
478 # The primaries switch is covered by the matrix below
479 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
481 lut = "%s_to_ACES.spi1d" % name
483 # Remove spaces and parentheses
484 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
486 genlut.generate1dLUTFromCTL(
499 cs.toReferenceTransforms = []
500 cs.toReferenceTransforms.append({
503 'interpolation': 'linear',
504 'direction': 'forward'
507 # AP1 primaries to AP0 primaries
508 cs.toReferenceTransforms.append({
510 'matrix': mat44FromMat33(acesAP1toAP0),
511 'direction': 'forward'
514 cs.fromReferenceTransforms = []
517 ACEScc = createACEScc()
518 configData['colorSpaces'].append(ACEScc)
523 def createACESProxy(name='ACESproxy'):
524 cs = ColorSpace(name)
525 cs.description = "The %s color space" % name
526 cs.equalityGroup = ''
531 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % (
533 # This transform gets back to the AP1 primaries
534 # Useful as the 1d LUT is only covering the transfer function
535 # The primaries switch is covered by the matrix below
536 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
538 lut = "%s_to_aces.spi1d" % name
540 # Remove spaces and parentheses
541 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
543 genlut.generate1dLUTFromCTL(
554 cs.toReferenceTransforms = []
555 cs.toReferenceTransforms.append({
558 'interpolation': 'linear',
559 'direction': 'forward'
562 # AP1 primaries to AP0 primaries
563 cs.toReferenceTransforms.append({
565 'matrix': mat44FromMat33(acesAP1toAP0),
566 'direction': 'forward'
569 cs.fromReferenceTransforms = []
572 ACESproxy = createACESProxy()
573 configData['colorSpaces'].append(ACESproxy)
578 def createACEScg(name='ACEScg'):
579 cs = ColorSpace(name)
580 cs.description = "The %s color space" % name
581 cs.equalityGroup = ''
585 cs.toReferenceTransforms = []
587 # AP1 primaries to AP0 primaries
588 cs.toReferenceTransforms.append({
590 'matrix': mat44FromMat33(acesAP1toAP0),
591 'direction': 'forward'
594 cs.fromReferenceTransforms = []
597 ACEScg = createACEScg()
598 configData['colorSpaces'].append(ACEScg)
603 def createADX(bitdepth=10, name='ADX'):
604 name = "%s%s" % (name, bitdepth)
605 cs = ColorSpace(name)
606 cs.description = "%s color space - used for film scans" % name
607 cs.equalityGroup = ''
612 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
613 adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
614 0.0, 1023.0 / 500.0, 0.0, 0.0,
615 0.0, 0.0, 1023.0 / 500.0, 0.0,
617 offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
619 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
620 adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
621 0.0, 65535.0 / 8000.0, 0.0, 0.0,
622 0.0, 0.0, 65535.0 / 8000.0, 0.0,
624 offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
627 cs.toReferenceTransforms = []
629 # Convert from ADX to Channel-Dependent Density
630 cs.toReferenceTransforms.append({
632 'matrix': adx_to_cdd,
634 'direction': 'forward'
637 # Convert from Channel-Dependent Density to Channel-Independent Density
638 cs.toReferenceTransforms.append({
640 'matrix': [0.75573, 0.22197, 0.02230, 0,
641 0.05901, 0.96928, -0.02829, 0,
642 0.16134, 0.07406, 0.76460, 0,
644 'direction': 'forward'
647 # Copied from Alex Fry's adx_cid_to_rle.py
648 def createCIDtoRLELUT():
649 def interpolate1D(x, xp, fp):
650 return numpy.interp(x, xp, fp)
652 LUT_1D_xp = [-0.190000000000000,
664 LUT_1D_fp = [-6.000000000000000,
676 REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
677 math.log(0.18, 10.0))
681 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
682 return (100.0 / 55.0) * x - REF_PT
684 def Fit(value, fromMin, fromMax, toMin, toMax):
685 if fromMin == fromMax:
686 raise ValueError("fromMin == fromMax")
687 return (value - fromMin) / (fromMax - fromMin) * (
688 toMax - toMin) + toMin
690 NUM_SAMPLES = 2 ** 12
693 for i in xrange(NUM_SAMPLES):
694 x = i / (NUM_SAMPLES - 1.0)
695 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
696 data.append(cid_to_rle(x))
698 lut = 'ADX_CID_to_RLE.spi1d'
699 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
704 # Convert Channel Independent Density values to Relative Log Exposure
706 lut = createCIDtoRLELUT()
707 cs.toReferenceTransforms.append({
710 'interpolation': 'linear',
711 'direction': 'forward'
714 # Convert Relative Log Exposure values to Relative Exposure values
715 cs.toReferenceTransforms.append({
718 'direction': 'inverse'
721 # Convert Relative Exposure values to ACES values
722 cs.toReferenceTransforms.append({
724 'matrix': [0.72286, 0.12630, 0.15084, 0,
725 0.11923, 0.76418, 0.11659, 0,
726 0.01427, 0.08213, 0.90359, 0,
728 'direction': 'forward'
731 cs.fromReferenceTransforms = []
734 ADX10 = createADX(bitdepth=10)
735 configData['colorSpaces'].append(ADX10)
737 ADX16 = createADX(bitdepth=16)
738 configData['colorSpaces'].append(ADX16)
741 # Camera Input Transforms
744 # RED color spaces to ACES
745 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
746 for cs in redColorSpaces:
747 configData['colorSpaces'].append(cs)
750 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
751 for cs in canonColorSpaces:
752 configData['colorSpaces'].append(cs)
755 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
756 for cs in sonyColorSpaces:
757 configData['colorSpaces'].append(cs)
760 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
761 for cs in arriColorSpaces:
762 configData['colorSpaces'].append(cs)
765 # Generic log transform
767 def createGenericLog(name='log',
774 lutResolution1d=lutResolution1d):
775 cs = ColorSpace(name)
776 cs.description = "The %s color space" % name
777 cs.equalityGroup = name
778 cs.family = 'Utility'
782 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % (
785 lut = "%s_to_aces.spi1d" % name
787 # Remove spaces and parentheses
788 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
790 genlut.generate1dLUTFromCTL(
798 'middleGrey': middleGrey,
799 'minExposure': minExposure,
800 'maxExposure': maxExposure
807 cs.toReferenceTransforms = []
808 cs.toReferenceTransforms.append({
811 'interpolation': 'linear',
812 'direction': 'forward'
815 cs.fromReferenceTransforms = []
821 def createACESLMT(lmtName,
824 lutResolution1d=1024,
827 cs = ColorSpace("%s" % lmtName)
828 cs.description = "The ACES Look Transform: %s" % lmtName
829 cs.equalityGroup = ''
835 pprint.pprint(lmtValues)
838 # Generate the shaper transform
844 shaperParams) = shaperInfo
846 shaperLut = "%s_to_aces.spi1d" % shaperName
847 if (not os.path.exists(lutDir + "/" + shaperLut)):
849 shaperToACESCTL % acesCTLReleaseDir
852 # Remove spaces and parentheses
853 shaperLut = shaperLut.replace(
854 ' ', '_').replace(')', '_').replace('(', '_')
856 genlut.generate1dLUTFromCTL(
857 lutDir + "/" + shaperLut,
861 1.0 / shaperInputScale,
867 shaperOCIOTransform = {
870 'interpolation': 'linear',
871 'direction': 'inverse'
875 # Generate the forward transform
877 cs.fromReferenceTransforms = []
879 if 'transformCTL' in lmtValues:
881 shaperToACESCTL % acesCTLReleaseDir,
882 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
884 lut = "%s.%s.spi3d" % (shaperName, lmtName)
886 # Remove spaces and parentheses
887 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
889 genlut.generate3dLUTFromCTL(
894 1.0 / shaperInputScale,
900 cs.fromReferenceTransforms.append(shaperOCIOTransform)
901 cs.fromReferenceTransforms.append({
904 'interpolation': 'tetrahedral',
905 'direction': 'forward'
909 # Generate the inverse transform
911 cs.toReferenceTransforms = []
913 if 'transformCTLInverse' in lmtValues:
916 acesCTLReleaseDir, odtValues['transformCTLInverse']),
917 shaperFromACESCTL % acesCTLReleaseDir
919 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
921 # Remove spaces and parentheses
922 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
924 genlut.generate3dLUTFromCTL(
935 cs.toReferenceTransforms.append({
938 'interpolation': 'tetrahedral',
939 'direction': 'forward'
942 shaperInverse = shaperOCIOTransform.copy()
943 shaperInverse['direction'] = 'forward'
944 cs.toReferenceTransforms.append(shaperInverse)
952 lmtLutResolution1d = max(4096, lutResolution1d)
953 lmtLutResolution3d = max(65, lutResolution3d)
956 lmtShaperName = 'LMT Shaper'
959 'minExposure': -10.0,
962 lmtShaper = createGenericLog(name=lmtShaperName,
963 middleGrey=lmtParams['middleGrey'],
964 minExposure=lmtParams['minExposure'],
965 maxExposure=lmtParams['maxExposure'],
966 lutResolution1d=lmtLutResolution1d)
967 configData['colorSpaces'].append(lmtShaper)
969 shaperInputScale_genericLog2 = 1.0
971 # Log 2 shaper name and CTL transforms bundled up
974 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
975 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
976 shaperInputScale_genericLog2,
980 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
982 for lmt in sortedLMTs:
983 (lmtName, lmtValues) = lmt
985 lmtValues['transformUserName'],
991 configData['colorSpaces'].append(cs)
994 # ACES RRT with the supplied ODT
996 def createACESRRTplusODT(odtName,
999 lutResolution1d=1024,
1002 cs = ColorSpace("%s" % odtName)
1003 cs.description = "%s - %s Output Transform" % (
1004 odtValues['transformUserNamePrefix'], odtName)
1005 cs.equalityGroup = ''
1006 cs.family = 'Output'
1011 pprint.pprint(odtValues)
1014 # Generate the shaper transform
1016 # if 'shaperCTL' in odtValues:
1021 shaperParams) = shaperInfo
1023 if 'legalRange' in odtValues:
1024 shaperParams['legalRange'] = odtValues['legalRange']
1026 shaperParams['legalRange'] = 0
1028 shaperLut = "%s_to_aces.spi1d" % shaperName
1029 if (not os.path.exists(lutDir + "/" + shaperLut)):
1031 shaperToACESCTL % acesCTLReleaseDir
1034 # Remove spaces and parentheses
1035 shaperLut = shaperLut.replace(
1036 ' ', '_').replace(')', '_').replace('(', '_')
1038 genlut.generate1dLUTFromCTL(
1039 lutDir + "/" + shaperLut,
1043 1.0 / shaperInputScale,
1049 shaperOCIOTransform = {
1052 'interpolation': 'linear',
1053 'direction': 'inverse'
1057 # Generate the forward transform
1059 cs.fromReferenceTransforms = []
1061 if 'transformLUT' in odtValues:
1062 # Copy into the lut dir
1063 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1064 lut = lutDir + "/" + transformLUTFileName
1065 shutil.copy(odtValues['transformLUT'], lut)
1067 cs.fromReferenceTransforms.append(shaperOCIOTransform)
1068 cs.fromReferenceTransforms.append({
1070 'path': transformLUTFileName,
1071 'interpolation': 'tetrahedral',
1072 'direction': 'forward'
1074 elif 'transformCTL' in odtValues:
1078 shaperToACESCTL % acesCTLReleaseDir,
1079 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1080 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1082 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1084 # Remove spaces and parentheses
1085 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1087 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1092 1.0 / shaperInputScale,
1098 cs.fromReferenceTransforms.append(shaperOCIOTransform)
1099 cs.fromReferenceTransforms.append({
1102 'interpolation': 'tetrahedral',
1103 'direction': 'forward'
1107 # Generate the inverse transform
1109 cs.toReferenceTransforms = []
1111 if 'transformLUTInverse' in odtValues:
1112 # Copy into the lut dir
1113 transformLUTInverseFileName = os.path.basename(
1114 odtValues['transformLUTInverse'])
1115 lut = lutDir + "/" + transformLUTInverseFileName
1116 shutil.copy(odtValues['transformLUTInverse'], lut)
1118 cs.toReferenceTransforms.append({
1120 'path': transformLUTInverseFileName,
1121 'interpolation': 'tetrahedral',
1122 'direction': 'forward'
1125 shaperInverse = shaperOCIOTransform.copy()
1126 shaperInverse['direction'] = 'forward'
1127 cs.toReferenceTransforms.append(shaperInverse)
1128 elif 'transformCTLInverse' in odtValues:
1131 acesCTLReleaseDir, odtValues['transformCTLInverse']),
1132 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1133 shaperFromACESCTL % acesCTLReleaseDir
1135 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1137 # Remove spaces and parentheses
1138 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1140 genlut.generate3dLUTFromCTL(
1152 cs.toReferenceTransforms.append({
1155 'interpolation': 'tetrahedral',
1156 'direction': 'forward'
1159 shaperInverse = shaperOCIOTransform.copy()
1160 shaperInverse['direction'] = 'forward'
1161 cs.toReferenceTransforms.append(shaperInverse)
1166 # RRT/ODT shaper options
1171 log2ShaperName = shaperName
1174 'minExposure': -6.0,
1177 log2Shaper = createGenericLog(name=log2ShaperName,
1178 middleGrey=log2Params['middleGrey'],
1179 minExposure=log2Params['minExposure'],
1180 maxExposure=log2Params['maxExposure'])
1181 configData['colorSpaces'].append(log2Shaper)
1183 shaperInputScale_genericLog2 = 1.0
1185 # Log 2 shaper name and CTL transforms bundled up
1188 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1189 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1190 shaperInputScale_genericLog2,
1194 shaperData[log2ShaperName] = log2ShaperData
1197 # Shaper that also includes the AP1 primaries
1198 # - Needed for some LUT baking steps
1200 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1201 middleGrey=log2Params['middleGrey'],
1202 minExposure=log2Params['minExposure'],
1203 maxExposure=log2Params['maxExposure'])
1204 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1205 # AP1 primaries to AP0 primaries
1206 log2ShaperAP1.toReferenceTransforms.append({
1208 'matrix': mat44FromMat33(acesAP1toAP0),
1209 'direction': 'forward'
1211 configData['colorSpaces'].append(log2ShaperAP1)
1214 # Choose your shaper
1216 rrtShaperName = log2ShaperName
1217 rrtShaper = log2ShaperData
1220 # RRT + ODT Combinations
1222 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1224 for odt in sortedOdts:
1225 (odtName, odtValues) = odt
1227 # Have to handle ODTs that can generate either legal or full output
1228 if odtName 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 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1233 odtNameLegal = odtValues['transformUserName']
1235 odtLegal = odtValues.copy()
1236 odtLegal['legalRange'] = 1
1238 cs = createACESRRTplusODT(
1245 configData['colorSpaces'].append(cs)
1247 # Create a display entry using this color space
1248 configData['displays'][odtNameLegal] = {
1251 'Output Transform': cs}
1253 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1254 'Academy.Rec709_100nits_dim.a1.0.0',
1255 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1256 print("Generating full range ODT for %s" % odtName)
1258 odtNameFull = "%s - Full" % odtValues['transformUserName']
1259 odtFull = odtValues.copy()
1260 odtFull['legalRange'] = 0
1262 csFull = createACESRRTplusODT(
1269 configData['colorSpaces'].append(csFull)
1271 # Create a display entry using this color space
1272 configData['displays'][odtNameFull] = {
1275 'Output Transform': csFull}
1278 # Generic Matrix transform
1280 def createGenericMatrix(name='matrix',
1281 fromReferenceValues=[],
1282 toReferenceValues=[]):
1283 cs = ColorSpace(name)
1284 cs.description = "The %s color space" % name
1285 cs.equalityGroup = name
1286 cs.family = 'Utility'
1289 cs.toReferenceTransforms = []
1290 if toReferenceValues != []:
1291 for matrix in toReferenceValues:
1292 cs.toReferenceTransforms.append({
1294 'matrix': mat44FromMat33(matrix),
1295 'direction': 'forward'
1298 cs.fromReferenceTransforms = []
1299 if fromReferenceValues != []:
1300 for matrix in fromReferenceValues:
1301 cs.fromReferenceTransforms.append({
1303 'matrix': mat44FromMat33(matrix),
1304 'direction': 'forward'
1309 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1310 configData['colorSpaces'].append(cs)
1312 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1313 configData['colorSpaces'].append(cs)
1315 # ACES to Linear, P3D60 primaries
1316 xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1317 -0.8325796487, 1.7692317536, 0.0237127115,
1318 0.0388233815, -0.0824996856, 1.0363685997]
1320 cs = createGenericMatrix('Linear - P3-D60',
1321 fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1322 configData['colorSpaces'].append(cs)
1324 # ACES to Linear, P3D60 primaries
1325 xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1326 -0.7951680258, 1.6897320548, 0.0226471906,
1327 0.0412418914, -0.0876390192, 1.1009293786]
1329 cs = createGenericMatrix('Linear - P3-DCI',
1330 fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1331 configData['colorSpaces'].append(cs)
1333 # ACES to Linear, Rec 709 primaries
1334 xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1335 -0.9692436363, 1.8759675015, 0.0415550574,
1336 0.0556300797, -0.2039769589, 1.0569715142]
1338 cs = createGenericMatrix('Linear - Rec.709',
1339 fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1340 configData['colorSpaces'].append(cs)
1342 # ACES to Linear, Rec 2020 primaries
1343 xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1344 -0.6666843518, 1.6164812366, 0.0157685458,
1345 0.0176398574, -0.0427706133, 0.9421031212]
1347 cs = createGenericMatrix('Linear - Rec.2020',
1348 fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1349 configData['colorSpaces'].append(cs)
1351 print("generateLUTs - end")
1355 def generateBakedLUTs(odtInfo,
1361 lutResolutionShaper=1024):
1368 Parameter description.
1373 Return value description.
1376 # Add the legal and full variations into this list
1377 odtInfoC = dict(odtInfo)
1378 for odtCTLName, odtValues in odtInfo.iteritems():
1379 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1380 'Academy.Rec709_100nits_dim.a1.0.0',
1381 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1382 odtName = odtValues["transformUserName"]
1384 odtValuesLegal = dict(odtValues)
1385 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1386 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1388 odtValuesFull = dict(odtValues)
1389 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1390 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1392 del (odtInfoC[odtCTLName])
1394 for odtCTLName, odtValues in odtInfoC.iteritems():
1395 odtPrefix = odtValues["transformUserNamePrefix"]
1396 odtName = odtValues["transformUserName"]
1399 for inputspace in ["ACEScc", "ACESproxy"]:
1400 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1401 args += ["--outputspace", "%s" % odtName]
1402 args += ["--description",
1403 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1404 args += ["--shaperspace", shaperName, "--shapersize",
1405 str(lutResolutionShaper)]
1406 args += ["--cubesize", str(lutResolution3d)]
1407 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1408 bakedDir, odtName, inputspace)]
1410 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1415 for inputspace in ["ACEScc", "ACESproxy"]:
1416 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1417 args += ["--outputspace", "%s" % odtName]
1418 args += ["--description",
1419 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1420 args += ["--shaperspace", shaperName, "--shapersize",
1421 str(lutResolutionShaper)]
1422 args += ["--cubesize", str(lutResolution3d)]
1424 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1425 bakedDir, odtName, inputspace)]
1426 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1427 args=(args + fargs))
1430 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1431 bakedDir, odtName, inputspace)]
1432 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1433 args=(args + largs))
1437 for inputspace in ["ACEScg", "ACES2065-1"]:
1438 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1439 args += ["--outputspace", "%s" % odtName]
1440 args += ["--description",
1441 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1442 if inputspace == 'ACEScg':
1443 linShaperName = "%s - AP1" % shaperName
1445 linShaperName = shaperName
1446 args += ["--shaperspace", linShaperName, "--shapersize",
1447 str(lutResolutionShaper)]
1449 args += ["--cubesize", str(lutResolution3d)]
1451 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1452 bakedDir, odtName, inputspace)]
1453 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1454 args=(args + margs))
1457 hargs = ["--format", "houdini",
1458 "%s/houdini/%s for %s Houdini.lut" % (
1459 bakedDir, odtName, inputspace)]
1460 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1461 args=(args + hargs))
1465 def createConfigDir(configDir, bakeSecondaryLUTs):
1472 Parameter description.
1477 Return value description.
1480 dirs = [configDir, "%s/luts" % configDir]
1481 if bakeSecondaryLUTs:
1482 dirs.extend(["%s/baked" % configDir,
1483 "%s/baked/flame" % configDir,
1484 "%s/baked/photoshop" % configDir,
1485 "%s/baked/houdini" % configDir,
1486 "%s/baked/lustre" % configDir,
1487 "%s/baked/maya" % configDir])
1490 if not os.path.exists(d):
1494 def getTransformInfo(ctlTransform):
1501 Parameter description.
1506 Return value description.
1509 fp = open(ctlTransform, 'rb')
1512 lines = fp.readlines()
1514 # Grab transform ID and User Name
1515 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1516 # print(transformID)
1517 transformUserName = '-'.join(
1518 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1519 1:]).lstrip().rstrip()
1520 transformUserNamePrefix = \
1521 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1522 0].lstrip().rstrip()
1523 # print(transformUserName)
1526 return transformID, transformUserName, transformUserNamePrefix
1529 def getODTInfo(acesCTLReleaseDir):
1533 For versions after WGR9.
1538 Parameter description.
1543 Return value description.
1546 # Credit to Alex Fry for the original approach here
1547 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1549 for dirName, subdirList, fileList in os.walk(odtDir):
1550 for fname in fileList:
1551 allodt.append((os.path.join(dirName, fname)))
1553 odtCTLs = [x for x in allodt if
1554 ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1560 for odtCTL in odtCTLs:
1561 odtTokens = os.path.split(odtCTL)
1564 # Handle nested directories
1565 odtPathTokens = os.path.split(odtTokens[-2])
1566 odtDir = odtPathTokens[-1]
1567 while odtPathTokens[-2][-3:] != 'odt':
1568 odtPathTokens = os.path.split(odtPathTokens[-2])
1569 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1572 # print("odtDir : %s" % odtDir)
1573 transformCTL = odtTokens[-1]
1574 # print(transformCTL)
1575 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1578 # Find id, user name and user name prefix
1579 (transformID, transformUserName,
1580 transformUserNamePrefix) = getTransformInfo(
1581 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1584 transformCTLInverse = "InvODT.%s.ctl" % odtName
1585 if not os.path.exists(
1586 os.path.join(odtTokens[-2], transformCTLInverse)):
1587 transformCTLInverse = None
1588 # print(transformCTLInverse)
1590 # Add to list of ODTs
1592 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1593 if transformCTLInverse != None:
1594 odts[odtName]['transformCTLInverse'] = os.path.join(
1595 odtDir, transformCTLInverse)
1597 odts[odtName]['transformID'] = transformID
1598 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1599 odts[odtName]['transformUserName'] = transformUserName
1601 print("ODT : %s" % odtName)
1602 print("\tTransform ID : %s" % transformID)
1603 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1604 print("\tTransform User Name : %s" % transformUserName)
1605 print("\tForward ctl : %s" % (
1606 odts[odtName]['transformCTL']))
1607 if 'transformCTLInverse' in odts[odtName]:
1608 print("\tInverse ctl : %s" % (
1609 odts[odtName]['transformCTLInverse']))
1611 print("\tInverse ctl : %s" % "None")
1618 def getLMTInfo(acesCTLReleaseDir):
1622 For versions after WGR9.
1627 Parameter description.
1632 Return value description.
1635 # Credit to Alex Fry for the original approach here
1636 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1638 for dirName, subdirList, fileList in os.walk(lmtDir):
1639 for fname in fileList:
1640 alllmt.append((os.path.join(dirName, fname)))
1642 lmtCTLs = [x for x in alllmt if
1643 ("InvLMT" not in x) and ("README" not in x) and (
1644 os.path.split(x)[-1][0] != '.')]
1650 for lmtCTL in lmtCTLs:
1651 lmtTokens = os.path.split(lmtCTL)
1654 # Handle nested directories
1655 lmtPathTokens = os.path.split(lmtTokens[-2])
1656 lmtDir = lmtPathTokens[-1]
1657 while lmtPathTokens[-2][-3:] != 'ctl':
1658 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1659 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1662 # print("lmtDir : %s" % lmtDir)
1663 transformCTL = lmtTokens[-1]
1664 # print(transformCTL)
1665 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1668 # Find id, user name and user name prefix
1669 (transformID, transformUserName,
1670 transformUserNamePrefix) = getTransformInfo(
1671 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1674 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1675 if not os.path.exists(
1676 os.path.join(lmtTokens[-2], transformCTLInverse)):
1677 transformCTLInverse = None
1678 # print(transformCTLInverse)
1680 # Add to list of LMTs
1682 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1683 if transformCTLInverse != None:
1684 # TODO: Check unresolved *odtName* referemce.
1685 lmts[odtName]['transformCTLInverse'] = os.path.join(
1686 lmtDir, transformCTLInverse)
1688 lmts[lmtName]['transformID'] = transformID
1689 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1690 lmts[lmtName]['transformUserName'] = transformUserName
1692 print("LMT : %s" % lmtName)
1693 print("\tTransform ID : %s" % transformID)
1694 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1695 print("\tTransform User Name : %s" % transformUserName)
1696 print("\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1697 if 'transformCTLInverse' in lmts[lmtName]:
1698 print("\t Inverse ctl : %s" % (
1699 lmts[lmtName]['transformCTLInverse']))
1701 print("\t Inverse ctl : %s" % "None")
1708 def createACESConfig(acesCTLReleaseDir,
1710 lutResolution1d=4096,
1712 bakeSecondaryLUTs=True,
1715 Creates the ACES configuration.
1720 Parameter description.
1725 Return value description.
1728 # Get ODT names and CTL paths
1729 odtInfo = getODTInfo(acesCTLReleaseDir)
1731 # Get ODT names and CTL paths
1732 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1735 createConfigDir(configDir, bakeSecondaryLUTs)
1737 # Generate config data and LUTs for different transforms
1738 lutDir = "%s/luts" % configDir
1739 shaperName = 'Output Shaper'
1740 configData = generateLUTs(odtInfo,
1749 # Create the config using the generated LUTs
1750 print("Creating generic config")
1751 config = createConfig(configData)
1754 # Write the config to disk
1755 writeConfig(config, "%s/config.ocio" % configDir)
1757 # Create a config that will work well with Nuke using the previously
1759 print("Creating Nuke-specific config")
1760 nuke_config = createConfig(configData, nuke=True)
1763 # Write the config to disk
1764 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1766 # Bake secondary LUTs using the config
1767 if bakeSecondaryLUTs:
1768 generateBakedLUTs(odtInfo,
1770 "%s/baked" % configDir,
1771 "%s/config.ocio" % configDir,
1786 Parameter description.
1791 Return value description.
1796 p = optparse.OptionParser(description='An OCIO config generation script',
1797 prog='createACESConfig',
1798 version='createACESConfig 0.1',
1799 usage='%prog [options]')
1800 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1801 'ACES_OCIO_CTL_DIRECTORY', None))
1802 p.add_option('--configDir', '-c', default=os.environ.get(
1803 'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1804 p.add_option('--lutResolution1d', default=4096)
1805 p.add_option('--lutResolution3d', default=64)
1806 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1807 p.add_option('--keepTempImages', action="store_true")
1809 options, arguments = p.parse_args()
1814 acesCTLDir = options.acesCTLDir
1815 configDir = options.configDir
1816 lutResolution1d = int(options.lutResolution1d)
1817 lutResolution3d = int(options.lutResolution3d)
1818 bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1819 cleanupTempImages = not (options.keepTempImages)
1822 argsStart = sys.argv.index('--') + 1
1823 args = sys.argv[argsStart:]
1825 argsStart = len(sys.argv) + 1
1828 print("command line : \n%s\n" % " ".join(sys.argv))
1830 # TODO: Use assertion and mention environment variables.
1832 print("process: No ACES CTL directory specified")
1835 print("process: No configuration directory specified")
1838 # Generate the configuration
1840 return createACESConfig(acesCTLDir,
1848 if __name__ == '__main__':