f61002ed7275c9de7c8369fd6670617ecefde66d
[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 import generateLUT as genlut
53
54 #
55 # Utility functions
56 #
57 def setConfigDefaultRoles( config, 
58     color_picking="",
59     color_timing="",
60     compositing_log="",
61     data="",
62     default="",
63     matte_paint="",
64     reference="",
65     scene_linear="",
66     texture_paint=""):
67     
68     # Add Roles
69     if color_picking: config.setRole( OCIO.Constants.ROLE_COLOR_PICKING, color_picking )
70     if color_timing: config.setRole( OCIO.Constants.ROLE_COLOR_TIMING, color_timing )
71     if compositing_log: config.setRole( OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log )
72     if data: config.setRole( OCIO.Constants.ROLE_DATA, data )
73     if default: config.setRole( OCIO.Constants.ROLE_DEFAULT, default )
74     if matte_paint: config.setRole( OCIO.Constants.ROLE_MATTE_PAINT, matte_paint )
75     if reference: config.setRole( OCIO.Constants.ROLE_REFERENCE, reference )
76     if scene_linear: config.setRole( OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear )
77     if texture_paint: config.setRole( OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint )
78
79 # Write config to disk
80 def writeConfig( config, configPath, sanityCheck=True ):
81     if sanityCheck:
82         try:
83             config.sanityCheck()
84         except Exception,e:
85             print e
86             print "Configuration was not written due to a failed Sanity Check"
87             return
88             #sys.exit()
89
90     fileHandle = open( configPath, mode='w' )
91     fileHandle.write( config.serialize() )
92     fileHandle.close()
93
94 def generateOCIOTransform(transforms):
95     #print( "Generating transforms")
96
97     interpolationOptions = { 
98         'linear':OCIO.Constants.INTERP_LINEAR,
99         'nearest':OCIO.Constants.INTERP_NEAREST, 
100         'tetrahedral':OCIO.Constants.INTERP_TETRAHEDRAL 
101     }
102     directionOptions = { 
103         'forward':OCIO.Constants.TRANSFORM_DIR_FORWARD,
104         'inverse':OCIO.Constants.TRANSFORM_DIR_INVERSE 
105     }
106
107     ocioTransforms = []
108
109     for transform in transforms:
110         if transform['type'] == 'lutFile':
111
112             ocioTransform = OCIO.FileTransform( src=transform['path'],
113                 interpolation=interpolationOptions[transform['interpolation']],
114                 direction=directionOptions[transform['direction']] )
115
116             ocioTransforms.append(ocioTransform)
117         elif transform['type'] == 'matrix':
118             ocioTransform = OCIO.MatrixTransform()
119             # MatrixTransform member variables can't be initialized directly. Each must be set individually
120             ocioTransform.setMatrix( transform['matrix'] )
121
122             if 'offset' in transform:
123                 ocioTransform.setOffset( transform['offset'] )
124             if 'direction' in transform:
125                 ocioTransform.setDirection( directionOptions[transform['direction']] )
126
127             ocioTransforms.append(ocioTransform)
128         elif transform['type'] == 'exponent':
129             ocioTransform = OCIO.ExponentTransform()
130             ocioTransform.setValue( transform['value'] )
131
132             ocioTransforms.append(ocioTransform)
133         elif transform['type'] == 'log':
134             ocioTransform = OCIO.LogTransform(base=transform['base'],
135                 direction=directionOptions[transform['direction']])
136
137             ocioTransforms.append(ocioTransform)
138         else:
139             print( "Ignoring unknown transform type : %s" % transform['type'] )
140
141     # Build a group transform if necessary
142     if len(ocioTransforms) > 1:
143         transformG = OCIO.GroupTransform()
144         for transform in ocioTransforms:
145             transformG.push_back( transform )
146         transform = transformG
147
148     # Or take the first transform from the list
149     else:
150         transform = ocioTransforms[0]
151
152     return transform
153
154 def createConfig(configData, nuke=False):
155     # Create the config
156     config = OCIO.Config()
157     
158     #
159     # Set config wide values
160     #
161     config.setDescription( "An ACES config generated from python" )
162     config.setSearchPath( "luts" )
163     
164     #
165     # Define the reference color space
166     #
167     referenceData = configData['referenceColorSpace']
168     print( "Adding the reference color space : %s" % referenceData.name)
169
170     # Create a color space
171     reference = OCIO.ColorSpace( name=referenceData.name, 
172         bitDepth=referenceData.bitDepth, 
173         description=referenceData.description, 
174         equalityGroup=referenceData.equalityGroup, 
175         family=referenceData.family, 
176         isData=referenceData.isData, 
177         allocation=referenceData.allocationType, 
178         allocationVars=referenceData.allocationVars ) 
179
180     # Add to config
181     config.addColorSpace( reference )
182
183     #
184     # Create the rest of the color spaces
185     #
186     #sortedColorspaces = sorted(configData['colorSpaces'], key=lambda x: x.name)
187     #print( sortedColorspaces )
188     #for colorspace in sortedColorspaces:
189     for colorspace in sorted(configData['colorSpaces']):
190         print( "Creating new color space : %s" % colorspace.name)
191
192         ocioColorspace = OCIO.ColorSpace( name=colorspace.name, 
193             bitDepth=colorspace.bitDepth, 
194             description=colorspace.description, 
195             equalityGroup=colorspace.equalityGroup, 
196             family=colorspace.family, 
197             isData=colorspace.isData,
198             allocation=colorspace.allocationType, 
199             allocationVars=colorspace.allocationVars ) 
200
201         if colorspace.toReferenceTransforms != []:
202             print( "Generating To-Reference transforms")
203             ocioTransform = generateOCIOTransform(colorspace.toReferenceTransforms)
204             ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE )
205
206         if colorspace.fromReferenceTransforms != []:
207             print( "Generating From-Reference transforms")
208             ocioTransform = generateOCIOTransform(colorspace.fromReferenceTransforms)
209             ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE )
210
211         config.addColorSpace(ocioColorspace)
212
213         print( "" )
214
215     #
216     # Define the views and displays
217     #
218     displays = []
219     views = []
220
221     # Generic display and view setup
222     if not nuke:
223         for display, viewList in configData['displays'].iteritems():
224             for viewName, colorspace in viewList.iteritems():
225                 config.addDisplay( display, viewName, colorspace.name )
226                 if not (viewName in views):
227                     views.append(viewName)
228             displays.append(display)
229     # A Nuke specific set of views and displays
230     #
231     # XXX
232     # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
233     #
234     else:
235         for display, viewList in configData['displays'].iteritems():
236             for viewName, colorspace in viewList.iteritems():
237                 if( viewName == 'Output Transform'):
238                     viewName = 'View'
239                     config.addDisplay( display, viewName, colorspace.name )
240                     if not (viewName in views):
241                         views.append(viewName)
242             displays.append(display)
243
244         config.addDisplay( 'linear', 'View', 'ACES2065-1' )
245         displays.append('linear')
246         config.addDisplay( 'log', 'View', 'ACEScc' )
247         displays.append('log')
248
249     # Set active displays and views
250     config.setActiveDisplays( ','.join(sorted(displays)) )
251     config.setActiveViews( ','.join(views) )
252
253     #
254     # Need to generalize this at some point
255     #
256
257     # Add Default Roles
258     setConfigDefaultRoles( config, 
259         color_picking=reference.getName(),
260         color_timing=reference.getName(),
261         compositing_log=reference.getName(),
262         data=reference.getName(),
263         default=reference.getName(),
264         matte_paint=reference.getName(),
265         reference=reference.getName(),
266         scene_linear=reference.getName(),
267         texture_paint=reference.getName() )
268
269     # Check to make sure we didn't screw something up
270     config.sanityCheck()
271
272     return config
273
274 #
275 # Functions to generate color space definitions and LUTs for transforms for a specific ACES release
276 #
277 class ColorSpace:
278     "A container for data needed to define an OCIO 'Color Space' "
279
280     def __init__(self, 
281         name, 
282         description=None, 
283         bitDepth=OCIO.Constants.BIT_DEPTH_F32,
284         equalityGroup=None,
285         family=None,
286         isData=False,
287         toReferenceTransforms=[],
288         fromReferenceTransforms=[],
289         allocationType=OCIO.Constants.ALLOCATION_UNIFORM,
290         allocationVars=[0.0, 1.0]):
291         "Initialize the standard class variables"
292         self.name = name
293         self.bitDepth=bitDepth
294         self.description = description
295         self.equalityGroup=equalityGroup
296         self.family=family 
297         self.isData=isData
298         self.toReferenceTransforms=toReferenceTransforms
299         self.fromReferenceTransforms=fromReferenceTransforms
300         self.allocationType=allocationType
301         self.allocationVars=allocationVars
302
303 # Create a 4x4 matrix (list) based on a 3x3 matrix (list) input
304 def mat44FromMat33(mat33):
305     return [mat33[0], mat33[1], mat33[2], 0.0, 
306             mat33[3], mat33[4], mat33[5], 0.0, 
307             mat33[6], mat33[7], mat33[8], 0.0, 
308             0,0,0,1.0]
309
310
311 # Output is a list of colorspaces and transforms that convert between those
312 # colorspaces and reference color space, ACES
313 def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d=4096, lutResolution3d=64, cleanup=True):
314     print( "generateLUTs - begin" )
315     configData = {}
316
317     #
318     # Define the reference color space
319     #
320     ACES = ColorSpace('ACES2065-1')
321     ACES.description = "The Academy Color Encoding System reference color space"
322     ACES.equalityGroup = ''
323     ACES.family = 'ACES'
324     ACES.isData=False
325     ACES.allocationType=OCIO.Constants.ALLOCATION_LG2
326     ACES.allocationVars=[-15, 6]
327
328     configData['referenceColorSpace'] = ACES
329
330     #
331     # Define the displays
332     #
333     configData['displays'] = {}
334
335     #
336     # Define the other color spaces
337     #
338     configData['colorSpaces'] = []
339
340     # Matrix converting ACES AP1 primaries to AP0
341     acesAP1toAP0 = [ 0.6954522414, 0.1406786965, 0.1638690622,
342                      0.0447945634, 0.8596711185, 0.0955343182,
343                     -0.0055258826, 0.0040252103, 1.0015006723]
344
345     # Matrix converting ACES AP0 primaries to XYZ
346     acesAP0toXYZ = [0.9525523959,  0.0000000000,  0.0000936786,
347                     0.3439664498,  0.7281660966, -0.0721325464,
348                     0.0000000000,  0.0000000000,  1.0088251844]
349
350     #
351     # ACEScc
352     #
353     def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
354         cs = ColorSpace(name)
355         cs.description = "The %s color space" % name
356         cs.equalityGroup = ''
357         cs.family = 'ACES'
358         cs.isData=False
359
360         ctls = [
361             '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
362             # This transform gets back to the AP1 primaries
363             # Useful as the 1d LUT is only covering the transfer function
364             # The primaries switch is covered by the matrix below
365             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
366         ]
367         lut = "%s_to_ACES.spi1d" % name
368         genlut.generate1dLUTFromCTL( lutDir + "/" + lut, 
369             ctls, 
370             lutResolution1d, 
371             'float', 
372             inputScale,
373             1.0, 
374             {},
375             cleanup, 
376             acesCTLReleaseDir,
377             minValue,
378             maxValue)
379
380         cs.toReferenceTransforms = []
381         cs.toReferenceTransforms.append( {
382             'type':'lutFile', 
383             'path':lut, 
384             'interpolation':'linear', 
385             'direction':'forward'
386         } )
387
388         # AP1 primaries to AP0 primaries
389         cs.toReferenceTransforms.append( {
390             'type':'matrix',
391             'matrix':mat44FromMat33(acesAP1toAP0),
392             'direction':'forward'
393         })
394
395         cs.fromReferenceTransforms = []
396         return cs
397
398     ACEScc = createACEScc()
399     configData['colorSpaces'].append(ACEScc)
400
401     #
402     # ACESproxy
403     #
404     def createACESProxy(name='ACESproxy'):
405         cs = ColorSpace(name)
406         cs.description = "The %s color space" % name
407         cs.equalityGroup = ''
408         cs.family = 'ACES'
409         cs.isData=False
410
411         ctls = [
412             '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
413             # This transform gets back to the AP1 primaries
414             # Useful as the 1d LUT is only covering the transfer function
415             # The primaries switch is covered by the matrix below
416             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
417         ]
418         lut = "%s_to_aces.spi1d" % name
419         genlut.generate1dLUTFromCTL( lutDir + "/" + lut, 
420             ctls, 
421             lutResolution1d, 
422             'uint16', 
423             64.0,
424             1.0, 
425             {},
426             cleanup, 
427             acesCTLReleaseDir )
428
429         cs.toReferenceTransforms = []
430         cs.toReferenceTransforms.append( {
431             'type':'lutFile', 
432             'path':lut, 
433             'interpolation':'linear', 
434             'direction':'forward'
435         } )
436
437         # AP1 primaries to AP0 primaries
438         cs.toReferenceTransforms.append( {
439             'type':'matrix',
440             'matrix':mat44FromMat33(acesAP1toAP0),
441             'direction':'forward'
442         })
443
444
445         cs.fromReferenceTransforms = []
446         return cs
447
448     ACESproxy = createACESProxy()
449     configData['colorSpaces'].append(ACESproxy)
450
451     #
452     # ACEScg
453     #
454     def createACEScg(name='ACEScg'):
455         cs = ColorSpace(name)
456         cs.description = "The %s color space" % name
457         cs.equalityGroup = ''
458         cs.family = 'ACES'
459         cs.isData=False
460
461         cs.toReferenceTransforms = []
462
463         # AP1 primaries to AP0 primaries
464         cs.toReferenceTransforms.append( {
465             'type':'matrix',
466             'matrix':mat44FromMat33(acesAP1toAP0),
467             'direction':'forward'
468         })
469
470         cs.fromReferenceTransforms = []
471         return cs
472
473     ACEScg = createACEScg()
474     configData['colorSpaces'].append(ACEScg)
475
476     #
477     # ADX
478     #
479     def createADX(bitdepth=10, name='ADX'):
480         name = "%s%s" % (name, bitdepth)
481         cs = ColorSpace(name)
482         cs.description = "%s color space - used for film scans" % name
483         cs.equalityGroup = ''
484         cs.family = 'ADX'
485         cs.isData=False
486
487         if bitdepth == 10:
488             cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
489             adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
490                         0.0, 1023.0/500.0, 0.0, 0.0,
491                         0.0, 0.0, 1023.0/500.0, 0.0,
492                         0.0, 0.0, 0.0, 1.0]
493             offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
494         elif bitdepth == 16:
495             cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
496             adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
497                         0.0, 65535.0/8000.0, 0.0, 0.0,
498                         0.0, 0.0, 65535.0/8000.0, 0.0,
499                         0.0, 0.0, 0.0, 1.0]
500             offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
501
502         cs.toReferenceTransforms = []
503
504         # Convert from ADX to Channel-Dependent Density
505         cs.toReferenceTransforms.append( {
506             'type':'matrix',
507             'matrix':adx_to_cdd,
508             'offset':offset,
509             'direction':'forward'
510         })
511
512         # Convert from Channel-Dependent Density to Channel-Independent Density
513         cs.toReferenceTransforms.append( {
514             'type':'matrix',
515             'matrix':[0.75573, 0.22197, 0.02230, 0,
516                         0.05901, 0.96928, -0.02829, 0,
517                         0.16134, 0.07406, 0.76460, 0,
518                         0.0, 0.0, 0.0, 1.0],
519             'direction':'forward'
520         })
521
522         # Copied from Alex Fry's adx_cid_to_rle.py
523         def createCIDtoRLELUT():
524             def interpolate1D(x, xp, fp):
525                 return numpy.interp(x, xp, fp)
526
527             LUT_1D_xp = [-0.190000000000000, 
528                           0.010000000000000,
529                           0.028000000000000,
530                           0.054000000000000,
531                           0.095000000000000,
532                           0.145000000000000,
533                           0.220000000000000,
534                           0.300000000000000,
535                           0.400000000000000,
536                           0.500000000000000,
537                           0.600000000000000]
538
539             LUT_1D_fp = [-6.000000000000000, 
540                          -2.721718645000000,
541                          -2.521718645000000,
542                          -2.321718645000000,
543                          -2.121718645000000,
544                          -1.921718645000000,
545                          -1.721718645000000,
546                          -1.521718645000000,
547                          -1.321718645000000,
548                          -1.121718645000000,
549                          -0.926545676714876]
550
551             REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
552
553             def cid_to_rle(x):
554                 if x <= 0.6:
555                     return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
556                 return (100.0 / 55.0) * x - REF_PT
557
558             def Fit(value, fromMin, fromMax, toMin, toMax):
559                 if fromMin == fromMax:
560                     raise ValueError("fromMin == fromMax")
561                 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
562
563             NUM_SAMPLES = 2**12
564             RANGE = (-0.19, 3.0)
565             data = []
566             for i in xrange(NUM_SAMPLES):
567                 x = i/(NUM_SAMPLES-1.0)
568                 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
569                 data.append(cid_to_rle(x))
570
571             lut = 'ADX_CID_to_RLE.spi1d'
572             genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
573
574             return lut
575
576         # Convert Channel Independent Density values to Relative Log Exposure values
577         lut = createCIDtoRLELUT()
578         cs.toReferenceTransforms.append( {
579             'type':'lutFile', 
580             'path':lut, 
581             'interpolation':'linear', 
582             'direction':'forward'
583         })
584
585         # Convert Relative Log Exposure values to Relative Exposure values
586         cs.toReferenceTransforms.append( {
587             'type':'log', 
588             'base':10, 
589             'direction':'inverse'
590         })
591
592         # Convert Relative Exposure values to ACES values
593         cs.toReferenceTransforms.append( {
594             'type':'matrix',
595             'matrix':[0.72286, 0.12630, 0.15084, 0,
596                         0.11923, 0.76418, 0.11659, 0,
597                         0.01427, 0.08213, 0.90359, 0,
598                         0.0, 0.0, 0.0, 1.0],
599             'direction':'forward'
600         })
601
602         cs.fromReferenceTransforms = []
603         return cs
604
605     ADX10 = createADX(bitdepth=10)
606     configData['colorSpaces'].append(ADX10)
607
608     ADX16 = createADX(bitdepth=16)
609     configData['colorSpaces'].append(ADX16)
610
611
612     #
613     # REDlogFilm to ACES
614     #
615     def createREDlogFilm(gamut, transferFunction, name='REDlogFilm'):
616         name = "%s - %s" % (transferFunction, gamut)
617         if transferFunction == "":
618             name = "Linear - %s" % gamut
619         if gamut == "":
620             name = "%s" % transferFunction
621
622         cs = ColorSpace(name)
623         cs.description = name
624         cs.equalityGroup = ''
625         cs.family = 'RED'
626         cs.isData=False
627
628         def cineonToLinear(codeValue):
629             nGamma = 0.6
630             blackPoint = 95.0
631             whitePoint = 685.0
632             codeValueToDensity = 0.002
633
634             blackLinear = pow(10.0, (blackPoint - whitePoint) * (codeValueToDensity / nGamma))
635             codeLinear = pow(10.0, (codeValue - whitePoint) * (codeValueToDensity / nGamma))
636
637             return (codeLinear - blackLinear)/(1.0 - blackLinear)
638
639         cs.toReferenceTransforms = []
640
641         if transferFunction == 'REDlogFilm':
642             data = array.array('f', "\0" * lutResolution1d * 4)
643             for c in range(lutResolution1d):
644                 data[c] = cineonToLinear(1023.0*c/(lutResolution1d-1))
645
646             lut = "CineonLog_to_linear.spi1d"
647             genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
648
649             cs.toReferenceTransforms.append( {
650                 'type':'lutFile', 
651                 'path':lut, 
652                 'interpolation':'linear', 
653                 'direction':'forward'
654             } )
655
656         if gamut == 'DRAGONcolor':
657             cs.toReferenceTransforms.append( {
658                 'type':'matrix',
659                 'matrix':mat44FromMat33([0.532279,  0.376648,  0.091073, 
660                                          0.046344,  0.974513, -0.020860, 
661                                         -0.053976, -0.000320, 1.054267]),
662                 'direction':'forward'
663             })
664         elif gamut == 'DRAGONcolor2':
665             cs.toReferenceTransforms.append( {
666                 'type':'matrix',
667                 'matrix':mat44FromMat33([0.468452,  0.331484,  0.200064, 
668                                          0.040787,  0.857658,  0.101553, 
669                                         -0.047504, -0.000282, 1.047756]),
670                 'direction':'forward'
671             })
672         elif gamut == 'REDcolor2':
673             cs.toReferenceTransforms.append( {
674                 'type':'matrix',
675                 'matrix':mat44FromMat33([0.480997, 0.402289, 0.116714, 
676                                         -0.004938, 1.000154, 0.004781, 
677                                         -0.105257, 0.025320, 1.079907]),
678                 'direction':'forward'
679             })
680         elif gamut == 'REDcolor3':
681             cs.toReferenceTransforms.append( {
682                 'type':'matrix',
683                 'matrix':mat44FromMat33([0.512136, 0.360370, 0.127494, 
684                                          0.070377, 0.903884, 0.025737, 
685                                         -0.020824, 0.017671, 1.003123]),
686                 'direction':'forward'
687             })
688         elif gamut == 'REDcolor4':
689             cs.toReferenceTransforms.append( {
690                 'type':'matrix',
691                 'matrix':mat44FromMat33([0.474202, 0.333677, 0.192121, 
692                                          0.065164, 0.836932, 0.097901, 
693                                         -0.019281, 0.016362, 1.002889]),
694                 'direction':'forward'
695             })
696
697         cs.fromReferenceTransforms = []
698         return cs
699
700     # Full conversion
701     REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "REDlogFilm", name="REDlogFilm")
702     configData['colorSpaces'].append(REDlogFilmDRAGON)
703
704     REDlogFilmDRAGON2 = createREDlogFilm("DRAGONcolor2", "REDlogFilm", name="REDlogFilm")
705     configData['colorSpaces'].append(REDlogFilmDRAGON2)
706
707     REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "REDlogFilm", name="REDlogFilm")
708     configData['colorSpaces'].append(REDlogFilmREDcolor2)
709
710     REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "REDlogFilm", name="REDlogFilm")
711     configData['colorSpaces'].append(REDlogFilmREDcolor3)
712
713     REDlogFilmREDcolor4 = createREDlogFilm("REDcolor4", "REDlogFilm", name="REDlogFilm")
714     configData['colorSpaces'].append(REDlogFilmREDcolor4)
715
716     # Linearization only
717     REDlogFilmDRAGON = createREDlogFilm("", "REDlogFilm", name="REDlogFilm")
718     configData['colorSpaces'].append(REDlogFilmDRAGON)
719
720     # Primaries only
721     REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "", name="REDlogFilm")
722     configData['colorSpaces'].append(REDlogFilmDRAGON)
723
724     REDlogFilmDRAGON2 = createREDlogFilm("DRAGONcolor2", "", name="REDlogFilm")
725     configData['colorSpaces'].append(REDlogFilmDRAGON2)
726
727     REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "", name="REDlogFilm")
728     configData['colorSpaces'].append(REDlogFilmREDcolor2)
729
730     REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "", name="REDlogFilm")
731     configData['colorSpaces'].append(REDlogFilmREDcolor3)
732
733     REDlogFilmREDcolor4 = createREDlogFilm("REDcolor4", "", name="REDlogFilm")
734     configData['colorSpaces'].append(REDlogFilmREDcolor4)
735
736     #
737     # Canon-Log to ACES
738     #
739     def createCanonLog(gamut, transferFunction, name='Canon-Log'):
740         name = "%s - %s" % (transferFunction, gamut)
741         if transferFunction == "":
742             name = "Linear - %s" % gamut
743         if gamut == "":
744             name = "%s" % transferFunction
745
746         cs = ColorSpace(name)
747         cs.description = name
748         cs.equalityGroup = ''
749         cs.family = 'Canon'
750         cs.isData=False
751
752         def legalToFull(codeValue):
753             return (codeValue - 64.0)/(940.0 - 64.0)
754
755         def canonLogToLinear(codeValue):
756             # log = fullToLegal(c1 * log10(c2*linear + 1) + c3)
757             # linear = (pow(10, (legalToFul(log) - c3)/c1) - 1)/c2
758             c1 = 0.529136
759             c2 = 10.1596
760             c3 = 0.0730597
761
762             linear = (pow(10.0, (legalToFull(codeValue) - c3)/c1) -1.0)/c2
763             linear = 0.9 * linear
764             #print( codeValue, linear )
765             return linear
766
767         cs.toReferenceTransforms = []
768
769         if transferFunction == "Canon-Log":
770             data = array.array('f', "\0" * lutResolution1d * 4)
771             for c in range(lutResolution1d):
772                 data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1))
773
774             lut = "%s_to_linear.spi1d" % transferFunction
775             genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
776
777             cs.toReferenceTransforms.append( {
778                 'type':'lutFile', 
779                 'path':lut, 
780                 'interpolation':'linear', 
781                 'direction':'forward'
782             } )
783
784         if gamut == 'Rec. 709 Daylight':
785             cs.toReferenceTransforms.append( {
786                 'type':'matrix',
787                 'matrix':[0.561538969, 0.402060105, 0.036400926, 0.0, 
788                             0.092739623, 0.924121198, -0.016860821, 0.0, 
789                             0.084812961, 0.006373835, 0.908813204, 0.0, 
790                             0,0,0,1.0],
791                 'direction':'forward'
792             })
793         elif gamut == 'Rec. 709 Tungsten':
794             cs.toReferenceTransforms.append( {
795                 'type':'matrix',
796                 'matrix':[0.566996399, 0.365079418, 0.067924183, 0.0, 
797                             0.070901044, 0.880331008, 0.048767948, 0.0, 
798                             0.073013542, -0.066540862, 0.99352732, 0.0, 
799                             0,0,0,1.0],
800                 'direction':'forward'
801             })
802         elif gamut == 'DCI-P3 Daylight':
803             cs.toReferenceTransforms.append( {
804                 'type':'matrix',
805                 'matrix':[0.607160575, 0.299507286, 0.093332140, 0.0, 
806                             0.004968120, 1.050982224, -0.055950343, 0.0, 
807                             -0.007839939, 0.000809127, 1.007030813, 0.0, 
808                             0,0,0,1.0],
809                 'direction':'forward'
810             })
811         elif gamut == 'DCI-P3 Tungsten':
812             cs.toReferenceTransforms.append( {
813                 'type':'matrix',
814                 'matrix':[0.650279125, 0.253880169, 0.095840706, 0.0, 
815                             -0.026137986, 1.017900530, 0.008237456, 0.0, 
816                             0.007757558, -0.063081669, 1.055324110, 0.0, 
817                             0,0,0,1.0],
818                 'direction':'forward'
819             })
820         elif gamut == 'Cinema Gamut Daylight':
821             cs.toReferenceTransforms.append( {
822                 'type':'matrix',
823                 'matrix':[0.763064455, 0.149021161, 0.087914384, 0.0, 
824                             0.003657457, 1.10696038, -0.110617837, 0.0, 
825                             -0.009407794,-0.218383305, 1.227791099, 0.0, 
826                             0,0,0,1.0],
827                 'direction':'forward'
828             })
829         elif gamut == 'Cinema Gamut Tungsten':
830             cs.toReferenceTransforms.append( {
831                 'type':'matrix',
832                 'matrix':[0.817416293, 0.090755698, 0.091828009, 0.0, 
833                             -0.035361374, 1.065690585, -0.030329211, 0.0, 
834                             0.010390366, -0.299271107, 1.288880741, 0.0, 
835                             0,0,0,1.0],
836                 'direction':'forward'
837             })
838
839         cs.fromReferenceTransforms = []
840         return cs
841
842     # Full conversion
843     CanonLog1 = createCanonLog("Rec. 709 Daylight", "Canon-Log", name="Canon-Log")
844     configData['colorSpaces'].append(CanonLog1)
845
846     CanonLog2 = createCanonLog("Rec. 709 Tungsten", "Canon-Log", name="Canon-Log")
847     configData['colorSpaces'].append(CanonLog2)
848
849     CanonLog3 = createCanonLog("DCI-P3 Daylight", "Canon-Log", name="Canon-Log")
850     configData['colorSpaces'].append(CanonLog3)
851
852     CanonLog4 = createCanonLog("DCI-P3 Tungsten", "Canon-Log", name="Canon-Log")
853     configData['colorSpaces'].append(CanonLog4)
854
855     CanonLog5 = createCanonLog("Cinema Gamut Daylight", "Canon-Log", name="Canon-Log")
856     configData['colorSpaces'].append(CanonLog5)
857
858     CanonLog6 = createCanonLog("Cinema Gamut Tungsten", "Canon-Log", name="Canon-Log")
859     configData['colorSpaces'].append(CanonLog6)
860
861     # Linearization only
862     CanonLog7 = createCanonLog('', "Canon-Log", name="Canon-Log")
863     configData['colorSpaces'].append(CanonLog7)
864
865     # Primaries only
866     CanonLog8 = createCanonLog("Rec. 709 Daylight", "", name="Canon-Log")
867     configData['colorSpaces'].append(CanonLog8)
868
869     CanonLog9 = createCanonLog("Rec. 709 Tungsten", "", name="Canon-Log")
870     configData['colorSpaces'].append(CanonLog9)
871
872     CanonLog10 = createCanonLog("DCI-P3 Daylight", "", name="Canon-Log")
873     configData['colorSpaces'].append(CanonLog10)
874
875     CanonLog11 = createCanonLog("DCI-P3 Tungsten", "", name="Canon-Log")
876     configData['colorSpaces'].append(CanonLog11)
877
878     CanonLog12 = createCanonLog("Cinema Gamut Daylight", "", name="Canon-Log")
879     configData['colorSpaces'].append(CanonLog12)
880
881     CanonLog13 = createCanonLog("Cinema Gamut Tungsten", "", name="Canon-Log")
882     configData['colorSpaces'].append(CanonLog13)
883
884     #
885     # SLog to ACES
886     #
887     def createSlog(gamut, transferFunction, name='S-Log3'):
888         name = "%s - %s" % (transferFunction, gamut)
889         if transferFunction == "":
890             name = "Linear - %s" % gamut
891         if gamut == "":
892             name = "%s" % transferFunction
893
894         cs = ColorSpace(name)
895         cs.description = name
896         cs.equalityGroup = ''
897         cs.family = 'Sony'
898         cs.isData=False
899
900         def sLog1ToLinear(SLog):
901             b = 64.
902             ab = 90.
903             w = 940.
904
905             if (SLog >= ab):
906                 lin = ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) * 0.9
907             else:
908                 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 5.) * 0.9 
909             return lin
910
911         def sLog2ToLinear(SLog):
912             b = 64.
913             ab = 90.
914             w = 940.
915
916             if (SLog >= ab):
917                 lin = ( 219. * ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) / 155.) * 0.9
918             else:
919                 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 3.53881278538813) * 0.9
920             return lin
921
922         def sLog3ToLinear(codeValue):
923             if codeValue >= (171.2102946929):
924                 linear = pow(10.0, ((codeValue - 420.0) / 261.5)) * (0.18 + 0.01) - 0.01
925             else:
926                 linear = (codeValue - 95.0)*0.01125000/(171.2102946929 - 95.0)
927             #print( codeValue, linear )
928             return linear
929
930         cs.toReferenceTransforms = []
931
932         if transferFunction == "S-Log1":
933             data = array.array('f', "\0" * lutResolution1d * 4)
934             for c in range(lutResolution1d):
935                 data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1))
936
937             lut = "%s_to_linear.spi1d" % transferFunction
938             genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
939
940             #print( "Writing %s" % lut)
941
942             cs.toReferenceTransforms.append( {
943                 'type':'lutFile', 
944                 'path':lut, 
945                 'interpolation':'linear', 
946                 'direction':'forward'
947             } )
948         elif transferFunction == "S-Log2":
949             data = array.array('f', "\0" * lutResolution1d * 4)
950             for c in range(lutResolution1d):
951                 data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1))
952
953             lut = "%s_to_linear.spi1d" % transferFunction
954             genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
955
956             #print( "Writing %s" % lut)
957
958             cs.toReferenceTransforms.append( {
959                 'type':'lutFile', 
960                 'path':lut, 
961                 'interpolation':'linear', 
962                 'direction':'forward'
963             } )
964         elif transferFunction == "S-Log3":
965             data = array.array('f', "\0" * lutResolution1d * 4)
966             for c in range(lutResolution1d):
967                 data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1))
968
969             lut = "%s_to_linear.spi1d" % transferFunction
970             genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
971
972             #print( "Writing %s" % lut)
973
974             cs.toReferenceTransforms.append( {
975                 'type':'lutFile', 
976                 'path':lut, 
977                 'interpolation':'linear', 
978                 'direction':'forward'
979             } )
980
981         if gamut == 'S-Gamut':
982             cs.toReferenceTransforms.append( {
983                 'type':'matrix',
984                 'matrix':mat44FromMat33([0.754338638, 0.133697046, 0.111968437,
985                                         0.021198141, 1.005410934, -0.026610548, 
986                                         -0.009756991, 0.004508563, 1.005253201]),
987                 'direction':'forward'
988             })
989         elif gamut == 'S-Gamut Daylight':
990             cs.toReferenceTransforms.append( {
991                 'type':'matrix',
992                 'matrix':mat44FromMat33([0.8764457030, 0.0145411681, 0.1090131290,
993                                         0.0774075345, 0.9529571767, -0.0303647111, 
994                                         0.0573564351, -0.1151066335, 1.0577501984]),
995                 'direction':'forward'
996             })
997         elif gamut == 'S-Gamut Tungsten':
998             cs.toReferenceTransforms.append( {
999                 'type':'matrix',
1000                 'matrix':mat44FromMat33([1.0110238740, -0.1362526051, 0.1252287310, 
1001                             0.1011994504, 0.9562196265, -0.0574190769,
1002                             0.0600766530, -0.1010185315, 1.0409418785]),
1003                 'direction':'forward'
1004             })
1005         elif gamut == 'S-Gamut3.Cine':
1006             cs.toReferenceTransforms.append( {
1007                 'type':'matrix',
1008                 'matrix':mat44FromMat33([0.6387886672, 0.2723514337, 0.0888598992, 
1009                                         -0.0039159061, 1.0880732308, -0.0841573249, 
1010                                         -0.0299072021, -0.0264325799, 1.0563397820]),
1011                 'direction':'forward'
1012             })
1013         elif gamut == 'S-Gamut3':
1014             cs.toReferenceTransforms.append( {
1015                 'type':'matrix',
1016                 'matrix':mat44FromMat33([0.7529825954, 0.1433702162, 0.1036471884, 
1017                             0.0217076974, 1.0153188355, -0.0370265329, 
1018                             -0.0094160528, 0.0033704179, 1.0060456349]),
1019                 'direction':'forward'
1020             })
1021
1022         cs.fromReferenceTransforms = []
1023         return cs
1024
1025     # SLog1
1026     SLog1SGamut = createSlog("S-Gamut", "S-Log1", name="S-Log")
1027     configData['colorSpaces'].append(SLog1SGamut)
1028
1029     # SLog2
1030     SLog2SGamut = createSlog("S-Gamut", "S-Log2", name="S-Log2")
1031     configData['colorSpaces'].append(SLog2SGamut)
1032
1033     SLog2SGamutDaylight = createSlog("S-Gamut Daylight", "S-Log2", name="S-Log2")
1034     configData['colorSpaces'].append(SLog2SGamutDaylight)
1035
1036     SLog2SGamutTungsten = createSlog("S-Gamut Tungsten", "S-Log2", name="S-Log2")
1037     configData['colorSpaces'].append(SLog2SGamutTungsten)
1038
1039     # SLog3
1040     SLog3SGamut3Cine = createSlog("S-Gamut3.Cine", "S-Log3", name="S-Log3")
1041     configData['colorSpaces'].append(SLog3SGamut3Cine)
1042
1043     SLog3SGamut3 = createSlog("S-Gamut3", "S-Log3", name="S-Log3")
1044     configData['colorSpaces'].append(SLog3SGamut3)
1045
1046     # Linearization only
1047     SLog1 = createSlog("", "S-Log1", name="S-Log")
1048     configData['colorSpaces'].append(SLog1)
1049
1050     SLog2 = createSlog("", "S-Log2", name="S-Log2")
1051     configData['colorSpaces'].append(SLog2)
1052
1053     SLog3 = createSlog("", "S-Log3", name="S-Log3")
1054     configData['colorSpaces'].append(SLog3)
1055
1056     # Primaries only
1057     SGamut = createSlog("S-Gamut", "", name="S-Log")
1058     configData['colorSpaces'].append(SGamut)
1059
1060     SGamutDaylight = createSlog("S-Gamut Daylight", "", name="S-Log2")
1061     configData['colorSpaces'].append(SGamutDaylight)
1062
1063     SGamutTungsten = createSlog("S-Gamut Tungsten", "", name="S-Log2")
1064     configData['colorSpaces'].append(SGamutTungsten)
1065
1066     SGamut3Cine = createSlog("S-Gamut3.Cine", "", name="S-Log3")
1067     configData['colorSpaces'].append(SGamut3Cine)
1068
1069     SGamut3 = createSlog("S-Gamut3", "", name="S-Log3")
1070     configData['colorSpaces'].append(SGamut3)
1071
1072     #
1073     # LogC to ACES
1074     #
1075     def createLogC(gamut, transferFunction, exposureIndex, name='LogC'):
1076         name = "%s (EI%s) - %s" % (transferFunction, exposureIndex, gamut)
1077         if transferFunction == "":
1078             name = "Linear - %s" % gamut
1079         if gamut == "":
1080             name = "%s (EI%s)" % (transferFunction, exposureIndex)
1081
1082         cs = ColorSpace(name)
1083         cs.description = name
1084         cs.equalityGroup = ''
1085         cs.family = 'ARRI'
1086         cs.isData=False
1087
1088         # Globals
1089         IDT_maker_version = "0.08"
1090
1091         nominalEI = 400.0
1092         blackSignal = 0.003907
1093         midGraySignal = 0.01
1094         encodingGain = 0.256598
1095         encodingOffset = 0.391007
1096
1097         def gainForEI(EI) :
1098             return (math.log(EI/nominalEI)/math.log(2) * (0.89 - 1) / 3 + 1) * encodingGain
1099
1100         def LogCInverseParametersForEI(EI) :
1101             cut = 1.0 / 9.0
1102             slope = 1.0 / (cut * math.log(10))
1103             offset = math.log10(cut) - slope * cut
1104             gain = EI / nominalEI
1105             gray = midGraySignal / gain
1106             # The higher the EI, the lower the gamma
1107             encGain = gainForEI(EI)
1108             encOffset = encodingOffset
1109             for i in range(0,3) :
1110                 nz = ((95.0 / 1023.0 - encOffset) / encGain - offset) / slope
1111                 encOffset = encodingOffset - math.log10(1 + nz) * encGain
1112             # Calculate some intermediate values
1113             a = 1.0 / gray
1114             b = nz - blackSignal / gray
1115             e = slope * a * encGain
1116             f = encGain * (slope * b + offset) + encOffset
1117             # Manipulations so we can return relative exposure
1118             s = 4 / (0.18 * EI)
1119             t = blackSignal
1120             b = b + a * t
1121             a = a * s
1122             f = f + e * t
1123             e = e * s
1124             return { 'a' : a,
1125                      'b' : b,
1126                      'cut' : (cut - b) / a,
1127                      'c' : encGain,
1128                      'd' : encOffset,
1129                      'e' : e,
1130                      'f' : f }
1131
1132         def logCtoLinear(codeValue, exposureIndex):
1133             p = LogCInverseParametersForEI(exposureIndex)
1134             breakpoint = p['e'] * p['cut'] + p['f']
1135             if (codeValue > breakpoint):
1136                 linear = (pow(10,(codeValue/1023.0 - p['d']) / p['c']) - p['b']) / p['a']
1137             else:
1138                 linear = (codeValue/1023.0 - p['f']) / p['e']
1139
1140             #print( codeValue, linear )
1141             return linear
1142
1143
1144         cs.toReferenceTransforms = []
1145
1146         if transferFunction == "V3 LogC":
1147             data = array.array('f', "\0" * lutResolution1d * 4)
1148             for c in range(lutResolution1d):
1149                 data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex))
1150
1151             lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex))
1152             genlut.writeSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1153
1154             #print( "Writing %s" % lut)
1155             cs.toReferenceTransforms.append( {
1156                 'type':'lutFile', 
1157                 'path':lut, 
1158                 'interpolation':'linear', 
1159                 'direction':'forward'
1160             } )
1161
1162         if gamut == 'Wide Gamut':
1163             cs.toReferenceTransforms.append( {
1164                 'type':'matrix',
1165                 'matrix':mat44FromMat33([0.680206, 0.236137, 0.083658, 
1166                             0.085415, 1.017471, -0.102886, 
1167                             0.002057, -0.062563, 1.060506]),
1168                 'direction':'forward'
1169             })
1170
1171         cs.fromReferenceTransforms = []
1172         return cs
1173
1174     transferFunction = "V3 LogC"
1175     gamut = "Wide Gamut"
1176     #EIs = [160.0, 200.0, 250.0, 320.0, 400.0, 500.0, 640.0, 800.0, 1000.0, 1280.0, 1600.0, 2000.0, 2560.0, 3200.0]
1177     EIs = [160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600, 2000, 2560, 3200]
1178     defaultEI = 800
1179
1180     # Full conversion
1181     for EI in EIs:
1182         LogCEIfull = createLogC(gamut, transferFunction, EI, name="LogC")
1183         configData['colorSpaces'].append(LogCEIfull)
1184
1185     # Linearization only
1186     for EI in [800]:
1187         LogCEIlinearization = createLogC("", transferFunction, EI, name="LogC")
1188         configData['colorSpaces'].append(LogCEIlinearization)
1189
1190     # Primaries
1191     LogCEIprimaries = createLogC(gamut, "", defaultEI, name="LogC")
1192     configData['colorSpaces'].append(LogCEIprimaries)
1193
1194     #
1195     # Generic log transform
1196     #
1197     def createGenericLog(name='log', 
1198         minValue=0.0, 
1199         maxValue=1.0, 
1200         inputScale=1.0,
1201         middleGrey=0.18,
1202         minExposure=-6.0,
1203         maxExposure=6.5,
1204         lutResolution1d=lutResolution1d):
1205         cs = ColorSpace(name)
1206         cs.description = "The %s color space" % name
1207         cs.equalityGroup = name
1208         cs.family = 'Utility'
1209         cs.isData=False
1210
1211         ctls = [
1212             #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
1213             '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
1214         ]
1215         lut = "%s_to_aces.spi1d" % name
1216
1217         genlut.generate1dLUTFromCTL( lutDir + "/" + lut, 
1218             ctls, 
1219             lutResolution1d, 
1220             'float', 
1221             inputScale,
1222             1.0, 
1223             {
1224                 'middleGrey'  : middleGrey,
1225                 'minExposure' : minExposure,
1226                 'maxExposure' : maxExposure
1227             },
1228             cleanup, 
1229             acesCTLReleaseDir,
1230             minValue,
1231             maxValue)
1232
1233         cs.toReferenceTransforms = []
1234         cs.toReferenceTransforms.append( {
1235             'type':'lutFile', 
1236             'path':lut, 
1237             'interpolation':'linear', 
1238             'direction':'forward'
1239         } )
1240
1241         cs.fromReferenceTransforms = []
1242         return cs
1243
1244     #
1245     # ACES LMTs
1246     #
1247     def createACESLMT(lmtName, 
1248         lmtValues,
1249         shaperInfo,
1250         lutResolution1d=1024, 
1251         lutResolution3d=64, 
1252         cleanup=True):
1253         cs = ColorSpace("%s" % lmtName)
1254         cs.description = "The ACES Look Transform: %s" % lmtName
1255         cs.equalityGroup = ''
1256         cs.family = 'Look'
1257         cs.isData=False
1258
1259         import pprint
1260         pprint.pprint( lmtValues )
1261
1262         #
1263         # Generate the shaper transform
1264         #
1265         (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1266
1267         shaperLut = "%s_to_aces.spi1d" % shaperName
1268         if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1269             ctls = [
1270                 shaperToACESCTL % acesCTLReleaseDir
1271             ]
1272             genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
1273                 ctls, 
1274                 lutResolution1d, 
1275                 'float', 
1276                 1.0/shaperInputScale,
1277                 1.0, 
1278                 shaperParams,
1279                 cleanup, 
1280                 acesCTLReleaseDir)
1281
1282         shaperOCIOTransform = {
1283             'type':'lutFile', 
1284             'path':shaperLut, 
1285             'interpolation':'linear', 
1286             'direction':'inverse'
1287         }
1288
1289         #
1290         # Generate the forward transform
1291         #
1292         cs.fromReferenceTransforms = []
1293
1294         if 'transformCTL' in lmtValues:
1295             ctls = [
1296                 shaperToACESCTL % acesCTLReleaseDir, 
1297                 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
1298             ]
1299             lut = "%s.%s.spi3d" % (shaperName, lmtName)
1300
1301             genlut.generate3dLUTFromCTL( lutDir + "/" + lut, 
1302                 ctls, 
1303                 lutResolution3d, 
1304                 'float', 
1305                 1.0/shaperInputScale,
1306                 1.0, 
1307                 shaperParams,
1308                 cleanup, 
1309                 acesCTLReleaseDir )
1310
1311             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1312             cs.fromReferenceTransforms.append( {
1313                 'type':'lutFile', 
1314                 'path':lut, 
1315                 'interpolation':'tetrahedral', 
1316                 'direction':'forward'
1317             } )
1318
1319         #
1320         # Generate the inverse transform
1321         #
1322         cs.toReferenceTransforms = []
1323
1324         if 'transformCTLInverse' in lmtValues:
1325             ctls = [
1326                 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1327                 shaperFromACESCTL % acesCTLReleaseDir
1328             ]
1329             lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
1330
1331             genlut.generate3dLUTFromCTL( lutDir + "/" + lut, 
1332                 ctls, 
1333                 lutResolution3d, 
1334                 'half', 
1335                 1.0,
1336                 shaperInputScale, 
1337                 shaperParams,
1338                 cleanup, 
1339                 acesCTLReleaseDir )
1340
1341             cs.toReferenceTransforms.append( {
1342                 'type':'lutFile', 
1343                 'path':lut, 
1344                 'interpolation':'tetrahedral', 
1345                 'direction':'forward'
1346             } )
1347
1348             shaperInverse = shaperOCIOTransform.copy()
1349             shaperInverse['direction'] = 'forward'
1350             cs.toReferenceTransforms.append( shaperInverse )
1351
1352         return cs
1353
1354     #
1355     # LMT Shaper
1356     #
1357
1358     lmtLutResolution1d = max(4096, lutResolution1d)
1359     lmtLutResolution3d = max(65, lutResolution3d)
1360
1361     # Log 2 shaper
1362     lmtShaperName = 'LMT Shaper'
1363     lmtParams = {
1364         'middleGrey'  : 0.18,
1365         'minExposure' : -10.0,
1366         'maxExposure' : 6.5
1367     }
1368     lmtShaper = createGenericLog(name=lmtShaperName, 
1369         middleGrey=lmtParams['middleGrey'], 
1370         minExposure=lmtParams['minExposure'], 
1371         maxExposure=lmtParams['maxExposure'],
1372         lutResolution1d=lmtLutResolution1d)
1373     configData['colorSpaces'].append(lmtShaper)
1374
1375     shaperInputScale_genericLog2 = 1.0
1376
1377     # Log 2 shaper name and CTL transforms bundled up
1378     lmtShaperData = [
1379         lmtShaperName, 
1380         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1381         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1382         #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1383         #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1384         shaperInputScale_genericLog2,
1385         lmtParams
1386     ]
1387
1388     sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
1389     print( sortedLMTs )
1390     for lmt in sortedLMTs:
1391         (lmtName, lmtValues) = lmt
1392         cs = createACESLMT(
1393             lmtValues['transformUserName'], 
1394             lmtValues,
1395             lmtShaperData,
1396             lmtLutResolution1d,
1397             lmtLutResolution3d,
1398             cleanup)
1399         configData['colorSpaces'].append(cs)
1400
1401     #
1402     # ACES RRT with the supplied ODT
1403     #
1404     def createACESRRTplusODT(odtName, 
1405         odtValues,
1406         shaperInfo,
1407         lutResolution1d=1024, 
1408         lutResolution3d=64, 
1409         cleanup=True):
1410         cs = ColorSpace("%s" % odtName)
1411         cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
1412         cs.equalityGroup = ''
1413         cs.family = 'Output'
1414         cs.isData=False
1415
1416         import pprint
1417         pprint.pprint( odtValues )
1418
1419         #
1420         # Generate the shaper transform
1421         #
1422         #if 'shaperCTL' in odtValues:
1423         (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1424
1425         if 'legalRange' in odtValues:
1426             shaperParams['legalRange'] = odtValues['legalRange']
1427         else:
1428             shaperParams['legalRange'] = 0
1429
1430         shaperLut = "%s_to_aces.spi1d" % shaperName
1431         if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1432             ctls = [
1433                 shaperToACESCTL % acesCTLReleaseDir
1434             ]
1435             genlut.generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
1436                 ctls, 
1437                 lutResolution1d, 
1438                 'float', 
1439                 1.0/shaperInputScale,
1440                 1.0, 
1441                 shaperParams,
1442                 cleanup, 
1443                 acesCTLReleaseDir)
1444
1445         shaperOCIOTransform = {
1446             'type':'lutFile', 
1447             'path':shaperLut, 
1448             'interpolation':'linear', 
1449             'direction':'inverse'
1450         }
1451
1452         #
1453         # Generate the forward transform
1454         #
1455         cs.fromReferenceTransforms = []
1456
1457         if 'transformLUT' in odtValues:
1458             # Copy into the lut dir
1459             transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1460             lut = lutDir + "/" + transformLUTFileName
1461             shutil.copy(odtValues['transformLUT'], lut)
1462
1463             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1464             cs.fromReferenceTransforms.append( {
1465                 'type':'lutFile', 
1466                 'path': transformLUTFileName, 
1467                 'interpolation':'tetrahedral', 
1468                 'direction':'forward'
1469             } )
1470         elif 'transformCTL' in odtValues:
1471             #shaperLut
1472
1473             ctls = [
1474                 shaperToACESCTL % acesCTLReleaseDir, 
1475                 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir, 
1476                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1477             ]
1478             lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1479
1480             genlut.generate3dLUTFromCTL( lutDir + "/" + lut, 
1481                 #shaperLUT,
1482                 ctls, 
1483                 lutResolution3d, 
1484                 'float', 
1485                 1.0/shaperInputScale,
1486                 1.0, 
1487                 shaperParams,
1488                 cleanup, 
1489                 acesCTLReleaseDir )
1490
1491             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1492             cs.fromReferenceTransforms.append( {
1493                 'type':'lutFile', 
1494                 'path':lut, 
1495                 'interpolation':'tetrahedral', 
1496                 'direction':'forward'
1497             } )
1498
1499         #
1500         # Generate the inverse transform
1501         #
1502         cs.toReferenceTransforms = []
1503
1504         if 'transformLUTInverse' in odtValues:
1505             # Copy into the lut dir
1506             transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
1507             lut = lutDir + "/" + transformLUTInverseFileName
1508             shutil.copy(odtValues['transformLUTInverse'], lut)
1509
1510             cs.toReferenceTransforms.append( {
1511                 'type':'lutFile', 
1512                 'path': transformLUTInverseFileName, 
1513                 'interpolation':'tetrahedral', 
1514                 'direction':'forward'
1515             } )
1516
1517             shaperInverse = shaperOCIOTransform.copy()
1518             shaperInverse['direction'] = 'forward'
1519             cs.toReferenceTransforms.append( shaperInverse )
1520         elif 'transformCTLInverse' in odtValues:
1521             ctls = [
1522                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1523                 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1524                 shaperFromACESCTL % acesCTLReleaseDir
1525             ]
1526             lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1527
1528             genlut.generate3dLUTFromCTL( lutDir + "/" + lut, 
1529                 #None,
1530                 ctls, 
1531                 lutResolution3d, 
1532                 'half', 
1533                 1.0,
1534                 shaperInputScale, 
1535                 shaperParams,
1536                 cleanup, 
1537                 acesCTLReleaseDir )
1538
1539             cs.toReferenceTransforms.append( {
1540                 'type':'lutFile', 
1541                 'path':lut, 
1542                 'interpolation':'tetrahedral', 
1543                 'direction':'forward'
1544             } )
1545
1546             shaperInverse = shaperOCIOTransform.copy()
1547             shaperInverse['direction'] = 'forward'
1548             cs.toReferenceTransforms.append( shaperInverse )
1549
1550         return cs
1551
1552     #
1553     # RRT/ODT shaper options
1554     #
1555     shaperData = {}
1556
1557     # Log 2 shaper
1558     log2ShaperName = shaperName
1559     log2Params = {
1560         'middleGrey'  : 0.18,
1561         'minExposure' : -6.0,
1562         'maxExposure' : 6.5
1563     }
1564     log2Shaper = createGenericLog(name=log2ShaperName, 
1565         middleGrey=log2Params['middleGrey'], 
1566         minExposure=log2Params['minExposure'], 
1567         maxExposure=log2Params['maxExposure'])
1568     configData['colorSpaces'].append(log2Shaper)
1569
1570     shaperInputScale_genericLog2 = 1.0
1571
1572     # Log 2 shaper name and CTL transforms bundled up
1573     log2ShaperData = [
1574         log2ShaperName, 
1575         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1576         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1577         #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1578         #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1579         shaperInputScale_genericLog2,
1580         log2Params
1581     ]
1582
1583     shaperData[log2ShaperName] = log2ShaperData
1584
1585     #
1586     # Shaper that also includes the AP1 primaries
1587     # - Needed for some LUT baking steps
1588     #
1589     log2ShaperAP1 = createGenericLog(name=log2ShaperName, 
1590         middleGrey=log2Params['middleGrey'], 
1591         minExposure=log2Params['minExposure'], 
1592         maxExposure=log2Params['maxExposure'])
1593     log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1594     # AP1 primaries to AP0 primaries
1595     log2ShaperAP1.toReferenceTransforms.append( {
1596         'type':'matrix',
1597         'matrix':mat44FromMat33(acesAP1toAP0),
1598         'direction':'forward'
1599     })
1600     configData['colorSpaces'].append(log2ShaperAP1)
1601
1602     #
1603     # Choose your shaper
1604     #
1605     # XXX
1606     # Shaper name. Should really be automated or made a user choice
1607     #
1608     # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
1609     #shaperName = 'log2Shaper'
1610
1611     #if shaperName in shaperData:
1612     #    rrtShaperName = shaperName
1613     #    rrtShaper = shaperData[shaperName]
1614     #else:
1615
1616     rrtShaperName = log2ShaperName
1617     rrtShaper = log2ShaperData
1618
1619     #
1620     # RRT + ODT Combinations
1621     #
1622     #for odtName, odtValues in odtInfo.iteritems():
1623     sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1624     print( sortedOdts )
1625     for odt in sortedOdts:
1626         (odtName, odtValues) = odt
1627
1628         # Have to handle ODTs that can generate either legal or full output
1629         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1630                 'Academy.Rec709_100nits_dim.a1.0.0',
1631                 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1632             odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1633         else:
1634             odtNameLegal = odtValues['transformUserName']
1635
1636         odtLegal = odtValues.copy()
1637         odtLegal['legalRange'] = 1
1638
1639         cs = createACESRRTplusODT(
1640             odtNameLegal, 
1641             odtLegal,
1642             rrtShaper,
1643             lutResolution1d,
1644             lutResolution3d,
1645             cleanup)
1646         configData['colorSpaces'].append(cs)
1647
1648         # Create a display entry using this color space
1649         configData['displays'][odtNameLegal] = { 
1650             'Linear':ACES, 
1651             'Log':ACEScc, 
1652             'Output Transform':cs }
1653
1654         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1655                 'Academy.Rec709_100nits_dim.a1.0.0', 
1656                 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1657
1658             print( "Generating full range ODT for %s" % odtName)
1659
1660             odtNameFull = "%s - Full" % odtValues['transformUserName']
1661             odtFull = odtValues.copy()
1662             odtFull['legalRange'] = 0
1663
1664             csFull = createACESRRTplusODT(
1665                 odtNameFull, 
1666                 odtFull,
1667                 rrtShaper,
1668                 lutResolution1d,
1669                 lutResolution3d,
1670                 cleanup)
1671             configData['colorSpaces'].append(csFull)
1672
1673             # Create a display entry using this color space
1674             configData['displays'][odtNameFull] = { 
1675                 'Linear':ACES, 
1676                 'Log':ACEScc, 
1677                 'Output Transform':csFull }
1678
1679     #
1680     # Generic Matrix transform
1681     #
1682     def createGenericMatrix(name='matrix', 
1683         fromReferenceValues=[],
1684         toReferenceValues=[]):
1685         cs = ColorSpace(name)
1686         cs.description = "The %s color space" % name
1687         cs.equalityGroup = name
1688         cs.family = 'Utility'
1689         cs.isData=False
1690
1691         cs.toReferenceTransforms = []
1692         if toReferenceValues != []:
1693             for matrix in toReferenceValues:
1694                 cs.toReferenceTransforms.append( {
1695                     'type':'matrix',
1696                     'matrix':mat44FromMat33(matrix),
1697                     'direction':'forward'
1698                 })
1699
1700         cs.fromReferenceTransforms = []
1701         if fromReferenceValues != []:
1702             for matrix in fromReferenceValues:
1703                 cs.fromReferenceTransforms.append( {
1704                     'type':'matrix',
1705                     'matrix':mat44FromMat33(matrix),
1706                     'direction':'forward'
1707                 })
1708
1709         return cs
1710
1711     cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1712     configData['colorSpaces'].append(cs)   
1713
1714     cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1715     configData['colorSpaces'].append(cs)   
1716
1717     # ACES to Linear, P3D60 primaries
1718     xyzToP3D60 = [ 2.4027414142, -0.8974841639, -0.3880533700,
1719                   -0.8325796487,  1.7692317536,  0.0237127115,
1720                    0.0388233815, -0.0824996856,  1.0363685997]
1721
1722     cs = createGenericMatrix('Linear - P3-D60', fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1723     configData['colorSpaces'].append(cs)   
1724
1725     # ACES to Linear, P3D60 primaries
1726     xyzToP3DCI = [ 2.7253940305, -1.0180030062, -0.4401631952,
1727                   -0.7951680258,  1.6897320548,  0.0226471906,
1728                    0.0412418914, -0.0876390192,  1.1009293786]
1729
1730     cs = createGenericMatrix('Linear - P3-DCI', fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1731     configData['colorSpaces'].append(cs)   
1732
1733     # ACES to Linear, Rec 709 primaries
1734     xyzToRec709 = [ 3.2409699419, -1.5373831776, -0.4986107603,
1735                    -0.9692436363,  1.8759675015,  0.0415550574,
1736                     0.0556300797, -0.2039769589,  1.0569715142]
1737
1738     cs = createGenericMatrix('Linear - Rec.709', fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1739     configData['colorSpaces'].append(cs)   
1740
1741     # ACES to Linear, Rec 2020 primaries
1742     xyzToRec2020 = [ 1.7166511880, -0.3556707838, -0.2533662814,
1743                     -0.6666843518,  1.6164812366,  0.0157685458,
1744                      0.0176398574, -0.0427706133,  0.9421031212]
1745
1746     cs = createGenericMatrix('Linear - Rec.2020', fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1747     configData['colorSpaces'].append(cs)   
1748
1749     print( "generateLUTs - end" )
1750     return configData
1751
1752 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1753
1754     # Add the legal and full variations into this list
1755     odtInfoC = dict(odtInfo)
1756     for odtCTLName, odtValues in odtInfo.iteritems():
1757         if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1758             'Academy.Rec709_100nits_dim.a1.0.0',
1759             'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1760                 odtName = odtValues["transformUserName"]
1761
1762                 odtValuesLegal = dict(odtValues)
1763                 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1764                 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1765
1766                 odtValuesFull = dict(odtValues)
1767                 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1768                 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1769                 
1770                 del( odtInfoC[odtCTLName] )
1771
1772     for odtCTLName, odtValues in odtInfoC.iteritems():
1773         odtPrefix = odtValues["transformUserNamePrefix"]
1774         odtName = odtValues["transformUserName"]
1775
1776         # For Photoshop
1777         for inputspace in ["ACEScc", "ACESproxy"]:
1778             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1779             args += ["--outputspace", "%s" % odtName ]
1780             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1781             args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
1782             args += ["--cubesize", str(lutResolution3d) ]
1783             args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1784
1785             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1786             bakeLUT.execute()    
1787
1788         # For Flame, Lustre
1789         for inputspace in ["ACEScc", "ACESproxy"]:
1790             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1791             args += ["--outputspace", "%s" % odtName ]
1792             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1793             args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
1794             args += ["--cubesize", str(lutResolution3d) ]
1795
1796             fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1797             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1798             bakeLUT.execute()    
1799
1800             largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1801             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1802             bakeLUT.execute()
1803
1804         # For Maya, Houdini
1805         for inputspace in ["ACEScg", "ACES2065-1"]:
1806             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1807             args += ["--outputspace", "%s" % odtName ]
1808             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1809             if inputspace == 'ACEScg':
1810                 linShaperName = "%s - AP1" % shaperName 
1811             else:
1812                 linShaperName = shaperName
1813             args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ] 
1814             
1815             args += ["--cubesize", str(lutResolution3d) ]
1816
1817             margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
1818             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
1819             bakeLUT.execute()    
1820
1821             hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
1822             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
1823             bakeLUT.execute()    
1824
1825
1826 def createConfigDir(configDir, bakeSecondaryLUTs):
1827     dirs = [configDir, "%s/luts" % configDir]
1828     if bakeSecondaryLUTs:
1829         dirs.extend(["%s/baked" % configDir, 
1830             "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
1831             "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
1832             "%s/baked/maya" % configDir])
1833
1834     for d in dirs:
1835         if not os.path.exists(d):
1836             os.mkdir(d)
1837
1838 def getTransformInfo(ctlTransform):
1839     fp = open(ctlTransform, 'rb')
1840
1841     # Read lines
1842     lines = fp.readlines()
1843
1844     # Grab transform ID and User Name
1845     transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1846     #print( transformID )
1847     transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
1848     transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
1849     #print( transformUserName )
1850     fp.close()
1851
1852     return (transformID, transformUserName, transformUserNamePrefix)
1853
1854 # For versions after WGR9
1855 def getODTInfo(acesCTLReleaseDir):
1856     # Credit to Alex Fry for the original approach here
1857     odtDir = os.path.join(acesCTLReleaseDir, "odt")
1858     allodt = []
1859     for dirName, subdirList, fileList in os.walk(odtDir):
1860         for fname in fileList:
1861             allodt.append((os.path.join(dirName,fname)))
1862
1863     odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1864
1865     #print odtCTLs
1866
1867     odts = {}
1868
1869     for odtCTL in odtCTLs:
1870         odtTokens = os.path.split(odtCTL)
1871         #print( odtTokens )
1872
1873         # Handle nested directories
1874         odtPathTokens = os.path.split(odtTokens[-2])
1875         odtDir = odtPathTokens[-1]
1876         while odtPathTokens[-2][-3:] != 'odt':
1877             odtPathTokens = os.path.split(odtPathTokens[-2])
1878             odtDir = os.path.join(odtPathTokens[-1], odtDir)
1879
1880         # Build full name
1881         #print( "odtDir : %s" % odtDir )
1882         transformCTL = odtTokens[-1]
1883         #print( transformCTL )
1884         odtName = string.join(transformCTL.split('.')[1:-1], '.')
1885         #print( odtName )
1886
1887         # Find id, user name and user name prefix
1888         (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1889             "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
1890
1891         # Find inverse
1892         transformCTLInverse = "InvODT.%s.ctl" % odtName
1893         if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
1894             transformCTLInverse = None
1895         #print( transformCTLInverse )
1896
1897         # Add to list of ODTs
1898         odts[odtName] = {}
1899         odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1900         if transformCTLInverse != None:
1901             odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
1902
1903         odts[odtName]['transformID'] = transformID
1904         odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1905         odts[odtName]['transformUserName'] = transformUserName
1906
1907         print( "ODT : %s" % odtName )
1908         print( "\tTransform ID               : %s" % transformID )
1909         print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1910         print( "\tTransform User Name        : %s" % transformUserName )
1911         print( "\tForward ctl                : %s" % odts[odtName]['transformCTL'])
1912         if 'transformCTLInverse' in odts[odtName]:
1913             print( "\tInverse ctl                : %s" % odts[odtName]['transformCTLInverse'])
1914         else:
1915             print( "\tInverse ctl                : %s" % "None" )
1916
1917     print( "\n" )
1918
1919     return odts
1920
1921 # For versions after WGR9
1922 def getLMTInfo(acesCTLReleaseDir):
1923     # Credit to Alex Fry for the original approach here
1924     lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1925     alllmt = []
1926     for dirName, subdirList, fileList in os.walk(lmtDir):
1927         for fname in fileList:
1928             alllmt.append((os.path.join(dirName,fname)))
1929
1930     lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
1931
1932     #print lmtCTLs
1933
1934     lmts = {}
1935
1936     for lmtCTL in lmtCTLs:
1937         lmtTokens = os.path.split(lmtCTL)
1938         #print( lmtTokens )
1939
1940         # Handle nested directories
1941         lmtPathTokens = os.path.split(lmtTokens[-2])
1942         lmtDir = lmtPathTokens[-1]
1943         while lmtPathTokens[-2][-3:] != 'ctl':
1944             lmtPathTokens = os.path.split(lmtPathTokens[-2])
1945             lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1946
1947         # Build full name
1948         #print( "lmtDir : %s" % lmtDir )
1949         transformCTL = lmtTokens[-1]
1950         #print( transformCTL )
1951         lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1952         #print( lmtName )
1953
1954         # Find id, user name and user name prefix
1955         (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
1956             "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
1957
1958         # Find inverse
1959         transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1960         if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
1961             transformCTLInverse = None
1962         #print( transformCTLInverse )
1963
1964         # Add to list of LMTs
1965         lmts[lmtName] = {}
1966         lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1967         if transformCTLInverse != None:
1968             lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
1969
1970         lmts[lmtName]['transformID'] = transformID
1971         lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1972         lmts[lmtName]['transformUserName'] = transformUserName
1973
1974         print( "LMT : %s" % lmtName )
1975         print( "\tTransform ID               : %s" % transformID )
1976         print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
1977         print( "\tTransform User Name        : %s" % transformUserName )
1978         print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1979         if 'transformCTLInverse' in lmts[lmtName]:
1980             print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
1981         else:
1982             print( "\t Inverse ctl : %s" % "None" )
1983
1984     print( "\n" )
1985
1986     return lmts
1987
1988 #
1989 # Create the ACES config
1990 #
1991 def createACESConfig(acesCTLReleaseDir, 
1992     configDir, 
1993     lutResolution1d=4096, 
1994     lutResolution3d=64, 
1995     bakeSecondaryLUTs=True,
1996     cleanup=True):
1997
1998     # Get ODT names and CTL paths
1999     odtInfo = getODTInfo(acesCTLReleaseDir)
2000
2001     # Get ODT names and CTL paths
2002     lmtInfo = getLMTInfo(acesCTLReleaseDir)
2003
2004     # Create config dir
2005     createConfigDir(configDir, bakeSecondaryLUTs)
2006
2007     # Generate config data and LUTs for different transforms
2008     lutDir = "%s/luts" % configDir
2009     shaperName = 'Output Shaper'
2010     configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
2011     
2012     # Create the config using the generated LUTs
2013     print( "Creating generic config")
2014     config = createConfig(configData)
2015     print( "\n\n\n" )
2016
2017     # Write the config to disk
2018     writeConfig(config, "%s/config.ocio" % configDir )
2019
2020     # Create a config that will work well with Nuke using the previously generated LUTs
2021     print( "Creating Nuke-specific config")
2022     nuke_config = createConfig(configData, nuke=True)
2023     print( "\n\n\n" )
2024
2025     # Write the config to disk
2026     writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
2027
2028     # Bake secondary LUTs using the config
2029     if bakeSecondaryLUTs:
2030         generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
2031
2032 #
2033 # Main
2034 #
2035 def main():
2036     import optparse
2037
2038     p = optparse.OptionParser(description='An OCIO config generation script',
2039                                 prog='createACESConfig',
2040                                 version='createACESConfig 0.1',
2041                                 usage='%prog [options]')
2042     p.add_option('--acesCTLDir', '-a', default=None)
2043     p.add_option('--configDir', '-c', default=None)
2044     p.add_option('--lutResolution1d', default=4096)
2045     p.add_option('--lutResolution3d', default=64)
2046     p.add_option('--dontBakeSecondaryLUTs', action="store_true")
2047     p.add_option('--keepTempImages', action="store_true")
2048
2049     options, arguments = p.parse_args()
2050
2051     #
2052     # Get options
2053     # 
2054     acesCTLDir = options.acesCTLDir
2055     configDir  = options.configDir
2056     lutResolution1d  = int(options.lutResolution1d)
2057     lutResolution3d  = int(options.lutResolution3d)
2058     bakeSecondaryLUTs  = not(options.dontBakeSecondaryLUTs)
2059     cleanupTempImages  = not(options.keepTempImages)
2060
2061     try:
2062         argsStart = sys.argv.index('--') + 1
2063         args = sys.argv[argsStart:]
2064     except:
2065         argsStart = len(sys.argv)+1
2066         args = []
2067
2068     print( "command line : \n%s\n" % " ".join(sys.argv) )
2069
2070     if configDir == None:
2071         print( "process: No ACES CTL directory specified" )
2072         return
2073  
2074     #
2075     # Generate the configuration
2076     #
2077     createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
2078 # main
2079
2080 if __name__ == '__main__':
2081     main()