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