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