Add main module attributes.
[OpenColorIO-Configs.git] / aces_1.0.0 / python / aces_ocio / create_aces_config.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 usage from python
6
7 import sys
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)
13
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
16
17
18
19 build instructions for osx for needed packages.
20
21 #opencolorio
22 brew install -vd opencolorio --with-python
23
24 #openimageio
25 brew tap homebrew/science
26
27 # optional installs
28 brew install -vd libRaw
29 brew install -vd OpenCV
30
31 brew install -vd openimageio --with-python
32
33 #ctl
34 brew install -vd CTL
35
36 #opencolorio - again.
37 # this time, 'ociolutimage' will build because openimageio is installed
38 brew uninstall -vd opencolorio
39 brew install -vd opencolorio --with-python
40 """
41
42 import math
43 import numpy
44 import os
45 import shutil
46 import string
47 import sys
48
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__), '..'))
53
54 import PyOpenColorIO as OCIO
55
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
63
64 __author__ = 'ACES Developers'
65 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
66 __license__ = ''
67 __maintainer__ = 'ACES Developers'
68 __email__ = 'aces@oscars.org'
69 __status__ = 'Production'
70
71 __all__ = ['ACES_OCIO_CTL_DIRECTORY_ENVIRON',
72            'ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON',
73            'setConfigDefaultRoles',
74            'writeConfig',
75            'generateOCIOTransform',
76            'createConfig',
77            'generateLUTs',
78            'generateBakedLUTs',
79            'createConfigDir',
80            'getTransformInfo',
81            'getODTInfo',
82            'getLMTInfo',
83            'createACESConfig',
84            'main']
85
86 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
87 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
88
89 #
90 # Utility functions
91 #
92 def setConfigDefaultRoles(config,
93                           color_picking="",
94                           color_timing="",
95                           compositing_log="",
96                           data="",
97                           default="",
98                           matte_paint="",
99                           reference="",
100                           scene_linear="",
101                           texture_paint=""):
102     # Add Roles
103     if color_picking:
104         config.setRole(OCIO.Constants.ROLE_COLOR_PICKING, color_picking)
105     if color_timing:
106         config.setRole(OCIO.Constants.ROLE_COLOR_TIMING, color_timing)
107     if compositing_log:
108         config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log)
109     if data:
110         config.setRole(OCIO.Constants.ROLE_DATA, data)
111     if default:
112         config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
113     if matte_paint:
114         config.setRole(OCIO.Constants.ROLE_MATTE_PAINT, matte_paint)
115     if reference:
116         config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
117     if scene_linear:
118         config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear)
119     if texture_paint:
120         config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint)
121
122
123 # Write config to disk
124 def writeConfig(config, configPath, sanityCheck=True):
125     if sanityCheck:
126         try:
127             config.sanityCheck()
128         except Exception, e:
129             print e
130             print "Configuration was not written due to a failed Sanity Check"
131             return
132             # sys.exit()
133
134     fileHandle = open(configPath, mode='w')
135     fileHandle.write(config.serialize())
136     fileHandle.close()
137
138
139 def generateOCIOTransform(transforms):
140     # print("Generating transforms")
141
142     interpolationOptions = {
143         'linear': OCIO.Constants.INTERP_LINEAR,
144         'nearest': OCIO.Constants.INTERP_NEAREST,
145         'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
146     }
147     directionOptions = {
148         'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
149         'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
150     }
151
152     ocioTransforms = []
153
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'])
166
167             if 'offset' in transform:
168                 ocioTransform.setOffset(transform['offset'])
169
170             if 'direction' in transform:
171                 ocioTransform.setDirection(
172                     directionOptions[transform['direction']])
173
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']])
183
184             ocioTransforms.append(ocioTransform)
185         else:
186             print("Ignoring unknown transform type : %s" % transform['type'])
187
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
194
195     # Or take the first transform from the list
196     else:
197         transform = ocioTransforms[0]
198
199     return transform
200
201
202 def createConfig(configData, nuke=False):
203     # Create the config
204     config = OCIO.Config()
205
206     #
207     # Set config wide values
208     #
209     config.setDescription("An ACES config generated from python")
210     config.setSearchPath("luts")
211
212     #
213     # Define the reference color space
214     #
215     referenceData = configData['referenceColorSpace']
216     print("Adding the reference color space : %s" % referenceData.name)
217
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)
228
229     # Add to config
230     config.addColorSpace(reference)
231
232     #
233     # Create the rest of the color spaces
234     #
235     for colorspace in sorted(configData['colorSpaces']):
236         print("Creating new color space : %s" % colorspace.name)
237
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)
247
248         if colorspace.toReferenceTransforms != []:
249             print("Generating To-Reference transforms")
250             ocioTransform = generateOCIOTransform(
251                 colorspace.toReferenceTransforms)
252             ocioColorspace.setTransform(
253                 ocioTransform,
254                 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
255
256         if colorspace.fromReferenceTransforms != []:
257             print("Generating From-Reference transforms")
258             ocioTransform = generateOCIOTransform(
259                 colorspace.fromReferenceTransforms)
260             ocioColorspace.setTransform(
261                 ocioTransform,
262                 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
263
264         config.addColorSpace(ocioColorspace)
265
266         print("")
267
268     #
269     # Define the views and displays
270     #
271     displays = []
272     views = []
273
274     # Generic display and view setup
275     if not nuke:
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
283     #
284     # XXX
285     # A few names: Output Transform, ACES, ACEScc, are hard-coded here.
286     # Would be better to automate.
287     #
288     else:
289         for display, viewList in configData['displays'].iteritems():
290             for viewName, colorspace in viewList.iteritems():
291                 if (viewName == 'Output Transform'):
292                     viewName = 'View'
293                     config.addDisplay(display, viewName, colorspace.name)
294                     if not (viewName in views):
295                         views.append(viewName)
296             displays.append(display)
297
298         config.addDisplay('linear', 'View', 'ACES2065-1')
299         displays.append('linear')
300         config.addDisplay('log', 'View', 'ACEScc')
301         displays.append('log')
302
303     # Set active displays and views
304     config.setActiveDisplays(','.join(sorted(displays)))
305     config.setActiveViews(','.join(views))
306
307     #
308     # Need to generalize this at some point
309     #
310
311     # Add Default Roles
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())
322
323     # Check to make sure we didn't screw something up
324     config.sanityCheck()
325
326     return config
327
328
329 #
330 # Functions to generate color space definitions and LUTs for transforms for a
331 # specific ACES release.
332 #
333
334 # Output is a list of colorspaces and transforms that convert between those
335 # colorspaces and reference color space, ACES
336 def generateLUTs(odtInfo,
337                  lmtInfo,
338                  shaperName,
339                  acesCTLReleaseDir,
340                  lutDir,
341                  lutResolution1d=4096,
342                  lutResolution3d=64,
343                  cleanup=True):
344     print("generateLUTs - begin")
345     configData = {}
346
347     #
348     # Define the reference color space
349     #
350     ACES = ColorSpace('ACES2065-1')
351     ACES.description = (
352         'The Academy Color Encoding System reference color space')
353     ACES.equalityGroup = ''
354     ACES.family = 'ACES'
355     ACES.isData = False
356     ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
357     ACES.allocationVars = [-15, 6]
358
359     configData['referenceColorSpace'] = ACES
360
361     #
362     # Define the displays
363     #
364     configData['displays'] = {}
365
366     #
367     # Define the other color spaces
368     #
369     configData['colorSpaces'] = []
370
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]
375
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]
380
381     #
382     # ACEScc
383     #
384     def createACEScc(name='ACEScc',
385                      minValue=0.0,
386                      maxValue=1.0,
387                      inputScale=1.0):
388         cs = ColorSpace(name)
389         cs.description = "The %s color space" % name
390         cs.equalityGroup = ''
391         cs.family = 'ACES'
392         cs.isData = False
393
394         ctls = [
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
400         ]
401         lut = "%s_to_ACES.spi1d" % name
402
403         # Remove spaces and parentheses
404         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
405
406         genlut.generate1dLUTFromCTL(
407             lutDir + "/" + lut,
408             ctls,
409             lutResolution1d,
410             'float',
411             inputScale,
412             1.0,
413             {},
414             cleanup,
415             acesCTLReleaseDir,
416             minValue,
417             maxValue)
418
419         cs.toReferenceTransforms = []
420         cs.toReferenceTransforms.append({
421             'type': 'lutFile',
422             'path': lut,
423             'interpolation': 'linear',
424             'direction': 'forward'
425         })
426
427         # AP1 primaries to AP0 primaries
428         cs.toReferenceTransforms.append({
429             'type': 'matrix',
430             'matrix': mat44FromMat33(acesAP1toAP0),
431             'direction': 'forward'
432         })
433
434         cs.fromReferenceTransforms = []
435         return cs
436
437     ACEScc = createACEScc()
438     configData['colorSpaces'].append(ACEScc)
439
440     #
441     # ACESproxy
442     #
443     def createACESProxy(name='ACESproxy'):
444         cs = ColorSpace(name)
445         cs.description = "The %s color space" % name
446         cs.equalityGroup = ''
447         cs.family = 'ACES'
448         cs.isData = False
449
450         ctls = [
451             '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % (
452                 acesCTLReleaseDir),
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
457         ]
458         lut = "%s_to_aces.spi1d" % name
459
460         # Remove spaces and parentheses
461         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
462
463         genlut.generate1dLUTFromCTL(
464             lutDir + "/" + lut,
465             ctls,
466             lutResolution1d,
467             'uint16',
468             64.0,
469             1.0,
470             {},
471             cleanup,
472             acesCTLReleaseDir)
473
474         cs.toReferenceTransforms = []
475         cs.toReferenceTransforms.append({
476             'type': 'lutFile',
477             'path': lut,
478             'interpolation': 'linear',
479             'direction': 'forward'
480         })
481
482         # AP1 primaries to AP0 primaries
483         cs.toReferenceTransforms.append({
484             'type': 'matrix',
485             'matrix': mat44FromMat33(acesAP1toAP0),
486             'direction': 'forward'
487         })
488
489         cs.fromReferenceTransforms = []
490         return cs
491
492     ACESproxy = createACESProxy()
493     configData['colorSpaces'].append(ACESproxy)
494
495     #
496     # ACEScg
497     #
498     def createACEScg(name='ACEScg'):
499         cs = ColorSpace(name)
500         cs.description = "The %s color space" % name
501         cs.equalityGroup = ''
502         cs.family = 'ACES'
503         cs.isData = False
504
505         cs.toReferenceTransforms = []
506
507         # AP1 primaries to AP0 primaries
508         cs.toReferenceTransforms.append({
509             'type': 'matrix',
510             'matrix': mat44FromMat33(acesAP1toAP0),
511             'direction': 'forward'
512         })
513
514         cs.fromReferenceTransforms = []
515         return cs
516
517     ACEScg = createACEScg()
518     configData['colorSpaces'].append(ACEScg)
519
520     #
521     # ADX
522     #
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 = ''
528         cs.family = 'ADX'
529         cs.isData = False
530
531         if bitdepth == 10:
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,
536                           0.0, 0.0, 0.0, 1.0]
537             offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
538         elif bitdepth == 16:
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,
543                           0.0, 0.0, 0.0, 1.0]
544             offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
545                       0.0]
546
547         cs.toReferenceTransforms = []
548
549         # Convert from ADX to Channel-Dependent Density
550         cs.toReferenceTransforms.append({
551             'type': 'matrix',
552             'matrix': adx_to_cdd,
553             'offset': offset,
554             'direction': 'forward'
555         })
556
557         # Convert from Channel-Dependent Density to Channel-Independent Density
558         cs.toReferenceTransforms.append({
559             'type': 'matrix',
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,
563                        0.0, 0.0, 0.0, 1.0],
564             'direction': 'forward'
565         })
566
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)
571
572             LUT_1D_xp = [-0.190000000000000,
573                          0.010000000000000,
574                          0.028000000000000,
575                          0.054000000000000,
576                          0.095000000000000,
577                          0.145000000000000,
578                          0.220000000000000,
579                          0.300000000000000,
580                          0.400000000000000,
581                          0.500000000000000,
582                          0.600000000000000]
583
584             LUT_1D_fp = [-6.000000000000000,
585                          -2.721718645000000,
586                          -2.521718645000000,
587                          -2.321718645000000,
588                          -2.121718645000000,
589                          -1.921718645000000,
590                          -1.721718645000000,
591                          -1.521718645000000,
592                          -1.321718645000000,
593                          -1.121718645000000,
594                          -0.926545676714876]
595
596             REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
597                       math.log(0.18, 10.0))
598
599             def cid_to_rle(x):
600                 if x <= 0.6:
601                     return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
602                 return (100.0 / 55.0) * x - REF_PT
603
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
609
610             NUM_SAMPLES = 2 ** 12
611             RANGE = (-0.19, 3.0)
612             data = []
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))
617
618             lut = 'ADX_CID_to_RLE.spi1d'
619             genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
620                               NUM_SAMPLES, 1)
621
622             return lut
623
624         # Convert Channel Independent Density values to Relative Log Exposure
625         # values.
626         lut = createCIDtoRLELUT()
627         cs.toReferenceTransforms.append({
628             'type': 'lutFile',
629             'path': lut,
630             'interpolation': 'linear',
631             'direction': 'forward'
632         })
633
634         # Convert Relative Log Exposure values to Relative Exposure values
635         cs.toReferenceTransforms.append({
636             'type': 'log',
637             'base': 10,
638             'direction': 'inverse'
639         })
640
641         # Convert Relative Exposure values to ACES values
642         cs.toReferenceTransforms.append({
643             'type': 'matrix',
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,
647                        0.0, 0.0, 0.0, 1.0],
648             'direction': 'forward'
649         })
650
651         cs.fromReferenceTransforms = []
652         return cs
653
654     ADX10 = createADX(bitdepth=10)
655     configData['colorSpaces'].append(ADX10)
656
657     ADX16 = createADX(bitdepth=16)
658     configData['colorSpaces'].append(ADX16)
659
660     #
661     # Camera Input Transforms
662     #
663
664     # RED color spaces to ACES
665     redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
666     for cs in redColorSpaces:
667         configData['colorSpaces'].append(cs)
668
669     # Canon-Log to ACES
670     canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
671     for cs in canonColorSpaces:
672         configData['colorSpaces'].append(cs)
673
674     # SLog to ACES
675     sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
676     for cs in sonyColorSpaces:
677         configData['colorSpaces'].append(cs)
678
679     # LogC to ACES
680     arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
681     for cs in arriColorSpaces:
682         configData['colorSpaces'].append(cs)
683
684     #
685     # Generic log transform
686     #
687     def createGenericLog(name='log',
688                          minValue=0.0,
689                          maxValue=1.0,
690                          inputScale=1.0,
691                          middleGrey=0.18,
692                          minExposure=-6.0,
693                          maxExposure=6.5,
694                          lutResolution1d=lutResolution1d):
695         cs = ColorSpace(name)
696         cs.description = "The %s color space" % name
697         cs.equalityGroup = name
698         cs.family = 'Utility'
699         cs.isData = False
700
701         ctls = [
702             '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % (
703                 acesCTLReleaseDir)
704         ]
705         lut = "%s_to_aces.spi1d" % name
706
707         # Remove spaces and parentheses
708         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
709
710         genlut.generate1dLUTFromCTL(
711             lutDir + "/" + lut,
712             ctls,
713             lutResolution1d,
714             'float',
715             inputScale,
716             1.0,
717             {
718                 'middleGrey': middleGrey,
719                 'minExposure': minExposure,
720                 'maxExposure': maxExposure
721             },
722             cleanup,
723             acesCTLReleaseDir,
724             minValue,
725             maxValue)
726
727         cs.toReferenceTransforms = []
728         cs.toReferenceTransforms.append({
729             'type': 'lutFile',
730             'path': lut,
731             'interpolation': 'linear',
732             'direction': 'forward'
733         })
734
735         cs.fromReferenceTransforms = []
736         return cs
737
738     #
739     # ACES LMTs
740     #
741     def createACESLMT(lmtName,
742                       lmtValues,
743                       shaperInfo,
744                       lutResolution1d=1024,
745                       lutResolution3d=64,
746                       cleanup=True):
747         cs = ColorSpace("%s" % lmtName)
748         cs.description = "The ACES Look Transform: %s" % lmtName
749         cs.equalityGroup = ''
750         cs.family = 'Look'
751         cs.isData = False
752
753         import pprint
754
755         pprint.pprint(lmtValues)
756
757         #
758         # Generate the shaper transform
759         #
760         (shaperName,
761          shaperToACESCTL,
762          shaperFromACESCTL,
763          shaperInputScale,
764          shaperParams) = shaperInfo
765
766         shaperLut = "%s_to_aces.spi1d" % shaperName
767         if (not os.path.exists(lutDir + "/" + shaperLut)):
768             ctls = [
769                 shaperToACESCTL % acesCTLReleaseDir
770             ]
771
772             # Remove spaces and parentheses
773             shaperLut = shaperLut.replace(
774                 ' ', '_').replace(')', '_').replace('(', '_')
775
776             genlut.generate1dLUTFromCTL(
777                 lutDir + "/" + shaperLut,
778                 ctls,
779                 lutResolution1d,
780                 'float',
781                 1.0 / shaperInputScale,
782                 1.0,
783                 shaperParams,
784                 cleanup,
785                 acesCTLReleaseDir)
786
787         shaperOCIOTransform = {
788             'type': 'lutFile',
789             'path': shaperLut,
790             'interpolation': 'linear',
791             'direction': 'inverse'
792         }
793
794         #
795         # Generate the forward transform
796         #
797         cs.fromReferenceTransforms = []
798
799         if 'transformCTL' in lmtValues:
800             ctls = [
801                 shaperToACESCTL % acesCTLReleaseDir,
802                 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
803             ]
804             lut = "%s.%s.spi3d" % (shaperName, lmtName)
805
806             # Remove spaces and parentheses
807             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
808
809             genlut.generate3dLUTFromCTL(
810                 lutDir + "/" + lut,
811                 ctls,
812                 lutResolution3d,
813                 'float',
814                 1.0 / shaperInputScale,
815                 1.0,
816                 shaperParams,
817                 cleanup,
818                 acesCTLReleaseDir)
819
820             cs.fromReferenceTransforms.append(shaperOCIOTransform)
821             cs.fromReferenceTransforms.append({
822                 'type': 'lutFile',
823                 'path': lut,
824                 'interpolation': 'tetrahedral',
825                 'direction': 'forward'
826             })
827
828         #
829         # Generate the inverse transform
830         #
831         cs.toReferenceTransforms = []
832
833         if 'transformCTLInverse' in lmtValues:
834             ctls = [
835                 '%s/%s' % (
836                     acesCTLReleaseDir, odtValues['transformCTLInverse']),
837                 shaperFromACESCTL % acesCTLReleaseDir
838             ]
839             lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
840
841             # Remove spaces and parentheses
842             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
843
844             genlut.generate3dLUTFromCTL(
845                 lutDir + "/" + lut,
846                 ctls,
847                 lutResolution3d,
848                 'half',
849                 1.0,
850                 shaperInputScale,
851                 shaperParams,
852                 cleanup,
853                 acesCTLReleaseDir)
854
855             cs.toReferenceTransforms.append({
856                 'type': 'lutFile',
857                 'path': lut,
858                 'interpolation': 'tetrahedral',
859                 'direction': 'forward'
860             })
861
862             shaperInverse = shaperOCIOTransform.copy()
863             shaperInverse['direction'] = 'forward'
864             cs.toReferenceTransforms.append(shaperInverse)
865
866         return cs
867
868     #
869     # LMT Shaper
870     #
871
872     lmtLutResolution1d = max(4096, lutResolution1d)
873     lmtLutResolution3d = max(65, lutResolution3d)
874
875     # Log 2 shaper
876     lmtShaperName = 'LMT Shaper'
877     lmtParams = {
878         'middleGrey': 0.18,
879         'minExposure': -10.0,
880         'maxExposure': 6.5
881     }
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)
888
889     shaperInputScale_genericLog2 = 1.0
890
891     # Log 2 shaper name and CTL transforms bundled up
892     lmtShaperData = [
893         lmtShaperName,
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,
897         lmtParams
898     ]
899
900     sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
901     print(sortedLMTs)
902     for lmt in sortedLMTs:
903         (lmtName, lmtValues) = lmt
904         cs = createACESLMT(
905             lmtValues['transformUserName'],
906             lmtValues,
907             lmtShaperData,
908             lmtLutResolution1d,
909             lmtLutResolution3d,
910             cleanup)
911         configData['colorSpaces'].append(cs)
912
913     #
914     # ACES RRT with the supplied ODT
915     #
916     def createACESRRTplusODT(odtName,
917                              odtValues,
918                              shaperInfo,
919                              lutResolution1d=1024,
920                              lutResolution3d=64,
921                              cleanup=True):
922         cs = ColorSpace("%s" % odtName)
923         cs.description = "%s - %s Output Transform" % (
924             odtValues['transformUserNamePrefix'], odtName)
925         cs.equalityGroup = ''
926         cs.family = 'Output'
927         cs.isData = False
928
929         import pprint
930
931         pprint.pprint(odtValues)
932
933         #
934         # Generate the shaper transform
935         #
936         # if 'shaperCTL' in odtValues:
937         (shaperName,
938          shaperToACESCTL,
939          shaperFromACESCTL,
940          shaperInputScale,
941          shaperParams) = shaperInfo
942
943         if 'legalRange' in odtValues:
944             shaperParams['legalRange'] = odtValues['legalRange']
945         else:
946             shaperParams['legalRange'] = 0
947
948         shaperLut = "%s_to_aces.spi1d" % shaperName
949         if (not os.path.exists(lutDir + "/" + shaperLut)):
950             ctls = [
951                 shaperToACESCTL % acesCTLReleaseDir
952             ]
953
954             # Remove spaces and parentheses
955             shaperLut = shaperLut.replace(
956                 ' ', '_').replace(')', '_').replace('(', '_')
957
958             genlut.generate1dLUTFromCTL(
959                 lutDir + "/" + shaperLut,
960                 ctls,
961                 lutResolution1d,
962                 'float',
963                 1.0 / shaperInputScale,
964                 1.0,
965                 shaperParams,
966                 cleanup,
967                 acesCTLReleaseDir)
968
969         shaperOCIOTransform = {
970             'type': 'lutFile',
971             'path': shaperLut,
972             'interpolation': 'linear',
973             'direction': 'inverse'
974         }
975
976         #
977         # Generate the forward transform
978         #
979         cs.fromReferenceTransforms = []
980
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)
986
987             cs.fromReferenceTransforms.append(shaperOCIOTransform)
988             cs.fromReferenceTransforms.append({
989                 'type': 'lutFile',
990                 'path': transformLUTFileName,
991                 'interpolation': 'tetrahedral',
992                 'direction': 'forward'
993             })
994         elif 'transformCTL' in odtValues:
995             # shaperLut
996
997             ctls = [
998                 shaperToACESCTL % acesCTLReleaseDir,
999                 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1000                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1001             ]
1002             lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1003
1004             # Remove spaces and parentheses
1005             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1006
1007             genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1008                                         # shaperLUT,
1009                                         ctls,
1010                                         lutResolution3d,
1011                                         'float',
1012                                         1.0 / shaperInputScale,
1013                                         1.0,
1014                                         shaperParams,
1015                                         cleanup,
1016                                         acesCTLReleaseDir)
1017
1018             cs.fromReferenceTransforms.append(shaperOCIOTransform)
1019             cs.fromReferenceTransforms.append({
1020                 'type': 'lutFile',
1021                 'path': lut,
1022                 'interpolation': 'tetrahedral',
1023                 'direction': 'forward'
1024             })
1025
1026         #
1027         # Generate the inverse transform
1028         #
1029         cs.toReferenceTransforms = []
1030
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)
1037
1038             cs.toReferenceTransforms.append({
1039                 'type': 'lutFile',
1040                 'path': transformLUTInverseFileName,
1041                 'interpolation': 'tetrahedral',
1042                 'direction': 'forward'
1043             })
1044
1045             shaperInverse = shaperOCIOTransform.copy()
1046             shaperInverse['direction'] = 'forward'
1047             cs.toReferenceTransforms.append(shaperInverse)
1048         elif 'transformCTLInverse' in odtValues:
1049             ctls = [
1050                 '%s/odt/%s' % (
1051                     acesCTLReleaseDir, odtValues['transformCTLInverse']),
1052                 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1053                 shaperFromACESCTL % acesCTLReleaseDir
1054             ]
1055             lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1056
1057             # Remove spaces and parentheses
1058             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1059
1060             genlut.generate3dLUTFromCTL(
1061                 lutDir + "/" + lut,
1062                 # None,
1063                 ctls,
1064                 lutResolution3d,
1065                 'half',
1066                 1.0,
1067                 shaperInputScale,
1068                 shaperParams,
1069                 cleanup,
1070                 acesCTLReleaseDir)
1071
1072             cs.toReferenceTransforms.append({
1073                 'type': 'lutFile',
1074                 'path': lut,
1075                 'interpolation': 'tetrahedral',
1076                 'direction': 'forward'
1077             })
1078
1079             shaperInverse = shaperOCIOTransform.copy()
1080             shaperInverse['direction'] = 'forward'
1081             cs.toReferenceTransforms.append(shaperInverse)
1082
1083         return cs
1084
1085     #
1086     # RRT/ODT shaper options
1087     #
1088     shaperData = {}
1089
1090     # Log 2 shaper
1091     log2ShaperName = shaperName
1092     log2Params = {
1093         'middleGrey': 0.18,
1094         'minExposure': -6.0,
1095         'maxExposure': 6.5
1096     }
1097     log2Shaper = createGenericLog(name=log2ShaperName,
1098                                   middleGrey=log2Params['middleGrey'],
1099                                   minExposure=log2Params['minExposure'],
1100                                   maxExposure=log2Params['maxExposure'])
1101     configData['colorSpaces'].append(log2Shaper)
1102
1103     shaperInputScale_genericLog2 = 1.0
1104
1105     # Log 2 shaper name and CTL transforms bundled up
1106     log2ShaperData = [
1107         log2ShaperName,
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,
1111         log2Params
1112     ]
1113
1114     shaperData[log2ShaperName] = log2ShaperData
1115
1116     #
1117     # Shaper that also includes the AP1 primaries
1118     # - Needed for some LUT baking steps
1119     #
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({
1127         'type': 'matrix',
1128         'matrix': mat44FromMat33(acesAP1toAP0),
1129         'direction': 'forward'
1130     })
1131     configData['colorSpaces'].append(log2ShaperAP1)
1132
1133     #
1134     # Choose your shaper
1135     #
1136     rrtShaperName = log2ShaperName
1137     rrtShaper = log2ShaperData
1138
1139     #
1140     # RRT + ODT Combinations
1141     #
1142     sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1143     print(sortedOdts)
1144     for odt in sortedOdts:
1145         (odtName, odtValues) = odt
1146
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']
1152         else:
1153             odtNameLegal = odtValues['transformUserName']
1154
1155         odtLegal = odtValues.copy()
1156         odtLegal['legalRange'] = 1
1157
1158         cs = createACESRRTplusODT(
1159             odtNameLegal,
1160             odtLegal,
1161             rrtShaper,
1162             lutResolution1d,
1163             lutResolution3d,
1164             cleanup)
1165         configData['colorSpaces'].append(cs)
1166
1167         # Create a display entry using this color space
1168         configData['displays'][odtNameLegal] = {
1169             'Linear': ACES,
1170             'Log': ACEScc,
1171             'Output Transform': cs}
1172
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)
1177
1178             odtNameFull = "%s - Full" % odtValues['transformUserName']
1179             odtFull = odtValues.copy()
1180             odtFull['legalRange'] = 0
1181
1182             csFull = createACESRRTplusODT(
1183                 odtNameFull,
1184                 odtFull,
1185                 rrtShaper,
1186                 lutResolution1d,
1187                 lutResolution3d,
1188                 cleanup)
1189             configData['colorSpaces'].append(csFull)
1190
1191             # Create a display entry using this color space
1192             configData['displays'][odtNameFull] = {
1193                 'Linear': ACES,
1194                 'Log': ACEScc,
1195                 'Output Transform': csFull}
1196
1197     #
1198     # Generic Matrix transform
1199     #
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'
1207         cs.isData = False
1208
1209         cs.toReferenceTransforms = []
1210         if toReferenceValues != []:
1211             for matrix in toReferenceValues:
1212                 cs.toReferenceTransforms.append({
1213                     'type': 'matrix',
1214                     'matrix': mat44FromMat33(matrix),
1215                     'direction': 'forward'
1216                 })
1217
1218         cs.fromReferenceTransforms = []
1219         if fromReferenceValues != []:
1220             for matrix in fromReferenceValues:
1221                 cs.fromReferenceTransforms.append({
1222                     'type': 'matrix',
1223                     'matrix': mat44FromMat33(matrix),
1224                     'direction': 'forward'
1225                 })
1226
1227         return cs
1228
1229     cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1230     configData['colorSpaces'].append(cs)
1231
1232     cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1233     configData['colorSpaces'].append(cs)
1234
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]
1239
1240     cs = createGenericMatrix('Linear - P3-D60',
1241                              fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1242     configData['colorSpaces'].append(cs)
1243
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]
1248
1249     cs = createGenericMatrix('Linear - P3-DCI',
1250                              fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1251     configData['colorSpaces'].append(cs)
1252
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]
1257
1258     cs = createGenericMatrix('Linear - Rec.709',
1259                              fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1260     configData['colorSpaces'].append(cs)
1261
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]
1266
1267     cs = createGenericMatrix('Linear - Rec.2020',
1268                              fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1269     configData['colorSpaces'].append(cs)
1270
1271     print("generateLUTs - end")
1272     return configData
1273
1274
1275 def generateBakedLUTs(odtInfo,
1276                       shaperName,
1277                       bakedDir,
1278                       configPath,
1279                       lutResolution1d,
1280                       lutResolution3d,
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"]
1289
1290             odtValuesLegal = dict(odtValues)
1291             odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1292             odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1293
1294             odtValuesFull = dict(odtValues)
1295             odtValuesFull["transformUserName"] = "%s - Full" % odtName
1296             odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1297
1298             del (odtInfoC[odtCTLName])
1299
1300     for odtCTLName, odtValues in odtInfoC.iteritems():
1301         odtPrefix = odtValues["transformUserNamePrefix"]
1302         odtName = odtValues["transformUserName"]
1303
1304         # For Photoshop
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)]
1315
1316             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1317                               args=args)
1318             bakeLUT.execute()
1319
1320             # For Flame, Lustre
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)]
1329
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))
1334             bakeLUT.execute()
1335
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))
1340             bakeLUT.execute()
1341
1342         # For Maya, Houdini
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
1350             else:
1351                 linShaperName = shaperName
1352             args += ["--shaperspace", linShaperName, "--shapersize",
1353                      str(lutResolutionShaper)]
1354
1355             args += ["--cubesize", str(lutResolution3d)]
1356
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))
1361             bakeLUT.execute()
1362
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))
1368             bakeLUT.execute()
1369
1370
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])
1380
1381     for d in dirs:
1382         if not os.path.exists(d):
1383             os.mkdir(d)
1384
1385
1386 def getTransformInfo(ctlTransform):
1387     fp = open(ctlTransform, 'rb')
1388
1389     # Read lines
1390     lines = fp.readlines()
1391
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)
1402     fp.close()
1403
1404     return transformID, transformUserName, transformUserNamePrefix
1405
1406
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")
1411     allodt = []
1412     for dirName, subdirList, fileList in os.walk(odtDir):
1413         for fname in fileList:
1414             allodt.append((os.path.join(dirName, fname)))
1415
1416     odtCTLs = [x for x in allodt if
1417                ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1418
1419     # print odtCTLs
1420
1421     odts = {}
1422
1423     for odtCTL in odtCTLs:
1424         odtTokens = os.path.split(odtCTL)
1425         # print(odtTokens)
1426
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)
1433
1434         # Build full name
1435         # print("odtDir : %s" % odtDir)
1436         transformCTL = odtTokens[-1]
1437         # print(transformCTL)
1438         odtName = string.join(transformCTL.split('.')[1:-1], '.')
1439         # print(odtName)
1440
1441         # Find id, user name and user name prefix
1442         (transformID, transformUserName,
1443          transformUserNamePrefix) = getTransformInfo(
1444             "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1445
1446         # Find inverse
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)
1452
1453         # Add to list of ODTs
1454         odts[odtName] = {}
1455         odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1456         if transformCTLInverse != None:
1457             odts[odtName]['transformCTLInverse'] = os.path.join(
1458                 odtDir, transformCTLInverse)
1459
1460         odts[odtName]['transformID'] = transformID
1461         odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1462         odts[odtName]['transformUserName'] = transformUserName
1463
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']))
1473         else:
1474             print("\tInverse ctl                : %s" % "None")
1475
1476     print("\n")
1477
1478     return odts
1479
1480
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")
1485     alllmt = []
1486     for dirName, subdirList, fileList in os.walk(lmtDir):
1487         for fname in fileList:
1488             alllmt.append((os.path.join(dirName, fname)))
1489
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] != '.')]
1493
1494     # print lmtCTLs
1495
1496     lmts = {}
1497
1498     for lmtCTL in lmtCTLs:
1499         lmtTokens = os.path.split(lmtCTL)
1500         # print(lmtTokens)
1501
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)
1508
1509         # Build full name
1510         # print("lmtDir : %s" % lmtDir)
1511         transformCTL = lmtTokens[-1]
1512         # print(transformCTL)
1513         lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1514         # print(lmtName)
1515
1516         # Find id, user name and user name prefix
1517         (transformID, transformUserName,
1518          transformUserNamePrefix) = getTransformInfo(
1519             "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1520
1521         # Find inverse
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)
1527
1528         # Add to list of LMTs
1529         lmts[lmtName] = {}
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)
1535
1536         lmts[lmtName]['transformID'] = transformID
1537         lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1538         lmts[lmtName]['transformUserName'] = transformUserName
1539
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']))
1548         else:
1549             print("\t Inverse ctl : %s" % "None")
1550
1551     print("\n")
1552
1553     return lmts
1554
1555
1556 #
1557 # Create the ACES config
1558 #
1559 def createACESConfig(acesCTLReleaseDir,
1560                      configDir,
1561                      lutResolution1d=4096,
1562                      lutResolution3d=64,
1563                      bakeSecondaryLUTs=True,
1564                      cleanup=True):
1565     # Get ODT names and CTL paths
1566     odtInfo = getODTInfo(acesCTLReleaseDir)
1567
1568     # Get ODT names and CTL paths
1569     lmtInfo = getLMTInfo(acesCTLReleaseDir)
1570
1571     # Create config dir
1572     createConfigDir(configDir, bakeSecondaryLUTs)
1573
1574     # Generate config data and LUTs for different transforms
1575     lutDir = "%s/luts" % configDir
1576     shaperName = 'Output Shaper'
1577     configData = generateLUTs(odtInfo,
1578                               lmtInfo,
1579                               shaperName,
1580                               acesCTLReleaseDir,
1581                               lutDir,
1582                               lutResolution1d,
1583                               lutResolution3d,
1584                               cleanup)
1585
1586     # Create the config using the generated LUTs
1587     print("Creating generic config")
1588     config = createConfig(configData)
1589     print("\n\n\n")
1590
1591     # Write the config to disk
1592     writeConfig(config, "%s/config.ocio" % configDir)
1593
1594     # Create a config that will work well with Nuke using the previously
1595     # generated LUTs.
1596     print("Creating Nuke-specific config")
1597     nuke_config = createConfig(configData, nuke=True)
1598     print("\n\n\n")
1599
1600     # Write the config to disk
1601     writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1602
1603     # Bake secondary LUTs using the config
1604     if bakeSecondaryLUTs:
1605         generateBakedLUTs(odtInfo,
1606                           shaperName,
1607                           "%s/baked" % configDir,
1608                           "%s/config.ocio" % configDir,
1609                           lutResolution1d,
1610                           lutResolution3d,
1611                           lutResolution1d)
1612
1613     return True
1614
1615
1616 #
1617 # Main
1618 #
1619 def main():
1620     import optparse
1621
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")
1634
1635     options, arguments = p.parse_args()
1636
1637     #
1638     # Get options
1639     # 
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)
1646
1647     try:
1648         argsStart = sys.argv.index('--') + 1
1649         args = sys.argv[argsStart:]
1650     except:
1651         argsStart = len(sys.argv) + 1
1652         args = []
1653
1654     print("command line : \n%s\n" % " ".join(sys.argv))
1655
1656     # TODO: Use assertion and mention environment variables.
1657     if not acesCTLDir:
1658         print("process: No ACES CTL directory specified")
1659         return
1660     if not configDir:
1661         print("process: No configuration directory specified")
1662         return
1663     #
1664     # Generate the configuration
1665     #
1666     return createACESConfig(acesCTLDir,
1667                             configDir,
1668                             lutResolution1d,
1669                             lutResolution3d,
1670                             bakeSecondaryLUTs,
1671                             cleanupTempImages)
1672
1673 # main
1674
1675 if __name__ == '__main__':
1676     main()