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