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