2 # -*- coding: utf-8 -*-
8 sys.path.append("/path/to/script")
9 import create_aces_config as cac
10 acesReleaseCTLDir = "/path/to/github/checkout/releases/v0.7.1/transforms/ctl"
11 configDir = "/path/to/config/dir"
12 cac.createACESConfig(acesReleaseCTLDir, configDir, 1024, 33, True)
14 usage from command line, from the directory with 'create_aces_config.py'
15 python create_aces_config.py -a "/path/to/github/checkout/releases/v0.7.1/transforms/ctl" -c "/path/to/config/dir" --lutResolution1d 1024 --lutResolution3d 33 --keepTempImages
19 build instructions for osx for needed packages.
22 brew install -vd opencolorio --with-python
25 brew tap homebrew/science
28 brew install -vd libRaw
29 brew install -vd OpenCV
31 brew install -vd openimageio --with-python
37 # this time, 'ociolutimage' will build because openimageio is installed
38 brew uninstall -vd opencolorio
39 brew install -vd opencolorio --with-python
49 # TODO: This restores the capability of running the script without having
50 # added the package to PYTHONPATH, this is ugly and should ideally replaced by
51 # dedicated executable in a /bin directory.
52 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
54 import PyOpenColorIO as OCIO
56 import aces_ocio.createARRIColorSpaces as arri
57 import aces_ocio.createCanonColorSpaces as canon
58 import aces_ocio.createREDColorSpaces as red
59 import aces_ocio.createSonyColorSpaces as sony
60 import aces_ocio.generateLUT as genlut
61 from aces_ocio.process import Process
62 from aces_ocio.util import ColorSpace, mat44FromMat33
64 __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'
92 def setConfigDefaultRoles(config,
104 config.setRole(OCIO.Constants.ROLE_COLOR_PICKING, color_picking)
106 config.setRole(OCIO.Constants.ROLE_COLOR_TIMING, color_timing)
108 config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log)
110 config.setRole(OCIO.Constants.ROLE_DATA, data)
112 config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
114 config.setRole(OCIO.Constants.ROLE_MATTE_PAINT, matte_paint)
116 config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
118 config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear)
120 config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint)
123 # Write config to disk
124 def writeConfig(config, configPath, sanityCheck=True):
130 print "Configuration was not written due to a failed Sanity Check"
134 fileHandle = open(configPath, mode='w')
135 fileHandle.write(config.serialize())
139 def generateOCIOTransform(transforms):
140 # print("Generating transforms")
142 interpolationOptions = {
143 'linear': OCIO.Constants.INTERP_LINEAR,
144 'nearest': OCIO.Constants.INTERP_NEAREST,
145 'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
148 'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
149 'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
154 for transform in transforms:
155 if transform['type'] == 'lutFile':
156 ocioTransform = OCIO.FileTransform(
157 src=transform['path'],
158 interpolation=interpolationOptions[transform['interpolation']],
159 direction=directionOptions[transform['direction']])
160 ocioTransforms.append(ocioTransform)
161 elif transform['type'] == 'matrix':
162 ocioTransform = OCIO.MatrixTransform()
163 # MatrixTransform member variables can't be initialized directly.
164 # Each must be set individually.
165 ocioTransform.setMatrix(transform['matrix'])
167 if 'offset' in transform:
168 ocioTransform.setOffset(transform['offset'])
170 if 'direction' in transform:
171 ocioTransform.setDirection(
172 directionOptions[transform['direction']])
174 ocioTransforms.append(ocioTransform)
175 elif transform['type'] == 'exponent':
176 ocioTransform = OCIO.ExponentTransform()
177 ocioTransform.setValue(transform['value'])
178 ocioTransforms.append(ocioTransform)
179 elif transform['type'] == 'log':
180 ocioTransform = OCIO.LogTransform(
181 base=transform['base'],
182 direction=directionOptions[transform['direction']])
184 ocioTransforms.append(ocioTransform)
186 print("Ignoring unknown transform type : %s" % transform['type'])
188 # Build a group transform if necessary
189 if len(ocioTransforms) > 1:
190 transformG = OCIO.GroupTransform()
191 for transform in ocioTransforms:
192 transformG.push_back(transform)
193 transform = transformG
195 # Or take the first transform from the list
197 transform = ocioTransforms[0]
202 def createConfig(configData, nuke=False):
204 config = OCIO.Config()
207 # Set config wide values
209 config.setDescription("An ACES config generated from python")
210 config.setSearchPath("luts")
213 # Define the reference color space
215 referenceData = configData['referenceColorSpace']
216 print("Adding the reference color space : %s" % referenceData.name)
218 # Create a color space
219 reference = OCIO.ColorSpace(
220 name=referenceData.name,
221 bitDepth=referenceData.bitDepth,
222 description=referenceData.description,
223 equalityGroup=referenceData.equalityGroup,
224 family=referenceData.family,
225 isData=referenceData.isData,
226 allocation=referenceData.allocationType,
227 allocationVars=referenceData.allocationVars)
230 config.addColorSpace(reference)
233 # Create the rest of the color spaces
235 for colorspace in sorted(configData['colorSpaces']):
236 print("Creating new color space : %s" % colorspace.name)
238 ocioColorspace = OCIO.ColorSpace(
239 name=colorspace.name,
240 bitDepth=colorspace.bitDepth,
241 description=colorspace.description,
242 equalityGroup=colorspace.equalityGroup,
243 family=colorspace.family,
244 isData=colorspace.isData,
245 allocation=colorspace.allocationType,
246 allocationVars=colorspace.allocationVars)
248 if colorspace.toReferenceTransforms != []:
249 print("Generating To-Reference transforms")
250 ocioTransform = generateOCIOTransform(
251 colorspace.toReferenceTransforms)
252 ocioColorspace.setTransform(
254 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
256 if colorspace.fromReferenceTransforms != []:
257 print("Generating From-Reference transforms")
258 ocioTransform = generateOCIOTransform(
259 colorspace.fromReferenceTransforms)
260 ocioColorspace.setTransform(
262 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
264 config.addColorSpace(ocioColorspace)
269 # Define the views and displays
274 # Generic display and view setup
276 for display, viewList in configData['displays'].iteritems():
277 for viewName, colorspace in viewList.iteritems():
278 config.addDisplay(display, viewName, colorspace.name)
279 if not (viewName in views):
280 views.append(viewName)
281 displays.append(display)
282 # A Nuke specific set of views and displays
285 # A few names: Output Transform, ACES, ACEScc, are hard-coded here.
286 # Would be better to automate.
289 for display, viewList in configData['displays'].iteritems():
290 for viewName, colorspace in viewList.iteritems():
291 if (viewName == 'Output Transform'):
293 config.addDisplay(display, viewName, colorspace.name)
294 if not (viewName in views):
295 views.append(viewName)
296 displays.append(display)
298 config.addDisplay('linear', 'View', 'ACES2065-1')
299 displays.append('linear')
300 config.addDisplay('log', 'View', 'ACEScc')
301 displays.append('log')
303 # Set active displays and views
304 config.setActiveDisplays(','.join(sorted(displays)))
305 config.setActiveViews(','.join(views))
308 # Need to generalize this at some point
312 setConfigDefaultRoles(config,
313 color_picking=reference.getName(),
314 color_timing=reference.getName(),
315 compositing_log=reference.getName(),
316 data=reference.getName(),
317 default=reference.getName(),
318 matte_paint=reference.getName(),
319 reference=reference.getName(),
320 scene_linear=reference.getName(),
321 texture_paint=reference.getName())
323 # Check to make sure we didn't screw something up
330 # Functions to generate color space definitions and LUTs for transforms for a
331 # specific ACES release.
334 # Output is a list of colorspaces and transforms that convert between those
335 # colorspaces and reference color space, ACES
336 def generateLUTs(odtInfo,
341 lutResolution1d=4096,
344 print("generateLUTs - begin")
348 # Define the reference color space
350 ACES = ColorSpace('ACES2065-1')
352 'The Academy Color Encoding System reference color space')
353 ACES.equalityGroup = ''
356 ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
357 ACES.allocationVars = [-15, 6]
359 configData['referenceColorSpace'] = ACES
362 # Define the displays
364 configData['displays'] = {}
367 # Define the other color spaces
369 configData['colorSpaces'] = []
371 # Matrix converting ACES AP1 primaries to AP0
372 acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
373 0.0447945634, 0.8596711185, 0.0955343182,
374 -0.0055258826, 0.0040252103, 1.0015006723]
376 # Matrix converting ACES AP0 primaries to XYZ
377 acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
378 0.3439664498, 0.7281660966, -0.0721325464,
379 0.0000000000, 0.0000000000, 1.0088251844]
384 def createACEScc(name='ACEScc',
388 cs = ColorSpace(name)
389 cs.description = "The %s color space" % name
390 cs.equalityGroup = ''
395 '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
396 # This transform gets back to the AP1 primaries
397 # Useful as the 1d LUT is only covering the transfer function
398 # The primaries switch is covered by the matrix below
399 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
401 lut = "%s_to_ACES.spi1d" % name
403 # Remove spaces and parentheses
404 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
406 genlut.generate1dLUTFromCTL(
419 cs.toReferenceTransforms = []
420 cs.toReferenceTransforms.append({
423 'interpolation': 'linear',
424 'direction': 'forward'
427 # AP1 primaries to AP0 primaries
428 cs.toReferenceTransforms.append({
430 'matrix': mat44FromMat33(acesAP1toAP0),
431 'direction': 'forward'
434 cs.fromReferenceTransforms = []
437 ACEScc = createACEScc()
438 configData['colorSpaces'].append(ACEScc)
443 def createACESProxy(name='ACESproxy'):
444 cs = ColorSpace(name)
445 cs.description = "The %s color space" % name
446 cs.equalityGroup = ''
451 '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % (
453 # This transform gets back to the AP1 primaries
454 # Useful as the 1d LUT is only covering the transfer function
455 # The primaries switch is covered by the matrix below
456 '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
458 lut = "%s_to_aces.spi1d" % name
460 # Remove spaces and parentheses
461 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
463 genlut.generate1dLUTFromCTL(
474 cs.toReferenceTransforms = []
475 cs.toReferenceTransforms.append({
478 'interpolation': 'linear',
479 'direction': 'forward'
482 # AP1 primaries to AP0 primaries
483 cs.toReferenceTransforms.append({
485 'matrix': mat44FromMat33(acesAP1toAP0),
486 'direction': 'forward'
489 cs.fromReferenceTransforms = []
492 ACESproxy = createACESProxy()
493 configData['colorSpaces'].append(ACESproxy)
498 def createACEScg(name='ACEScg'):
499 cs = ColorSpace(name)
500 cs.description = "The %s color space" % name
501 cs.equalityGroup = ''
505 cs.toReferenceTransforms = []
507 # AP1 primaries to AP0 primaries
508 cs.toReferenceTransforms.append({
510 'matrix': mat44FromMat33(acesAP1toAP0),
511 'direction': 'forward'
514 cs.fromReferenceTransforms = []
517 ACEScg = createACEScg()
518 configData['colorSpaces'].append(ACEScg)
523 def createADX(bitdepth=10, name='ADX'):
524 name = "%s%s" % (name, bitdepth)
525 cs = ColorSpace(name)
526 cs.description = "%s color space - used for film scans" % name
527 cs.equalityGroup = ''
532 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
533 adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
534 0.0, 1023.0 / 500.0, 0.0, 0.0,
535 0.0, 0.0, 1023.0 / 500.0, 0.0,
537 offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
539 cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
540 adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
541 0.0, 65535.0 / 8000.0, 0.0, 0.0,
542 0.0, 0.0, 65535.0 / 8000.0, 0.0,
544 offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
547 cs.toReferenceTransforms = []
549 # Convert from ADX to Channel-Dependent Density
550 cs.toReferenceTransforms.append({
552 'matrix': adx_to_cdd,
554 'direction': 'forward'
557 # Convert from Channel-Dependent Density to Channel-Independent Density
558 cs.toReferenceTransforms.append({
560 'matrix': [0.75573, 0.22197, 0.02230, 0,
561 0.05901, 0.96928, -0.02829, 0,
562 0.16134, 0.07406, 0.76460, 0,
564 'direction': 'forward'
567 # Copied from Alex Fry's adx_cid_to_rle.py
568 def createCIDtoRLELUT():
569 def interpolate1D(x, xp, fp):
570 return numpy.interp(x, xp, fp)
572 LUT_1D_xp = [-0.190000000000000,
584 LUT_1D_fp = [-6.000000000000000,
596 REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
597 math.log(0.18, 10.0))
601 return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
602 return (100.0 / 55.0) * x - REF_PT
604 def Fit(value, fromMin, fromMax, toMin, toMax):
605 if fromMin == fromMax:
606 raise ValueError("fromMin == fromMax")
607 return (value - fromMin) / (fromMax - fromMin) * (
608 toMax - toMin) + toMin
610 NUM_SAMPLES = 2 ** 12
613 for i in xrange(NUM_SAMPLES):
614 x = i / (NUM_SAMPLES - 1.0)
615 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
616 data.append(cid_to_rle(x))
618 lut = 'ADX_CID_to_RLE.spi1d'
619 genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
624 # Convert Channel Independent Density values to Relative Log Exposure
626 lut = createCIDtoRLELUT()
627 cs.toReferenceTransforms.append({
630 'interpolation': 'linear',
631 'direction': 'forward'
634 # Convert Relative Log Exposure values to Relative Exposure values
635 cs.toReferenceTransforms.append({
638 'direction': 'inverse'
641 # Convert Relative Exposure values to ACES values
642 cs.toReferenceTransforms.append({
644 'matrix': [0.72286, 0.12630, 0.15084, 0,
645 0.11923, 0.76418, 0.11659, 0,
646 0.01427, 0.08213, 0.90359, 0,
648 'direction': 'forward'
651 cs.fromReferenceTransforms = []
654 ADX10 = createADX(bitdepth=10)
655 configData['colorSpaces'].append(ADX10)
657 ADX16 = createADX(bitdepth=16)
658 configData['colorSpaces'].append(ADX16)
661 # Camera Input Transforms
664 # RED color spaces to ACES
665 redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
666 for cs in redColorSpaces:
667 configData['colorSpaces'].append(cs)
670 canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
671 for cs in canonColorSpaces:
672 configData['colorSpaces'].append(cs)
675 sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
676 for cs in sonyColorSpaces:
677 configData['colorSpaces'].append(cs)
680 arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
681 for cs in arriColorSpaces:
682 configData['colorSpaces'].append(cs)
685 # Generic log transform
687 def createGenericLog(name='log',
694 lutResolution1d=lutResolution1d):
695 cs = ColorSpace(name)
696 cs.description = "The %s color space" % name
697 cs.equalityGroup = name
698 cs.family = 'Utility'
702 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % (
705 lut = "%s_to_aces.spi1d" % name
707 # Remove spaces and parentheses
708 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
710 genlut.generate1dLUTFromCTL(
718 'middleGrey': middleGrey,
719 'minExposure': minExposure,
720 'maxExposure': maxExposure
727 cs.toReferenceTransforms = []
728 cs.toReferenceTransforms.append({
731 'interpolation': 'linear',
732 'direction': 'forward'
735 cs.fromReferenceTransforms = []
741 def createACESLMT(lmtName,
744 lutResolution1d=1024,
747 cs = ColorSpace("%s" % lmtName)
748 cs.description = "The ACES Look Transform: %s" % lmtName
749 cs.equalityGroup = ''
755 pprint.pprint(lmtValues)
758 # Generate the shaper transform
764 shaperParams) = shaperInfo
766 shaperLut = "%s_to_aces.spi1d" % shaperName
767 if (not os.path.exists(lutDir + "/" + shaperLut)):
769 shaperToACESCTL % acesCTLReleaseDir
772 # Remove spaces and parentheses
773 shaperLut = shaperLut.replace(
774 ' ', '_').replace(')', '_').replace('(', '_')
776 genlut.generate1dLUTFromCTL(
777 lutDir + "/" + shaperLut,
781 1.0 / shaperInputScale,
787 shaperOCIOTransform = {
790 'interpolation': 'linear',
791 'direction': 'inverse'
795 # Generate the forward transform
797 cs.fromReferenceTransforms = []
799 if 'transformCTL' in lmtValues:
801 shaperToACESCTL % acesCTLReleaseDir,
802 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
804 lut = "%s.%s.spi3d" % (shaperName, lmtName)
806 # Remove spaces and parentheses
807 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
809 genlut.generate3dLUTFromCTL(
814 1.0 / shaperInputScale,
820 cs.fromReferenceTransforms.append(shaperOCIOTransform)
821 cs.fromReferenceTransforms.append({
824 'interpolation': 'tetrahedral',
825 'direction': 'forward'
829 # Generate the inverse transform
831 cs.toReferenceTransforms = []
833 if 'transformCTLInverse' in lmtValues:
836 acesCTLReleaseDir, odtValues['transformCTLInverse']),
837 shaperFromACESCTL % acesCTLReleaseDir
839 lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
841 # Remove spaces and parentheses
842 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
844 genlut.generate3dLUTFromCTL(
855 cs.toReferenceTransforms.append({
858 'interpolation': 'tetrahedral',
859 'direction': 'forward'
862 shaperInverse = shaperOCIOTransform.copy()
863 shaperInverse['direction'] = 'forward'
864 cs.toReferenceTransforms.append(shaperInverse)
872 lmtLutResolution1d = max(4096, lutResolution1d)
873 lmtLutResolution3d = max(65, lutResolution3d)
876 lmtShaperName = 'LMT Shaper'
879 'minExposure': -10.0,
882 lmtShaper = createGenericLog(name=lmtShaperName,
883 middleGrey=lmtParams['middleGrey'],
884 minExposure=lmtParams['minExposure'],
885 maxExposure=lmtParams['maxExposure'],
886 lutResolution1d=lmtLutResolution1d)
887 configData['colorSpaces'].append(lmtShaper)
889 shaperInputScale_genericLog2 = 1.0
891 # Log 2 shaper name and CTL transforms bundled up
894 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
895 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
896 shaperInputScale_genericLog2,
900 sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
902 for lmt in sortedLMTs:
903 (lmtName, lmtValues) = lmt
905 lmtValues['transformUserName'],
911 configData['colorSpaces'].append(cs)
914 # ACES RRT with the supplied ODT
916 def createACESRRTplusODT(odtName,
919 lutResolution1d=1024,
922 cs = ColorSpace("%s" % odtName)
923 cs.description = "%s - %s Output Transform" % (
924 odtValues['transformUserNamePrefix'], odtName)
925 cs.equalityGroup = ''
931 pprint.pprint(odtValues)
934 # Generate the shaper transform
936 # if 'shaperCTL' in odtValues:
941 shaperParams) = shaperInfo
943 if 'legalRange' in odtValues:
944 shaperParams['legalRange'] = odtValues['legalRange']
946 shaperParams['legalRange'] = 0
948 shaperLut = "%s_to_aces.spi1d" % shaperName
949 if (not os.path.exists(lutDir + "/" + shaperLut)):
951 shaperToACESCTL % acesCTLReleaseDir
954 # Remove spaces and parentheses
955 shaperLut = shaperLut.replace(
956 ' ', '_').replace(')', '_').replace('(', '_')
958 genlut.generate1dLUTFromCTL(
959 lutDir + "/" + shaperLut,
963 1.0 / shaperInputScale,
969 shaperOCIOTransform = {
972 'interpolation': 'linear',
973 'direction': 'inverse'
977 # Generate the forward transform
979 cs.fromReferenceTransforms = []
981 if 'transformLUT' in odtValues:
982 # Copy into the lut dir
983 transformLUTFileName = os.path.basename(odtValues['transformLUT'])
984 lut = lutDir + "/" + transformLUTFileName
985 shutil.copy(odtValues['transformLUT'], lut)
987 cs.fromReferenceTransforms.append(shaperOCIOTransform)
988 cs.fromReferenceTransforms.append({
990 'path': transformLUTFileName,
991 'interpolation': 'tetrahedral',
992 'direction': 'forward'
994 elif 'transformCTL' in odtValues:
998 shaperToACESCTL % acesCTLReleaseDir,
999 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1000 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1002 lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1004 # Remove spaces and parentheses
1005 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1007 genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1012 1.0 / shaperInputScale,
1018 cs.fromReferenceTransforms.append(shaperOCIOTransform)
1019 cs.fromReferenceTransforms.append({
1022 'interpolation': 'tetrahedral',
1023 'direction': 'forward'
1027 # Generate the inverse transform
1029 cs.toReferenceTransforms = []
1031 if 'transformLUTInverse' in odtValues:
1032 # Copy into the lut dir
1033 transformLUTInverseFileName = os.path.basename(
1034 odtValues['transformLUTInverse'])
1035 lut = lutDir + "/" + transformLUTInverseFileName
1036 shutil.copy(odtValues['transformLUTInverse'], lut)
1038 cs.toReferenceTransforms.append({
1040 'path': transformLUTInverseFileName,
1041 'interpolation': 'tetrahedral',
1042 'direction': 'forward'
1045 shaperInverse = shaperOCIOTransform.copy()
1046 shaperInverse['direction'] = 'forward'
1047 cs.toReferenceTransforms.append(shaperInverse)
1048 elif 'transformCTLInverse' in odtValues:
1051 acesCTLReleaseDir, odtValues['transformCTLInverse']),
1052 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1053 shaperFromACESCTL % acesCTLReleaseDir
1055 lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1057 # Remove spaces and parentheses
1058 lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1060 genlut.generate3dLUTFromCTL(
1072 cs.toReferenceTransforms.append({
1075 'interpolation': 'tetrahedral',
1076 'direction': 'forward'
1079 shaperInverse = shaperOCIOTransform.copy()
1080 shaperInverse['direction'] = 'forward'
1081 cs.toReferenceTransforms.append(shaperInverse)
1086 # RRT/ODT shaper options
1091 log2ShaperName = shaperName
1094 'minExposure': -6.0,
1097 log2Shaper = createGenericLog(name=log2ShaperName,
1098 middleGrey=log2Params['middleGrey'],
1099 minExposure=log2Params['minExposure'],
1100 maxExposure=log2Params['maxExposure'])
1101 configData['colorSpaces'].append(log2Shaper)
1103 shaperInputScale_genericLog2 = 1.0
1105 # Log 2 shaper name and CTL transforms bundled up
1108 '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1109 '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1110 shaperInputScale_genericLog2,
1114 shaperData[log2ShaperName] = log2ShaperData
1117 # Shaper that also includes the AP1 primaries
1118 # - Needed for some LUT baking steps
1120 log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1121 middleGrey=log2Params['middleGrey'],
1122 minExposure=log2Params['minExposure'],
1123 maxExposure=log2Params['maxExposure'])
1124 log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1125 # AP1 primaries to AP0 primaries
1126 log2ShaperAP1.toReferenceTransforms.append({
1128 'matrix': mat44FromMat33(acesAP1toAP0),
1129 'direction': 'forward'
1131 configData['colorSpaces'].append(log2ShaperAP1)
1134 # Choose your shaper
1136 rrtShaperName = log2ShaperName
1137 rrtShaper = log2ShaperData
1140 # RRT + ODT Combinations
1142 sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1144 for odt in sortedOdts:
1145 (odtName, odtValues) = odt
1147 # Have to handle ODTs that can generate either legal or full output
1148 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1149 'Academy.Rec709_100nits_dim.a1.0.0',
1150 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1151 odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1153 odtNameLegal = odtValues['transformUserName']
1155 odtLegal = odtValues.copy()
1156 odtLegal['legalRange'] = 1
1158 cs = createACESRRTplusODT(
1165 configData['colorSpaces'].append(cs)
1167 # Create a display entry using this color space
1168 configData['displays'][odtNameLegal] = {
1171 'Output Transform': cs}
1173 if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1174 'Academy.Rec709_100nits_dim.a1.0.0',
1175 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1176 print("Generating full range ODT for %s" % odtName)
1178 odtNameFull = "%s - Full" % odtValues['transformUserName']
1179 odtFull = odtValues.copy()
1180 odtFull['legalRange'] = 0
1182 csFull = createACESRRTplusODT(
1189 configData['colorSpaces'].append(csFull)
1191 # Create a display entry using this color space
1192 configData['displays'][odtNameFull] = {
1195 'Output Transform': csFull}
1198 # Generic Matrix transform
1200 def createGenericMatrix(name='matrix',
1201 fromReferenceValues=[],
1202 toReferenceValues=[]):
1203 cs = ColorSpace(name)
1204 cs.description = "The %s color space" % name
1205 cs.equalityGroup = name
1206 cs.family = 'Utility'
1209 cs.toReferenceTransforms = []
1210 if toReferenceValues != []:
1211 for matrix in toReferenceValues:
1212 cs.toReferenceTransforms.append({
1214 'matrix': mat44FromMat33(matrix),
1215 'direction': 'forward'
1218 cs.fromReferenceTransforms = []
1219 if fromReferenceValues != []:
1220 for matrix in fromReferenceValues:
1221 cs.fromReferenceTransforms.append({
1223 'matrix': mat44FromMat33(matrix),
1224 'direction': 'forward'
1229 cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1230 configData['colorSpaces'].append(cs)
1232 cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1233 configData['colorSpaces'].append(cs)
1235 # ACES to Linear, P3D60 primaries
1236 xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1237 -0.8325796487, 1.7692317536, 0.0237127115,
1238 0.0388233815, -0.0824996856, 1.0363685997]
1240 cs = createGenericMatrix('Linear - P3-D60',
1241 fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1242 configData['colorSpaces'].append(cs)
1244 # ACES to Linear, P3D60 primaries
1245 xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1246 -0.7951680258, 1.6897320548, 0.0226471906,
1247 0.0412418914, -0.0876390192, 1.1009293786]
1249 cs = createGenericMatrix('Linear - P3-DCI',
1250 fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1251 configData['colorSpaces'].append(cs)
1253 # ACES to Linear, Rec 709 primaries
1254 xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1255 -0.9692436363, 1.8759675015, 0.0415550574,
1256 0.0556300797, -0.2039769589, 1.0569715142]
1258 cs = createGenericMatrix('Linear - Rec.709',
1259 fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1260 configData['colorSpaces'].append(cs)
1262 # ACES to Linear, Rec 2020 primaries
1263 xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1264 -0.6666843518, 1.6164812366, 0.0157685458,
1265 0.0176398574, -0.0427706133, 0.9421031212]
1267 cs = createGenericMatrix('Linear - Rec.2020',
1268 fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1269 configData['colorSpaces'].append(cs)
1271 print("generateLUTs - end")
1275 def generateBakedLUTs(odtInfo,
1281 lutResolutionShaper=1024):
1282 # Add the legal and full variations into this list
1283 odtInfoC = dict(odtInfo)
1284 for odtCTLName, odtValues in odtInfo.iteritems():
1285 if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1286 'Academy.Rec709_100nits_dim.a1.0.0',
1287 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1288 odtName = odtValues["transformUserName"]
1290 odtValuesLegal = dict(odtValues)
1291 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1292 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1294 odtValuesFull = dict(odtValues)
1295 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1296 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1298 del (odtInfoC[odtCTLName])
1300 for odtCTLName, odtValues in odtInfoC.iteritems():
1301 odtPrefix = odtValues["transformUserNamePrefix"]
1302 odtName = odtValues["transformUserName"]
1305 for inputspace in ["ACEScc", "ACESproxy"]:
1306 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1307 args += ["--outputspace", "%s" % odtName]
1308 args += ["--description",
1309 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1310 args += ["--shaperspace", shaperName, "--shapersize",
1311 str(lutResolutionShaper)]
1312 args += ["--cubesize", str(lutResolution3d)]
1313 args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1314 bakedDir, odtName, inputspace)]
1316 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1321 for inputspace in ["ACEScc", "ACESproxy"]:
1322 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1323 args += ["--outputspace", "%s" % odtName]
1324 args += ["--description",
1325 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1326 args += ["--shaperspace", shaperName, "--shapersize",
1327 str(lutResolutionShaper)]
1328 args += ["--cubesize", str(lutResolution3d)]
1330 fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1331 bakedDir, odtName, inputspace)]
1332 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1333 args=(args + fargs))
1336 largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1337 bakedDir, odtName, inputspace)]
1338 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1339 args=(args + largs))
1343 for inputspace in ["ACEScg", "ACES2065-1"]:
1344 args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1345 args += ["--outputspace", "%s" % odtName]
1346 args += ["--description",
1347 "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1348 if inputspace == 'ACEScg':
1349 linShaperName = "%s - AP1" % shaperName
1351 linShaperName = shaperName
1352 args += ["--shaperspace", linShaperName, "--shapersize",
1353 str(lutResolutionShaper)]
1355 args += ["--cubesize", str(lutResolution3d)]
1357 margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1358 bakedDir, odtName, inputspace)]
1359 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1360 args=(args + margs))
1363 hargs = ["--format", "houdini",
1364 "%s/houdini/%s for %s Houdini.lut" % (
1365 bakedDir, odtName, inputspace)]
1366 bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1367 args=(args + hargs))
1371 def createConfigDir(configDir, bakeSecondaryLUTs):
1372 dirs = [configDir, "%s/luts" % configDir]
1373 if bakeSecondaryLUTs:
1374 dirs.extend(["%s/baked" % configDir,
1375 "%s/baked/flame" % configDir,
1376 "%s/baked/photoshop" % configDir,
1377 "%s/baked/houdini" % configDir,
1378 "%s/baked/lustre" % configDir,
1379 "%s/baked/maya" % configDir])
1382 if not os.path.exists(d):
1386 def getTransformInfo(ctlTransform):
1387 fp = open(ctlTransform, 'rb')
1390 lines = fp.readlines()
1392 # Grab transform ID and User Name
1393 transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1394 # print(transformID)
1395 transformUserName = '-'.join(
1396 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1397 1:]).lstrip().rstrip()
1398 transformUserNamePrefix = \
1399 lines[2][3:].split('<')[1].split('>')[1].split('-')[
1400 0].lstrip().rstrip()
1401 # print(transformUserName)
1404 return transformID, transformUserName, transformUserNamePrefix
1407 # For versions after WGR9
1408 def getODTInfo(acesCTLReleaseDir):
1409 # Credit to Alex Fry for the original approach here
1410 odtDir = os.path.join(acesCTLReleaseDir, "odt")
1412 for dirName, subdirList, fileList in os.walk(odtDir):
1413 for fname in fileList:
1414 allodt.append((os.path.join(dirName, fname)))
1416 odtCTLs = [x for x in allodt if
1417 ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1423 for odtCTL in odtCTLs:
1424 odtTokens = os.path.split(odtCTL)
1427 # Handle nested directories
1428 odtPathTokens = os.path.split(odtTokens[-2])
1429 odtDir = odtPathTokens[-1]
1430 while odtPathTokens[-2][-3:] != 'odt':
1431 odtPathTokens = os.path.split(odtPathTokens[-2])
1432 odtDir = os.path.join(odtPathTokens[-1], odtDir)
1435 # print("odtDir : %s" % odtDir)
1436 transformCTL = odtTokens[-1]
1437 # print(transformCTL)
1438 odtName = string.join(transformCTL.split('.')[1:-1], '.')
1441 # Find id, user name and user name prefix
1442 (transformID, transformUserName,
1443 transformUserNamePrefix) = getTransformInfo(
1444 "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1447 transformCTLInverse = "InvODT.%s.ctl" % odtName
1448 if not os.path.exists(
1449 os.path.join(odtTokens[-2], transformCTLInverse)):
1450 transformCTLInverse = None
1451 # print(transformCTLInverse)
1453 # Add to list of ODTs
1455 odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1456 if transformCTLInverse != None:
1457 odts[odtName]['transformCTLInverse'] = os.path.join(
1458 odtDir, transformCTLInverse)
1460 odts[odtName]['transformID'] = transformID
1461 odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1462 odts[odtName]['transformUserName'] = transformUserName
1464 print("ODT : %s" % odtName)
1465 print("\tTransform ID : %s" % transformID)
1466 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1467 print("\tTransform User Name : %s" % transformUserName)
1468 print("\tForward ctl : %s" % (
1469 odts[odtName]['transformCTL']))
1470 if 'transformCTLInverse' in odts[odtName]:
1471 print("\tInverse ctl : %s" % (
1472 odts[odtName]['transformCTLInverse']))
1474 print("\tInverse ctl : %s" % "None")
1481 # For versions after WGR9
1482 def getLMTInfo(acesCTLReleaseDir):
1483 # Credit to Alex Fry for the original approach here
1484 lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1486 for dirName, subdirList, fileList in os.walk(lmtDir):
1487 for fname in fileList:
1488 alllmt.append((os.path.join(dirName, fname)))
1490 lmtCTLs = [x for x in alllmt if
1491 ("InvLMT" not in x) and ("README" not in x) and (
1492 os.path.split(x)[-1][0] != '.')]
1498 for lmtCTL in lmtCTLs:
1499 lmtTokens = os.path.split(lmtCTL)
1502 # Handle nested directories
1503 lmtPathTokens = os.path.split(lmtTokens[-2])
1504 lmtDir = lmtPathTokens[-1]
1505 while lmtPathTokens[-2][-3:] != 'ctl':
1506 lmtPathTokens = os.path.split(lmtPathTokens[-2])
1507 lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1510 # print("lmtDir : %s" % lmtDir)
1511 transformCTL = lmtTokens[-1]
1512 # print(transformCTL)
1513 lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1516 # Find id, user name and user name prefix
1517 (transformID, transformUserName,
1518 transformUserNamePrefix) = getTransformInfo(
1519 "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1522 transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1523 if not os.path.exists(
1524 os.path.join(lmtTokens[-2], transformCTLInverse)):
1525 transformCTLInverse = None
1526 # print(transformCTLInverse)
1528 # Add to list of LMTs
1530 lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1531 if transformCTLInverse != None:
1532 # TODO: Check unresolved *odtName* referemce.
1533 lmts[odtName]['transformCTLInverse'] = os.path.join(
1534 lmtDir, transformCTLInverse)
1536 lmts[lmtName]['transformID'] = transformID
1537 lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1538 lmts[lmtName]['transformUserName'] = transformUserName
1540 print("LMT : %s" % lmtName)
1541 print("\tTransform ID : %s" % transformID)
1542 print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1543 print("\tTransform User Name : %s" % transformUserName)
1544 print("\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1545 if 'transformCTLInverse' in lmts[lmtName]:
1546 print("\t Inverse ctl : %s" % (
1547 lmts[lmtName]['transformCTLInverse']))
1549 print("\t Inverse ctl : %s" % "None")
1557 # Create the ACES config
1559 def createACESConfig(acesCTLReleaseDir,
1561 lutResolution1d=4096,
1563 bakeSecondaryLUTs=True,
1565 # Get ODT names and CTL paths
1566 odtInfo = getODTInfo(acesCTLReleaseDir)
1568 # Get ODT names and CTL paths
1569 lmtInfo = getLMTInfo(acesCTLReleaseDir)
1572 createConfigDir(configDir, bakeSecondaryLUTs)
1574 # Generate config data and LUTs for different transforms
1575 lutDir = "%s/luts" % configDir
1576 shaperName = 'Output Shaper'
1577 configData = generateLUTs(odtInfo,
1586 # Create the config using the generated LUTs
1587 print("Creating generic config")
1588 config = createConfig(configData)
1591 # Write the config to disk
1592 writeConfig(config, "%s/config.ocio" % configDir)
1594 # Create a config that will work well with Nuke using the previously
1596 print("Creating Nuke-specific config")
1597 nuke_config = createConfig(configData, nuke=True)
1600 # Write the config to disk
1601 writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1603 # Bake secondary LUTs using the config
1604 if bakeSecondaryLUTs:
1605 generateBakedLUTs(odtInfo,
1607 "%s/baked" % configDir,
1608 "%s/config.ocio" % configDir,
1622 p = optparse.OptionParser(description='An OCIO config generation script',
1623 prog='createACESConfig',
1624 version='createACESConfig 0.1',
1625 usage='%prog [options]')
1626 p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1627 'ACES_OCIO_CTL_DIRECTORY', None))
1628 p.add_option('--configDir', '-c', default=os.environ.get(
1629 'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1630 p.add_option('--lutResolution1d', default=4096)
1631 p.add_option('--lutResolution3d', default=64)
1632 p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1633 p.add_option('--keepTempImages', action="store_true")
1635 options, arguments = p.parse_args()
1640 acesCTLDir = options.acesCTLDir
1641 configDir = options.configDir
1642 lutResolution1d = int(options.lutResolution1d)
1643 lutResolution3d = int(options.lutResolution3d)
1644 bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1645 cleanupTempImages = not (options.keepTempImages)
1648 argsStart = sys.argv.index('--') + 1
1649 args = sys.argv[argsStart:]
1651 argsStart = len(sys.argv) + 1
1654 print("command line : \n%s\n" % " ".join(sys.argv))
1656 # TODO: Use assertion and mention environment variables.
1658 print("process: No ACES CTL directory specified")
1661 print("process: No configuration directory specified")
1664 # Generate the configuration
1666 return createACESConfig(acesCTLDir,
1675 if __name__ == '__main__':