18f2d8d4f3baa1fda910749ec18f5e4f5f34b78f
[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 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
65 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
66
67 #
68 # Utility functions
69 #
70 def setConfigDefaultRoles(config,
71                           color_picking="",
72                           color_timing="",
73                           compositing_log="",
74                           data="",
75                           default="",
76                           matte_paint="",
77                           reference="",
78                           scene_linear="",
79                           texture_paint=""):
80     # Add Roles
81     if color_picking:
82         config.setRole(OCIO.Constants.ROLE_COLOR_PICKING, color_picking)
83     if color_timing:
84         config.setRole(OCIO.Constants.ROLE_COLOR_TIMING, color_timing)
85     if compositing_log:
86         config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log)
87     if data:
88         config.setRole(OCIO.Constants.ROLE_DATA, data)
89     if default:
90         config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
91     if matte_paint:
92         config.setRole(OCIO.Constants.ROLE_MATTE_PAINT, matte_paint)
93     if reference:
94         config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
95     if scene_linear:
96         config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear)
97     if texture_paint:
98         config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint)
99
100
101 # Write config to disk
102 def writeConfig(config, configPath, sanityCheck=True):
103     if sanityCheck:
104         try:
105             config.sanityCheck()
106         except Exception, e:
107             print e
108             print "Configuration was not written due to a failed Sanity Check"
109             return
110             # sys.exit()
111
112     fileHandle = open(configPath, mode='w')
113     fileHandle.write(config.serialize())
114     fileHandle.close()
115
116
117 def generateOCIOTransform(transforms):
118     # print("Generating transforms")
119
120     interpolationOptions = {
121         'linear': OCIO.Constants.INTERP_LINEAR,
122         'nearest': OCIO.Constants.INTERP_NEAREST,
123         'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
124     }
125     directionOptions = {
126         'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
127         'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
128     }
129
130     ocioTransforms = []
131
132     for transform in transforms:
133         if transform['type'] == 'lutFile':
134             ocioTransform = OCIO.FileTransform(
135                 src=transform['path'],
136                 interpolation=interpolationOptions[transform['interpolation']],
137                 direction=directionOptions[transform['direction']])
138             ocioTransforms.append(ocioTransform)
139         elif transform['type'] == 'matrix':
140             ocioTransform = OCIO.MatrixTransform()
141             # MatrixTransform member variables can't be initialized directly.
142             # Each must be set individually.
143             ocioTransform.setMatrix(transform['matrix'])
144
145             if 'offset' in transform:
146                 ocioTransform.setOffset(transform['offset'])
147
148             if 'direction' in transform:
149                 ocioTransform.setDirection(
150                     directionOptions[transform['direction']])
151
152             ocioTransforms.append(ocioTransform)
153         elif transform['type'] == 'exponent':
154             ocioTransform = OCIO.ExponentTransform()
155             ocioTransform.setValue(transform['value'])
156             ocioTransforms.append(ocioTransform)
157         elif transform['type'] == 'log':
158             ocioTransform = OCIO.LogTransform(
159                 base=transform['base'],
160                 direction=directionOptions[transform['direction']])
161
162             ocioTransforms.append(ocioTransform)
163         else:
164             print("Ignoring unknown transform type : %s" % transform['type'])
165
166     # Build a group transform if necessary
167     if len(ocioTransforms) > 1:
168         transformG = OCIO.GroupTransform()
169         for transform in ocioTransforms:
170             transformG.push_back(transform)
171         transform = transformG
172
173     # Or take the first transform from the list
174     else:
175         transform = ocioTransforms[0]
176
177     return transform
178
179
180 def createConfig(configData, nuke=False):
181     # Create the config
182     config = OCIO.Config()
183
184     #
185     # Set config wide values
186     #
187     config.setDescription("An ACES config generated from python")
188     config.setSearchPath("luts")
189
190     #
191     # Define the reference color space
192     #
193     referenceData = configData['referenceColorSpace']
194     print("Adding the reference color space : %s" % referenceData.name)
195
196     # Create a color space
197     reference = OCIO.ColorSpace(
198         name=referenceData.name,
199         bitDepth=referenceData.bitDepth,
200         description=referenceData.description,
201         equalityGroup=referenceData.equalityGroup,
202         family=referenceData.family,
203         isData=referenceData.isData,
204         allocation=referenceData.allocationType,
205         allocationVars=referenceData.allocationVars)
206
207     # Add to config
208     config.addColorSpace(reference)
209
210     #
211     # Create the rest of the color spaces
212     #
213     for colorspace in sorted(configData['colorSpaces']):
214         print("Creating new color space : %s" % colorspace.name)
215
216         ocioColorspace = OCIO.ColorSpace(
217             name=colorspace.name,
218             bitDepth=colorspace.bitDepth,
219             description=colorspace.description,
220             equalityGroup=colorspace.equalityGroup,
221             family=colorspace.family,
222             isData=colorspace.isData,
223             allocation=colorspace.allocationType,
224             allocationVars=colorspace.allocationVars)
225
226         if colorspace.toReferenceTransforms != []:
227             print("Generating To-Reference transforms")
228             ocioTransform = generateOCIOTransform(
229                 colorspace.toReferenceTransforms)
230             ocioColorspace.setTransform(
231                 ocioTransform,
232                 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
233
234         if colorspace.fromReferenceTransforms != []:
235             print("Generating From-Reference transforms")
236             ocioTransform = generateOCIOTransform(
237                 colorspace.fromReferenceTransforms)
238             ocioColorspace.setTransform(
239                 ocioTransform,
240                 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
241
242         config.addColorSpace(ocioColorspace)
243
244         print("")
245
246     #
247     # Define the views and displays
248     #
249     displays = []
250     views = []
251
252     # Generic display and view setup
253     if not nuke:
254         for display, viewList in configData['displays'].iteritems():
255             for viewName, colorspace in viewList.iteritems():
256                 config.addDisplay(display, viewName, colorspace.name)
257                 if not (viewName in views):
258                     views.append(viewName)
259             displays.append(display)
260     # A Nuke specific set of views and displays
261     #
262     # XXX
263     # A few names: Output Transform, ACES, ACEScc, are hard-coded here.
264     # Would be better to automate.
265     #
266     else:
267         for display, viewList in configData['displays'].iteritems():
268             for viewName, colorspace in viewList.iteritems():
269                 if (viewName == 'Output Transform'):
270                     viewName = 'View'
271                     config.addDisplay(display, viewName, colorspace.name)
272                     if not (viewName in views):
273                         views.append(viewName)
274             displays.append(display)
275
276         config.addDisplay('linear', 'View', 'ACES2065-1')
277         displays.append('linear')
278         config.addDisplay('log', 'View', 'ACEScc')
279         displays.append('log')
280
281     # Set active displays and views
282     config.setActiveDisplays(','.join(sorted(displays)))
283     config.setActiveViews(','.join(views))
284
285     #
286     # Need to generalize this at some point
287     #
288
289     # Add Default Roles
290     setConfigDefaultRoles(config,
291                           color_picking=reference.getName(),
292                           color_timing=reference.getName(),
293                           compositing_log=reference.getName(),
294                           data=reference.getName(),
295                           default=reference.getName(),
296                           matte_paint=reference.getName(),
297                           reference=reference.getName(),
298                           scene_linear=reference.getName(),
299                           texture_paint=reference.getName())
300
301     # Check to make sure we didn't screw something up
302     config.sanityCheck()
303
304     return config
305
306
307 #
308 # Functions to generate color space definitions and LUTs for transforms for a
309 # specific ACES release.
310 #
311
312 # Output is a list of colorspaces and transforms that convert between those
313 # colorspaces and reference color space, ACES
314 def generateLUTs(odtInfo,
315                  lmtInfo,
316                  shaperName,
317                  acesCTLReleaseDir,
318                  lutDir,
319                  lutResolution1d=4096,
320                  lutResolution3d=64,
321                  cleanup=True):
322     print("generateLUTs - begin")
323     configData = {}
324
325     #
326     # Define the reference color space
327     #
328     ACES = ColorSpace('ACES2065-1')
329     ACES.description = (
330         'The Academy Color Encoding System reference color space')
331     ACES.equalityGroup = ''
332     ACES.family = 'ACES'
333     ACES.isData = False
334     ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
335     ACES.allocationVars = [-15, 6]
336
337     configData['referenceColorSpace'] = ACES
338
339     #
340     # Define the displays
341     #
342     configData['displays'] = {}
343
344     #
345     # Define the other color spaces
346     #
347     configData['colorSpaces'] = []
348
349     # Matrix converting ACES AP1 primaries to AP0
350     acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
351                     0.0447945634, 0.8596711185, 0.0955343182,
352                     -0.0055258826, 0.0040252103, 1.0015006723]
353
354     # Matrix converting ACES AP0 primaries to XYZ
355     acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
356                     0.3439664498, 0.7281660966, -0.0721325464,
357                     0.0000000000, 0.0000000000, 1.0088251844]
358
359     #
360     # ACEScc
361     #
362     def createACEScc(name='ACEScc',
363                      minValue=0.0,
364                      maxValue=1.0,
365                      inputScale=1.0):
366         cs = ColorSpace(name)
367         cs.description = "The %s color space" % name
368         cs.equalityGroup = ''
369         cs.family = 'ACES'
370         cs.isData = False
371
372         ctls = [
373             '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
374             # This transform gets back to the AP1 primaries
375             # Useful as the 1d LUT is only covering the transfer function
376             # The primaries switch is covered by the matrix below
377             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
378         ]
379         lut = "%s_to_ACES.spi1d" % name
380
381         # Remove spaces and parentheses
382         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
383
384         genlut.generate1dLUTFromCTL(
385             lutDir + "/" + lut,
386             ctls,
387             lutResolution1d,
388             'float',
389             inputScale,
390             1.0,
391             {},
392             cleanup,
393             acesCTLReleaseDir,
394             minValue,
395             maxValue)
396
397         cs.toReferenceTransforms = []
398         cs.toReferenceTransforms.append({
399             'type': 'lutFile',
400             'path': lut,
401             'interpolation': 'linear',
402             'direction': 'forward'
403         })
404
405         # AP1 primaries to AP0 primaries
406         cs.toReferenceTransforms.append({
407             'type': 'matrix',
408             'matrix': mat44FromMat33(acesAP1toAP0),
409             'direction': 'forward'
410         })
411
412         cs.fromReferenceTransforms = []
413         return cs
414
415     ACEScc = createACEScc()
416     configData['colorSpaces'].append(ACEScc)
417
418     #
419     # ACESproxy
420     #
421     def createACESProxy(name='ACESproxy'):
422         cs = ColorSpace(name)
423         cs.description = "The %s color space" % name
424         cs.equalityGroup = ''
425         cs.family = 'ACES'
426         cs.isData = False
427
428         ctls = [
429             '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % (
430                 acesCTLReleaseDir),
431             # This transform gets back to the AP1 primaries
432             # Useful as the 1d LUT is only covering the transfer function
433             # The primaries switch is covered by the matrix below
434             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
435         ]
436         lut = "%s_to_aces.spi1d" % name
437
438         # Remove spaces and parentheses
439         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
440
441         genlut.generate1dLUTFromCTL(
442             lutDir + "/" + lut,
443             ctls,
444             lutResolution1d,
445             'uint16',
446             64.0,
447             1.0,
448             {},
449             cleanup,
450             acesCTLReleaseDir)
451
452         cs.toReferenceTransforms = []
453         cs.toReferenceTransforms.append({
454             'type': 'lutFile',
455             'path': lut,
456             'interpolation': 'linear',
457             'direction': 'forward'
458         })
459
460         # AP1 primaries to AP0 primaries
461         cs.toReferenceTransforms.append({
462             'type': 'matrix',
463             'matrix': mat44FromMat33(acesAP1toAP0),
464             'direction': 'forward'
465         })
466
467         cs.fromReferenceTransforms = []
468         return cs
469
470     ACESproxy = createACESProxy()
471     configData['colorSpaces'].append(ACESproxy)
472
473     #
474     # ACEScg
475     #
476     def createACEScg(name='ACEScg'):
477         cs = ColorSpace(name)
478         cs.description = "The %s color space" % name
479         cs.equalityGroup = ''
480         cs.family = 'ACES'
481         cs.isData = False
482
483         cs.toReferenceTransforms = []
484
485         # AP1 primaries to AP0 primaries
486         cs.toReferenceTransforms.append({
487             'type': 'matrix',
488             'matrix': mat44FromMat33(acesAP1toAP0),
489             'direction': 'forward'
490         })
491
492         cs.fromReferenceTransforms = []
493         return cs
494
495     ACEScg = createACEScg()
496     configData['colorSpaces'].append(ACEScg)
497
498     #
499     # ADX
500     #
501     def createADX(bitdepth=10, name='ADX'):
502         name = "%s%s" % (name, bitdepth)
503         cs = ColorSpace(name)
504         cs.description = "%s color space - used for film scans" % name
505         cs.equalityGroup = ''
506         cs.family = 'ADX'
507         cs.isData = False
508
509         if bitdepth == 10:
510             cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
511             adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
512                           0.0, 1023.0 / 500.0, 0.0, 0.0,
513                           0.0, 0.0, 1023.0 / 500.0, 0.0,
514                           0.0, 0.0, 0.0, 1.0]
515             offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
516         elif bitdepth == 16:
517             cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
518             adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
519                           0.0, 65535.0 / 8000.0, 0.0, 0.0,
520                           0.0, 0.0, 65535.0 / 8000.0, 0.0,
521                           0.0, 0.0, 0.0, 1.0]
522             offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
523                       0.0]
524
525         cs.toReferenceTransforms = []
526
527         # Convert from ADX to Channel-Dependent Density
528         cs.toReferenceTransforms.append({
529             'type': 'matrix',
530             'matrix': adx_to_cdd,
531             'offset': offset,
532             'direction': 'forward'
533         })
534
535         # Convert from Channel-Dependent Density to Channel-Independent Density
536         cs.toReferenceTransforms.append({
537             'type': 'matrix',
538             'matrix': [0.75573, 0.22197, 0.02230, 0,
539                        0.05901, 0.96928, -0.02829, 0,
540                        0.16134, 0.07406, 0.76460, 0,
541                        0.0, 0.0, 0.0, 1.0],
542             'direction': 'forward'
543         })
544
545         # Copied from Alex Fry's adx_cid_to_rle.py
546         def createCIDtoRLELUT():
547             def interpolate1D(x, xp, fp):
548                 return numpy.interp(x, xp, fp)
549
550             LUT_1D_xp = [-0.190000000000000,
551                          0.010000000000000,
552                          0.028000000000000,
553                          0.054000000000000,
554                          0.095000000000000,
555                          0.145000000000000,
556                          0.220000000000000,
557                          0.300000000000000,
558                          0.400000000000000,
559                          0.500000000000000,
560                          0.600000000000000]
561
562             LUT_1D_fp = [-6.000000000000000,
563                          -2.721718645000000,
564                          -2.521718645000000,
565                          -2.321718645000000,
566                          -2.121718645000000,
567                          -1.921718645000000,
568                          -1.721718645000000,
569                          -1.521718645000000,
570                          -1.321718645000000,
571                          -1.121718645000000,
572                          -0.926545676714876]
573
574             REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
575                       math.log(0.18, 10.0))
576
577             def cid_to_rle(x):
578                 if x <= 0.6:
579                     return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
580                 return (100.0 / 55.0) * x - REF_PT
581
582             def Fit(value, fromMin, fromMax, toMin, toMax):
583                 if fromMin == fromMax:
584                     raise ValueError("fromMin == fromMax")
585                 return (value - fromMin) / (fromMax - fromMin) * (
586                     toMax - toMin) + toMin
587
588             NUM_SAMPLES = 2 ** 12
589             RANGE = (-0.19, 3.0)
590             data = []
591             for i in xrange(NUM_SAMPLES):
592                 x = i / (NUM_SAMPLES - 1.0)
593                 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
594                 data.append(cid_to_rle(x))
595
596             lut = 'ADX_CID_to_RLE.spi1d'
597             genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
598                               NUM_SAMPLES, 1)
599
600             return lut
601
602         # Convert Channel Independent Density values to Relative Log Exposure
603         # values.
604         lut = createCIDtoRLELUT()
605         cs.toReferenceTransforms.append({
606             'type': 'lutFile',
607             'path': lut,
608             'interpolation': 'linear',
609             'direction': 'forward'
610         })
611
612         # Convert Relative Log Exposure values to Relative Exposure values
613         cs.toReferenceTransforms.append({
614             'type': 'log',
615             'base': 10,
616             'direction': 'inverse'
617         })
618
619         # Convert Relative Exposure values to ACES values
620         cs.toReferenceTransforms.append({
621             'type': 'matrix',
622             'matrix': [0.72286, 0.12630, 0.15084, 0,
623                        0.11923, 0.76418, 0.11659, 0,
624                        0.01427, 0.08213, 0.90359, 0,
625                        0.0, 0.0, 0.0, 1.0],
626             'direction': 'forward'
627         })
628
629         cs.fromReferenceTransforms = []
630         return cs
631
632     ADX10 = createADX(bitdepth=10)
633     configData['colorSpaces'].append(ADX10)
634
635     ADX16 = createADX(bitdepth=16)
636     configData['colorSpaces'].append(ADX16)
637
638     #
639     # Camera Input Transforms
640     #
641
642     # RED color spaces to ACES
643     redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
644     for cs in redColorSpaces:
645         configData['colorSpaces'].append(cs)
646
647     # Canon-Log to ACES
648     canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
649     for cs in canonColorSpaces:
650         configData['colorSpaces'].append(cs)
651
652     # SLog to ACES
653     sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
654     for cs in sonyColorSpaces:
655         configData['colorSpaces'].append(cs)
656
657     # LogC to ACES
658     arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
659     for cs in arriColorSpaces:
660         configData['colorSpaces'].append(cs)
661
662     #
663     # Generic log transform
664     #
665     def createGenericLog(name='log',
666                          minValue=0.0,
667                          maxValue=1.0,
668                          inputScale=1.0,
669                          middleGrey=0.18,
670                          minExposure=-6.0,
671                          maxExposure=6.5,
672                          lutResolution1d=lutResolution1d):
673         cs = ColorSpace(name)
674         cs.description = "The %s color space" % name
675         cs.equalityGroup = name
676         cs.family = 'Utility'
677         cs.isData = False
678
679         ctls = [
680             '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % (
681                 acesCTLReleaseDir)
682         ]
683         lut = "%s_to_aces.spi1d" % name
684
685         # Remove spaces and parentheses
686         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
687
688         genlut.generate1dLUTFromCTL(
689             lutDir + "/" + lut,
690             ctls,
691             lutResolution1d,
692             'float',
693             inputScale,
694             1.0,
695             {
696                 'middleGrey': middleGrey,
697                 'minExposure': minExposure,
698                 'maxExposure': maxExposure
699             },
700             cleanup,
701             acesCTLReleaseDir,
702             minValue,
703             maxValue)
704
705         cs.toReferenceTransforms = []
706         cs.toReferenceTransforms.append({
707             'type': 'lutFile',
708             'path': lut,
709             'interpolation': 'linear',
710             'direction': 'forward'
711         })
712
713         cs.fromReferenceTransforms = []
714         return cs
715
716     #
717     # ACES LMTs
718     #
719     def createACESLMT(lmtName,
720                       lmtValues,
721                       shaperInfo,
722                       lutResolution1d=1024,
723                       lutResolution3d=64,
724                       cleanup=True):
725         cs = ColorSpace("%s" % lmtName)
726         cs.description = "The ACES Look Transform: %s" % lmtName
727         cs.equalityGroup = ''
728         cs.family = 'Look'
729         cs.isData = False
730
731         import pprint
732
733         pprint.pprint(lmtValues)
734
735         #
736         # Generate the shaper transform
737         #
738         (shaperName,
739          shaperToACESCTL,
740          shaperFromACESCTL,
741          shaperInputScale,
742          shaperParams) = shaperInfo
743
744         shaperLut = "%s_to_aces.spi1d" % shaperName
745         if (not os.path.exists(lutDir + "/" + shaperLut)):
746             ctls = [
747                 shaperToACESCTL % acesCTLReleaseDir
748             ]
749
750             # Remove spaces and parentheses
751             shaperLut = shaperLut.replace(
752                 ' ', '_').replace(')', '_').replace('(', '_')
753
754             genlut.generate1dLUTFromCTL(
755                 lutDir + "/" + shaperLut,
756                 ctls,
757                 lutResolution1d,
758                 'float',
759                 1.0 / shaperInputScale,
760                 1.0,
761                 shaperParams,
762                 cleanup,
763                 acesCTLReleaseDir)
764
765         shaperOCIOTransform = {
766             'type': 'lutFile',
767             'path': shaperLut,
768             'interpolation': 'linear',
769             'direction': 'inverse'
770         }
771
772         #
773         # Generate the forward transform
774         #
775         cs.fromReferenceTransforms = []
776
777         if 'transformCTL' in lmtValues:
778             ctls = [
779                 shaperToACESCTL % acesCTLReleaseDir,
780                 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
781             ]
782             lut = "%s.%s.spi3d" % (shaperName, lmtName)
783
784             # Remove spaces and parentheses
785             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
786
787             genlut.generate3dLUTFromCTL(
788                 lutDir + "/" + lut,
789                 ctls,
790                 lutResolution3d,
791                 'float',
792                 1.0 / shaperInputScale,
793                 1.0,
794                 shaperParams,
795                 cleanup,
796                 acesCTLReleaseDir)
797
798             cs.fromReferenceTransforms.append(shaperOCIOTransform)
799             cs.fromReferenceTransforms.append({
800                 'type': 'lutFile',
801                 'path': lut,
802                 'interpolation': 'tetrahedral',
803                 'direction': 'forward'
804             })
805
806         #
807         # Generate the inverse transform
808         #
809         cs.toReferenceTransforms = []
810
811         if 'transformCTLInverse' in lmtValues:
812             ctls = [
813                 '%s/%s' % (
814                     acesCTLReleaseDir, odtValues['transformCTLInverse']),
815                 shaperFromACESCTL % acesCTLReleaseDir
816             ]
817             lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
818
819             # Remove spaces and parentheses
820             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
821
822             genlut.generate3dLUTFromCTL(
823                 lutDir + "/" + lut,
824                 ctls,
825                 lutResolution3d,
826                 'half',
827                 1.0,
828                 shaperInputScale,
829                 shaperParams,
830                 cleanup,
831                 acesCTLReleaseDir)
832
833             cs.toReferenceTransforms.append({
834                 'type': 'lutFile',
835                 'path': lut,
836                 'interpolation': 'tetrahedral',
837                 'direction': 'forward'
838             })
839
840             shaperInverse = shaperOCIOTransform.copy()
841             shaperInverse['direction'] = 'forward'
842             cs.toReferenceTransforms.append(shaperInverse)
843
844         return cs
845
846     #
847     # LMT Shaper
848     #
849
850     lmtLutResolution1d = max(4096, lutResolution1d)
851     lmtLutResolution3d = max(65, lutResolution3d)
852
853     # Log 2 shaper
854     lmtShaperName = 'LMT Shaper'
855     lmtParams = {
856         'middleGrey': 0.18,
857         'minExposure': -10.0,
858         'maxExposure': 6.5
859     }
860     lmtShaper = createGenericLog(name=lmtShaperName,
861                                  middleGrey=lmtParams['middleGrey'],
862                                  minExposure=lmtParams['minExposure'],
863                                  maxExposure=lmtParams['maxExposure'],
864                                  lutResolution1d=lmtLutResolution1d)
865     configData['colorSpaces'].append(lmtShaper)
866
867     shaperInputScale_genericLog2 = 1.0
868
869     # Log 2 shaper name and CTL transforms bundled up
870     lmtShaperData = [
871         lmtShaperName,
872         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
873         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
874         shaperInputScale_genericLog2,
875         lmtParams
876     ]
877
878     sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
879     print(sortedLMTs)
880     for lmt in sortedLMTs:
881         (lmtName, lmtValues) = lmt
882         cs = createACESLMT(
883             lmtValues['transformUserName'],
884             lmtValues,
885             lmtShaperData,
886             lmtLutResolution1d,
887             lmtLutResolution3d,
888             cleanup)
889         configData['colorSpaces'].append(cs)
890
891     #
892     # ACES RRT with the supplied ODT
893     #
894     def createACESRRTplusODT(odtName,
895                              odtValues,
896                              shaperInfo,
897                              lutResolution1d=1024,
898                              lutResolution3d=64,
899                              cleanup=True):
900         cs = ColorSpace("%s" % odtName)
901         cs.description = "%s - %s Output Transform" % (
902             odtValues['transformUserNamePrefix'], odtName)
903         cs.equalityGroup = ''
904         cs.family = 'Output'
905         cs.isData = False
906
907         import pprint
908
909         pprint.pprint(odtValues)
910
911         #
912         # Generate the shaper transform
913         #
914         # if 'shaperCTL' in odtValues:
915         (shaperName,
916          shaperToACESCTL,
917          shaperFromACESCTL,
918          shaperInputScale,
919          shaperParams) = shaperInfo
920
921         if 'legalRange' in odtValues:
922             shaperParams['legalRange'] = odtValues['legalRange']
923         else:
924             shaperParams['legalRange'] = 0
925
926         shaperLut = "%s_to_aces.spi1d" % shaperName
927         if (not os.path.exists(lutDir + "/" + shaperLut)):
928             ctls = [
929                 shaperToACESCTL % acesCTLReleaseDir
930             ]
931
932             # Remove spaces and parentheses
933             shaperLut = shaperLut.replace(
934                 ' ', '_').replace(')', '_').replace('(', '_')
935
936             genlut.generate1dLUTFromCTL(
937                 lutDir + "/" + shaperLut,
938                 ctls,
939                 lutResolution1d,
940                 'float',
941                 1.0 / shaperInputScale,
942                 1.0,
943                 shaperParams,
944                 cleanup,
945                 acesCTLReleaseDir)
946
947         shaperOCIOTransform = {
948             'type': 'lutFile',
949             'path': shaperLut,
950             'interpolation': 'linear',
951             'direction': 'inverse'
952         }
953
954         #
955         # Generate the forward transform
956         #
957         cs.fromReferenceTransforms = []
958
959         if 'transformLUT' in odtValues:
960             # Copy into the lut dir
961             transformLUTFileName = os.path.basename(odtValues['transformLUT'])
962             lut = lutDir + "/" + transformLUTFileName
963             shutil.copy(odtValues['transformLUT'], lut)
964
965             cs.fromReferenceTransforms.append(shaperOCIOTransform)
966             cs.fromReferenceTransforms.append({
967                 'type': 'lutFile',
968                 'path': transformLUTFileName,
969                 'interpolation': 'tetrahedral',
970                 'direction': 'forward'
971             })
972         elif 'transformCTL' in odtValues:
973             # shaperLut
974
975             ctls = [
976                 shaperToACESCTL % acesCTLReleaseDir,
977                 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
978                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
979             ]
980             lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
981
982             # Remove spaces and parentheses
983             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
984
985             genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
986                                         # shaperLUT,
987                                         ctls,
988                                         lutResolution3d,
989                                         'float',
990                                         1.0 / shaperInputScale,
991                                         1.0,
992                                         shaperParams,
993                                         cleanup,
994                                         acesCTLReleaseDir)
995
996             cs.fromReferenceTransforms.append(shaperOCIOTransform)
997             cs.fromReferenceTransforms.append({
998                 'type': 'lutFile',
999                 'path': lut,
1000                 'interpolation': 'tetrahedral',
1001                 'direction': 'forward'
1002             })
1003
1004         #
1005         # Generate the inverse transform
1006         #
1007         cs.toReferenceTransforms = []
1008
1009         if 'transformLUTInverse' in odtValues:
1010             # Copy into the lut dir
1011             transformLUTInverseFileName = os.path.basename(
1012                 odtValues['transformLUTInverse'])
1013             lut = lutDir + "/" + transformLUTInverseFileName
1014             shutil.copy(odtValues['transformLUTInverse'], lut)
1015
1016             cs.toReferenceTransforms.append({
1017                 'type': 'lutFile',
1018                 'path': transformLUTInverseFileName,
1019                 'interpolation': 'tetrahedral',
1020                 'direction': 'forward'
1021             })
1022
1023             shaperInverse = shaperOCIOTransform.copy()
1024             shaperInverse['direction'] = 'forward'
1025             cs.toReferenceTransforms.append(shaperInverse)
1026         elif 'transformCTLInverse' in odtValues:
1027             ctls = [
1028                 '%s/odt/%s' % (
1029                     acesCTLReleaseDir, odtValues['transformCTLInverse']),
1030                 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1031                 shaperFromACESCTL % acesCTLReleaseDir
1032             ]
1033             lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1034
1035             # Remove spaces and parentheses
1036             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1037
1038             genlut.generate3dLUTFromCTL(
1039                 lutDir + "/" + lut,
1040                 # None,
1041                 ctls,
1042                 lutResolution3d,
1043                 'half',
1044                 1.0,
1045                 shaperInputScale,
1046                 shaperParams,
1047                 cleanup,
1048                 acesCTLReleaseDir)
1049
1050             cs.toReferenceTransforms.append({
1051                 'type': 'lutFile',
1052                 'path': lut,
1053                 'interpolation': 'tetrahedral',
1054                 'direction': 'forward'
1055             })
1056
1057             shaperInverse = shaperOCIOTransform.copy()
1058             shaperInverse['direction'] = 'forward'
1059             cs.toReferenceTransforms.append(shaperInverse)
1060
1061         return cs
1062
1063     #
1064     # RRT/ODT shaper options
1065     #
1066     shaperData = {}
1067
1068     # Log 2 shaper
1069     log2ShaperName = shaperName
1070     log2Params = {
1071         'middleGrey': 0.18,
1072         'minExposure': -6.0,
1073         'maxExposure': 6.5
1074     }
1075     log2Shaper = createGenericLog(name=log2ShaperName,
1076                                   middleGrey=log2Params['middleGrey'],
1077                                   minExposure=log2Params['minExposure'],
1078                                   maxExposure=log2Params['maxExposure'])
1079     configData['colorSpaces'].append(log2Shaper)
1080
1081     shaperInputScale_genericLog2 = 1.0
1082
1083     # Log 2 shaper name and CTL transforms bundled up
1084     log2ShaperData = [
1085         log2ShaperName,
1086         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1087         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1088         shaperInputScale_genericLog2,
1089         log2Params
1090     ]
1091
1092     shaperData[log2ShaperName] = log2ShaperData
1093
1094     #
1095     # Shaper that also includes the AP1 primaries
1096     # - Needed for some LUT baking steps
1097     #
1098     log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1099                                      middleGrey=log2Params['middleGrey'],
1100                                      minExposure=log2Params['minExposure'],
1101                                      maxExposure=log2Params['maxExposure'])
1102     log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1103     # AP1 primaries to AP0 primaries
1104     log2ShaperAP1.toReferenceTransforms.append({
1105         'type': 'matrix',
1106         'matrix': mat44FromMat33(acesAP1toAP0),
1107         'direction': 'forward'
1108     })
1109     configData['colorSpaces'].append(log2ShaperAP1)
1110
1111     #
1112     # Choose your shaper
1113     #
1114     rrtShaperName = log2ShaperName
1115     rrtShaper = log2ShaperData
1116
1117     #
1118     # RRT + ODT Combinations
1119     #
1120     sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1121     print(sortedOdts)
1122     for odt in sortedOdts:
1123         (odtName, odtValues) = odt
1124
1125         # Have to handle ODTs that can generate either legal or full output
1126         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1127                        'Academy.Rec709_100nits_dim.a1.0.0',
1128                        'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1129             odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1130         else:
1131             odtNameLegal = odtValues['transformUserName']
1132
1133         odtLegal = odtValues.copy()
1134         odtLegal['legalRange'] = 1
1135
1136         cs = createACESRRTplusODT(
1137             odtNameLegal,
1138             odtLegal,
1139             rrtShaper,
1140             lutResolution1d,
1141             lutResolution3d,
1142             cleanup)
1143         configData['colorSpaces'].append(cs)
1144
1145         # Create a display entry using this color space
1146         configData['displays'][odtNameLegal] = {
1147             'Linear': ACES,
1148             'Log': ACEScc,
1149             'Output Transform': cs}
1150
1151         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1152                        'Academy.Rec709_100nits_dim.a1.0.0',
1153                        'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1154             print("Generating full range ODT for %s" % odtName)
1155
1156             odtNameFull = "%s - Full" % odtValues['transformUserName']
1157             odtFull = odtValues.copy()
1158             odtFull['legalRange'] = 0
1159
1160             csFull = createACESRRTplusODT(
1161                 odtNameFull,
1162                 odtFull,
1163                 rrtShaper,
1164                 lutResolution1d,
1165                 lutResolution3d,
1166                 cleanup)
1167             configData['colorSpaces'].append(csFull)
1168
1169             # Create a display entry using this color space
1170             configData['displays'][odtNameFull] = {
1171                 'Linear': ACES,
1172                 'Log': ACEScc,
1173                 'Output Transform': csFull}
1174
1175     #
1176     # Generic Matrix transform
1177     #
1178     def createGenericMatrix(name='matrix',
1179                             fromReferenceValues=[],
1180                             toReferenceValues=[]):
1181         cs = ColorSpace(name)
1182         cs.description = "The %s color space" % name
1183         cs.equalityGroup = name
1184         cs.family = 'Utility'
1185         cs.isData = False
1186
1187         cs.toReferenceTransforms = []
1188         if toReferenceValues != []:
1189             for matrix in toReferenceValues:
1190                 cs.toReferenceTransforms.append({
1191                     'type': 'matrix',
1192                     'matrix': mat44FromMat33(matrix),
1193                     'direction': 'forward'
1194                 })
1195
1196         cs.fromReferenceTransforms = []
1197         if fromReferenceValues != []:
1198             for matrix in fromReferenceValues:
1199                 cs.fromReferenceTransforms.append({
1200                     'type': 'matrix',
1201                     'matrix': mat44FromMat33(matrix),
1202                     'direction': 'forward'
1203                 })
1204
1205         return cs
1206
1207     cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1208     configData['colorSpaces'].append(cs)
1209
1210     cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1211     configData['colorSpaces'].append(cs)
1212
1213     # ACES to Linear, P3D60 primaries
1214     xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1215                   -0.8325796487, 1.7692317536, 0.0237127115,
1216                   0.0388233815, -0.0824996856, 1.0363685997]
1217
1218     cs = createGenericMatrix('Linear - P3-D60',
1219                              fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1220     configData['colorSpaces'].append(cs)
1221
1222     # ACES to Linear, P3D60 primaries
1223     xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1224                   -0.7951680258, 1.6897320548, 0.0226471906,
1225                   0.0412418914, -0.0876390192, 1.1009293786]
1226
1227     cs = createGenericMatrix('Linear - P3-DCI',
1228                              fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1229     configData['colorSpaces'].append(cs)
1230
1231     # ACES to Linear, Rec 709 primaries
1232     xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1233                    -0.9692436363, 1.8759675015, 0.0415550574,
1234                    0.0556300797, -0.2039769589, 1.0569715142]
1235
1236     cs = createGenericMatrix('Linear - Rec.709',
1237                              fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1238     configData['colorSpaces'].append(cs)
1239
1240     # ACES to Linear, Rec 2020 primaries
1241     xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1242                     -0.6666843518, 1.6164812366, 0.0157685458,
1243                     0.0176398574, -0.0427706133, 0.9421031212]
1244
1245     cs = createGenericMatrix('Linear - Rec.2020',
1246                              fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1247     configData['colorSpaces'].append(cs)
1248
1249     print("generateLUTs - end")
1250     return configData
1251
1252
1253 def generateBakedLUTs(odtInfo,
1254                       shaperName,
1255                       bakedDir,
1256                       configPath,
1257                       lutResolution1d,
1258                       lutResolution3d,
1259                       lutResolutionShaper=1024):
1260     # Add the legal and full variations into this list
1261     odtInfoC = dict(odtInfo)
1262     for odtCTLName, odtValues in odtInfo.iteritems():
1263         if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1264                           'Academy.Rec709_100nits_dim.a1.0.0',
1265                           'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1266             odtName = odtValues["transformUserName"]
1267
1268             odtValuesLegal = dict(odtValues)
1269             odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1270             odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1271
1272             odtValuesFull = dict(odtValues)
1273             odtValuesFull["transformUserName"] = "%s - Full" % odtName
1274             odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1275
1276             del (odtInfoC[odtCTLName])
1277
1278     for odtCTLName, odtValues in odtInfoC.iteritems():
1279         odtPrefix = odtValues["transformUserNamePrefix"]
1280         odtName = odtValues["transformUserName"]
1281
1282         # For Photoshop
1283         for inputspace in ["ACEScc", "ACESproxy"]:
1284             args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1285             args += ["--outputspace", "%s" % odtName]
1286             args += ["--description",
1287                      "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1288             args += ["--shaperspace", shaperName, "--shapersize",
1289                      str(lutResolutionShaper)]
1290             args += ["--cubesize", str(lutResolution3d)]
1291             args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1292                 bakedDir, odtName, inputspace)]
1293
1294             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1295                               args=args)
1296             bakeLUT.execute()
1297
1298             # For Flame, Lustre
1299         for inputspace in ["ACEScc", "ACESproxy"]:
1300             args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1301             args += ["--outputspace", "%s" % odtName]
1302             args += ["--description",
1303                      "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1304             args += ["--shaperspace", shaperName, "--shapersize",
1305                      str(lutResolutionShaper)]
1306             args += ["--cubesize", str(lutResolution3d)]
1307
1308             fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1309                 bakedDir, odtName, inputspace)]
1310             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1311                               args=(args + fargs))
1312             bakeLUT.execute()
1313
1314             largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1315                 bakedDir, odtName, inputspace)]
1316             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1317                               args=(args + largs))
1318             bakeLUT.execute()
1319
1320         # For Maya, Houdini
1321         for inputspace in ["ACEScg", "ACES2065-1"]:
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             if inputspace == 'ACEScg':
1327                 linShaperName = "%s - AP1" % shaperName
1328             else:
1329                 linShaperName = shaperName
1330             args += ["--shaperspace", linShaperName, "--shapersize",
1331                      str(lutResolutionShaper)]
1332
1333             args += ["--cubesize", str(lutResolution3d)]
1334
1335             margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1336                 bakedDir, odtName, inputspace)]
1337             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1338                               args=(args + margs))
1339             bakeLUT.execute()
1340
1341             hargs = ["--format", "houdini",
1342                      "%s/houdini/%s for %s Houdini.lut" % (
1343                          bakedDir, odtName, inputspace)]
1344             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1345                               args=(args + hargs))
1346             bakeLUT.execute()
1347
1348
1349 def createConfigDir(configDir, bakeSecondaryLUTs):
1350     dirs = [configDir, "%s/luts" % configDir]
1351     if bakeSecondaryLUTs:
1352         dirs.extend(["%s/baked" % configDir,
1353                      "%s/baked/flame" % configDir,
1354                      "%s/baked/photoshop" % configDir,
1355                      "%s/baked/houdini" % configDir,
1356                      "%s/baked/lustre" % configDir,
1357                      "%s/baked/maya" % configDir])
1358
1359     for d in dirs:
1360         if not os.path.exists(d):
1361             os.mkdir(d)
1362
1363
1364 def getTransformInfo(ctlTransform):
1365     fp = open(ctlTransform, 'rb')
1366
1367     # Read lines
1368     lines = fp.readlines()
1369
1370     # Grab transform ID and User Name
1371     transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1372     # print(transformID)
1373     transformUserName = '-'.join(
1374         lines[2][3:].split('<')[1].split('>')[1].split('-')[
1375         1:]).lstrip().rstrip()
1376     transformUserNamePrefix = \
1377         lines[2][3:].split('<')[1].split('>')[1].split('-')[
1378             0].lstrip().rstrip()
1379     # print(transformUserName)
1380     fp.close()
1381
1382     return transformID, transformUserName, transformUserNamePrefix
1383
1384
1385 # For versions after WGR9
1386 def getODTInfo(acesCTLReleaseDir):
1387     # Credit to Alex Fry for the original approach here
1388     odtDir = os.path.join(acesCTLReleaseDir, "odt")
1389     allodt = []
1390     for dirName, subdirList, fileList in os.walk(odtDir):
1391         for fname in fileList:
1392             allodt.append((os.path.join(dirName, fname)))
1393
1394     odtCTLs = [x for x in allodt if
1395                ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1396
1397     # print odtCTLs
1398
1399     odts = {}
1400
1401     for odtCTL in odtCTLs:
1402         odtTokens = os.path.split(odtCTL)
1403         # print(odtTokens)
1404
1405         # Handle nested directories
1406         odtPathTokens = os.path.split(odtTokens[-2])
1407         odtDir = odtPathTokens[-1]
1408         while odtPathTokens[-2][-3:] != 'odt':
1409             odtPathTokens = os.path.split(odtPathTokens[-2])
1410             odtDir = os.path.join(odtPathTokens[-1], odtDir)
1411
1412         # Build full name
1413         # print("odtDir : %s" % odtDir)
1414         transformCTL = odtTokens[-1]
1415         # print(transformCTL)
1416         odtName = string.join(transformCTL.split('.')[1:-1], '.')
1417         # print(odtName)
1418
1419         # Find id, user name and user name prefix
1420         (transformID, transformUserName,
1421          transformUserNamePrefix) = getTransformInfo(
1422             "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1423
1424         # Find inverse
1425         transformCTLInverse = "InvODT.%s.ctl" % odtName
1426         if not os.path.exists(
1427                 os.path.join(odtTokens[-2], transformCTLInverse)):
1428             transformCTLInverse = None
1429         # print(transformCTLInverse)
1430
1431         # Add to list of ODTs
1432         odts[odtName] = {}
1433         odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1434         if transformCTLInverse != None:
1435             odts[odtName]['transformCTLInverse'] = os.path.join(
1436                 odtDir, transformCTLInverse)
1437
1438         odts[odtName]['transformID'] = transformID
1439         odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1440         odts[odtName]['transformUserName'] = transformUserName
1441
1442         print("ODT : %s" % odtName)
1443         print("\tTransform ID               : %s" % transformID)
1444         print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1445         print("\tTransform User Name        : %s" % transformUserName)
1446         print("\tForward ctl                : %s" % (
1447             odts[odtName]['transformCTL']))
1448         if 'transformCTLInverse' in odts[odtName]:
1449             print("\tInverse ctl                : %s" % (
1450                 odts[odtName]['transformCTLInverse']))
1451         else:
1452             print("\tInverse ctl                : %s" % "None")
1453
1454     print("\n")
1455
1456     return odts
1457
1458
1459 # For versions after WGR9
1460 def getLMTInfo(acesCTLReleaseDir):
1461     # Credit to Alex Fry for the original approach here
1462     lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1463     alllmt = []
1464     for dirName, subdirList, fileList in os.walk(lmtDir):
1465         for fname in fileList:
1466             alllmt.append((os.path.join(dirName, fname)))
1467
1468     lmtCTLs = [x for x in alllmt if
1469                ("InvLMT" not in x) and ("README" not in x) and (
1470                    os.path.split(x)[-1][0] != '.')]
1471
1472     # print lmtCTLs
1473
1474     lmts = {}
1475
1476     for lmtCTL in lmtCTLs:
1477         lmtTokens = os.path.split(lmtCTL)
1478         # print(lmtTokens)
1479
1480         # Handle nested directories
1481         lmtPathTokens = os.path.split(lmtTokens[-2])
1482         lmtDir = lmtPathTokens[-1]
1483         while lmtPathTokens[-2][-3:] != 'ctl':
1484             lmtPathTokens = os.path.split(lmtPathTokens[-2])
1485             lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1486
1487         # Build full name
1488         # print("lmtDir : %s" % lmtDir)
1489         transformCTL = lmtTokens[-1]
1490         # print(transformCTL)
1491         lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1492         # print(lmtName)
1493
1494         # Find id, user name and user name prefix
1495         (transformID, transformUserName,
1496          transformUserNamePrefix) = getTransformInfo(
1497             "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1498
1499         # Find inverse
1500         transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1501         if not os.path.exists(
1502                 os.path.join(lmtTokens[-2], transformCTLInverse)):
1503             transformCTLInverse = None
1504         # print(transformCTLInverse)
1505
1506         # Add to list of LMTs
1507         lmts[lmtName] = {}
1508         lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1509         if transformCTLInverse != None:
1510             # TODO: Check unresolved *odtName* referemce.
1511             lmts[odtName]['transformCTLInverse'] = os.path.join(
1512                 lmtDir, transformCTLInverse)
1513
1514         lmts[lmtName]['transformID'] = transformID
1515         lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1516         lmts[lmtName]['transformUserName'] = transformUserName
1517
1518         print("LMT : %s" % lmtName)
1519         print("\tTransform ID               : %s" % transformID)
1520         print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1521         print("\tTransform User Name        : %s" % transformUserName)
1522         print("\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1523         if 'transformCTLInverse' in lmts[lmtName]:
1524             print("\t Inverse ctl : %s" % (
1525                 lmts[lmtName]['transformCTLInverse']))
1526         else:
1527             print("\t Inverse ctl : %s" % "None")
1528
1529     print("\n")
1530
1531     return lmts
1532
1533
1534 #
1535 # Create the ACES config
1536 #
1537 def createACESConfig(acesCTLReleaseDir,
1538                      configDir,
1539                      lutResolution1d=4096,
1540                      lutResolution3d=64,
1541                      bakeSecondaryLUTs=True,
1542                      cleanup=True):
1543     # Get ODT names and CTL paths
1544     odtInfo = getODTInfo(acesCTLReleaseDir)
1545
1546     # Get ODT names and CTL paths
1547     lmtInfo = getLMTInfo(acesCTLReleaseDir)
1548
1549     # Create config dir
1550     createConfigDir(configDir, bakeSecondaryLUTs)
1551
1552     # Generate config data and LUTs for different transforms
1553     lutDir = "%s/luts" % configDir
1554     shaperName = 'Output Shaper'
1555     configData = generateLUTs(odtInfo,
1556                               lmtInfo,
1557                               shaperName,
1558                               acesCTLReleaseDir,
1559                               lutDir,
1560                               lutResolution1d,
1561                               lutResolution3d,
1562                               cleanup)
1563
1564     # Create the config using the generated LUTs
1565     print("Creating generic config")
1566     config = createConfig(configData)
1567     print("\n\n\n")
1568
1569     # Write the config to disk
1570     writeConfig(config, "%s/config.ocio" % configDir)
1571
1572     # Create a config that will work well with Nuke using the previously
1573     # generated LUTs.
1574     print("Creating Nuke-specific config")
1575     nuke_config = createConfig(configData, nuke=True)
1576     print("\n\n\n")
1577
1578     # Write the config to disk
1579     writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1580
1581     # Bake secondary LUTs using the config
1582     if bakeSecondaryLUTs:
1583         generateBakedLUTs(odtInfo,
1584                           shaperName,
1585                           "%s/baked" % configDir,
1586                           "%s/config.ocio" % configDir,
1587                           lutResolution1d,
1588                           lutResolution3d,
1589                           lutResolution1d)
1590
1591     return True
1592
1593
1594 #
1595 # Main
1596 #
1597 def main():
1598     import optparse
1599
1600     p = optparse.OptionParser(description='An OCIO config generation script',
1601                               prog='createACESConfig',
1602                               version='createACESConfig 0.1',
1603                               usage='%prog [options]')
1604     p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1605         'ACES_OCIO_CTL_DIRECTORY', None))
1606     p.add_option('--configDir', '-c', default=os.environ.get(
1607         'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1608     p.add_option('--lutResolution1d', default=4096)
1609     p.add_option('--lutResolution3d', default=64)
1610     p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1611     p.add_option('--keepTempImages', action="store_true")
1612
1613     options, arguments = p.parse_args()
1614
1615     #
1616     # Get options
1617     # 
1618     acesCTLDir = options.acesCTLDir
1619     configDir = options.configDir
1620     lutResolution1d = int(options.lutResolution1d)
1621     lutResolution3d = int(options.lutResolution3d)
1622     bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1623     cleanupTempImages = not (options.keepTempImages)
1624
1625     try:
1626         argsStart = sys.argv.index('--') + 1
1627         args = sys.argv[argsStart:]
1628     except:
1629         argsStart = len(sys.argv) + 1
1630         args = []
1631
1632     print("command line : \n%s\n" % " ".join(sys.argv))
1633
1634     # TODO: Use assertion and mention environment variables.
1635     if not acesCTLDir:
1636         print("process: No ACES CTL directory specified")
1637         return
1638     if not configDir:
1639         print("process: No configuration directory specified")
1640         return
1641     #
1642     # Generate the configuration
1643     #
1644     return createACESConfig(acesCTLDir,
1645                             configDir,
1646                             lutResolution1d,
1647                             lutResolution3d,
1648                             bakeSecondaryLUTs,
1649                             cleanupTempImages)
1650
1651 # main
1652
1653 if __name__ == '__main__':
1654     main()