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