Python scripts used to generate ACES OCIO configurations
[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     #
595     # ACEScc
596     #
597     def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
598         cs = ColorSpace(name)
599         cs.description = "The %s color space" % name
600         cs.equalityGroup = ''
601         cs.family = 'ACES'
602         cs.isData=False
603
604         ctls = [
605             '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
606             # This transform gets back to the AP1 primaries
607             # Useful to the 1d LUT is only covering the transfer function
608             # The primaries switch is covered by the matrix below
609             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
610         ]
611         lut = "%s_to_ACES.spi1d" % name
612         generate1dLUTFromCTL( lutDir + "/" + lut, 
613             ctls, 
614             lutResolution1d, 
615             'float', 
616             inputScale,
617             1.0, 
618             {},
619             cleanup, 
620             acesCTLReleaseDir,
621             minValue,
622             maxValue)
623
624         cs.toReferenceTransforms = []
625         cs.toReferenceTransforms.append( {
626             'type':'lutFile', 
627             'path':lut, 
628             'interpolation':'linear', 
629             'direction':'forward'
630         } )
631
632         # AP1 primaries to AP0 primaries
633         cs.toReferenceTransforms.append( {
634             'type':'matrix',
635             'matrix':mat44FromMat33(acesAP1toAP0),
636             'direction':'forward'
637         })
638
639         cs.fromReferenceTransforms = []
640         return cs
641
642     ACEScc = createACEScc()
643     configData['colorSpaces'].append(ACEScc)
644
645     #
646     # ACESproxy
647     #
648     def createACESProxy(name='ACESproxy'):
649         cs = ColorSpace(name)
650         cs.description = "The %s color space" % name
651         cs.equalityGroup = ''
652         cs.family = 'ACES'
653         cs.isData=False
654
655         ctls = [
656             '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
657             # This transform gets back to the AP1 primaries
658             # Useful to the 1d LUT is only covering the transfer function
659             # The primaries switch is covered by the matrix below
660             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
661         ]
662         lut = "%s_to_aces.spi1d" % name
663         generate1dLUTFromCTL( lutDir + "/" + lut, 
664             ctls, 
665             lutResolution1d, 
666             'uint16', 
667             64.0,
668             1.0, 
669             {},
670             cleanup, 
671             acesCTLReleaseDir )
672
673         cs.toReferenceTransforms = []
674         cs.toReferenceTransforms.append( {
675             'type':'lutFile', 
676             'path':lut, 
677             'interpolation':'linear', 
678             'direction':'forward'
679         } )
680
681         # AP1 primaries to AP0 primaries
682         cs.toReferenceTransforms.append( {
683             'type':'matrix',
684             'matrix':mat44FromMat33(acesAP1toAP0),
685             'direction':'forward'
686         })
687
688
689         cs.fromReferenceTransforms = []
690         return cs
691
692     ACESproxy = createACESProxy()
693     configData['colorSpaces'].append(ACESproxy)
694
695     #
696     # ACEScg
697     #
698     def createACEScg(name='ACEScg'):
699         cs = ColorSpace(name)
700         cs.description = "The %s color space" % name
701         cs.equalityGroup = ''
702         cs.family = 'ACES'
703         cs.isData=False
704
705         cs.toReferenceTransforms = []
706
707         # AP1 primaries to AP0 primaries
708         cs.toReferenceTransforms.append( {
709             'type':'matrix',
710             'matrix':mat44FromMat33(acesAP1toAP0),
711             'direction':'forward'
712         })
713
714         cs.fromReferenceTransforms = []
715         return cs
716
717     ACEScg = createACEScg()
718     configData['colorSpaces'].append(ACEScg)
719
720     #
721     # ADX
722     #
723     def createADX(bitdepth=10, name='ADX'):
724         name = "%s%s" % (name, bitdepth)
725         cs = ColorSpace(name)
726         cs.description = "%s color space - used for film scans" % name
727         cs.equalityGroup = ''
728         cs.family = 'ADX'
729         cs.isData=False
730
731         if bitdepth == 10:
732             cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
733             adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
734                         0.0, 1023.0/500.0, 0.0, 0.0,
735                         0.0, 0.0, 1023.0/500.0, 0.0,
736                         0.0, 0.0, 0.0, 1.0]
737             offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
738         elif bitdepth == 16:
739             cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
740             adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
741                         0.0, 65535.0/8000.0, 0.0, 0.0,
742                         0.0, 0.0, 65535.0/8000.0, 0.0,
743                         0.0, 0.0, 0.0, 1.0]
744             offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
745
746         cs.toReferenceTransforms = []
747
748         # Convert from ADX to Channel-Dependent Density
749         cs.toReferenceTransforms.append( {
750             'type':'matrix',
751             'matrix':adx_to_cdd,
752             'offset':offset,
753             'direction':'forward'
754         })
755
756         # Convert from Channel-Dependent Density to Channel-Independent Density
757         cs.toReferenceTransforms.append( {
758             'type':'matrix',
759             'matrix':[0.75573, 0.22197, 0.02230, 0,
760                         0.05901, 0.96928, -0.02829, 0,
761                         0.16134, 0.07406, 0.76460, 0,
762                         0.0, 0.0, 0.0, 1.0],
763             'direction':'forward'
764         })
765
766         # Copied from Alex Fry's adx_cid_to_rle.py
767         def createCIDtoRLELUT():
768             def interpolate1D(x, xp, fp):
769                 return numpy.interp(x, xp, fp)
770
771             LUT_1D_xp = [-0.190000000000000, 
772                           0.010000000000000,
773                           0.028000000000000,
774                           0.054000000000000,
775                           0.095000000000000,
776                           0.145000000000000,
777                           0.220000000000000,
778                           0.300000000000000,
779                           0.400000000000000,
780                           0.500000000000000,
781                           0.600000000000000]
782
783             LUT_1D_fp = [-6.000000000000000, 
784                          -2.721718645000000,
785                          -2.521718645000000,
786                          -2.321718645000000,
787                          -2.121718645000000,
788                          -1.921718645000000,
789                          -1.721718645000000,
790                          -1.521718645000000,
791                          -1.321718645000000,
792                          -1.121718645000000,
793                          -0.926545676714876]
794
795             REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
796
797             def cid_to_rle(x):
798                 if x <= 0.6:
799                     return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
800                 return (100.0 / 55.0) * x - REF_PT
801
802             def Fit(value, fromMin, fromMax, toMin, toMax):
803                 if fromMin == fromMax:
804                     raise ValueError("fromMin == fromMax")
805                 return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
806
807             NUM_SAMPLES = 2**12
808             RANGE = (-0.19, 3.0)
809             data = []
810             for i in xrange(NUM_SAMPLES):
811                 x = i/(NUM_SAMPLES-1.0)
812                 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
813                 data.append(cid_to_rle(x))
814
815             lut = 'ADX_CID_to_RLE.spi1d'
816             WriteSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
817
818             return lut
819
820         # Convert Channel Independent Density values to Relative Log Exposure values
821         lut = createCIDtoRLELUT()
822         cs.toReferenceTransforms.append( {
823             'type':'lutFile', 
824             'path':lut, 
825             'interpolation':'linear', 
826             'direction':'forward'
827         })
828
829         # Convert Relative Log Exposure values to Relative Exposure values
830         cs.toReferenceTransforms.append( {
831             'type':'log', 
832             'base':10, 
833             'direction':'inverse'
834         })
835
836         # Convert Relative Exposure values to ACES values
837         cs.toReferenceTransforms.append( {
838             'type':'matrix',
839             'matrix':[0.72286, 0.12630, 0.15084, 0,
840                         0.11923, 0.76418, 0.11659, 0,
841                         0.01427, 0.08213, 0.90359, 0,
842                         0.0, 0.0, 0.0, 1.0],
843             'direction':'forward'
844         })
845
846         cs.fromReferenceTransforms = []
847         return cs
848
849     ADX10 = createADX(bitdepth=10)
850     configData['colorSpaces'].append(ADX10)
851
852     ADX16 = createADX(bitdepth=16)
853     configData['colorSpaces'].append(ADX16)
854
855
856     #
857     # REDlogFilm to ACES
858     #
859     def createREDlogFilm(gamut, transferFunction, name='REDlogFilm'):
860         name = "%s - %s" % (transferFunction, gamut)
861         if transferFunction == "":
862             name = "Linear - %s" % gamut
863         if gamut == "":
864             name = "%s" % transferFunction
865
866         cs = ColorSpace(name)
867         cs.description = name
868         cs.equalityGroup = ''
869         cs.family = 'RED'
870         cs.isData=False
871
872         def cineonToLinear(codeValue):
873             nGamma = 0.6
874             blackPoint = 95.0
875             whitePoint = 685.0
876             codeValueToDensity = 0.002
877
878             blackLinear = pow(10.0, (blackPoint - whitePoint) * (codeValueToDensity / nGamma))
879             codeLinear = pow(10.0, (codeValue - whitePoint) * (codeValueToDensity / nGamma))
880
881             return (codeLinear - blackLinear)/(1.0 - blackLinear)
882
883         cs.toReferenceTransforms = []
884
885         if transferFunction == 'REDlogFilm':
886             data = array.array('f', "\0" * lutResolution1d * 4)
887             for c in range(lutResolution1d):
888                 data[c] = cineonToLinear(1023.0*c/(lutResolution1d-1))
889
890             lut = "CineonLog_to_linear.spi1d"
891             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
892
893             cs.toReferenceTransforms.append( {
894                 'type':'lutFile', 
895                 'path':lut, 
896                 'interpolation':'linear', 
897                 'direction':'forward'
898             } )
899
900         if gamut == 'DRAGONcolor':
901             cs.toReferenceTransforms.append( {
902                 'type':'matrix',
903                 'matrix':mat44FromMat33([0.532279, 0.376648, 0.091073, 
904                                         0.046344, 0.974513, -0.020860, 
905                                         -0.053976, -0.000320, 1.054267]),
906                 'direction':'forward'
907             })
908         elif gamut == 'REDcolor3':
909             cs.toReferenceTransforms.append( {
910                 'type':'matrix',
911                 'matrix':mat44FromMat33([0.512136, 0.360370, 0.127494, 
912                                         0.070377, 0.903884, 0.025737, 
913                                         -0.020824, 0.017671, 1.003123]),
914                 'direction':'forward'
915             })
916         elif gamut == 'REDcolor2':
917             cs.toReferenceTransforms.append( {
918                 'type':'matrix',
919                 'matrix':mat44FromMat33([0.480997, 0.402289, 0.116714, 
920                                         -0.004938, 1.000154, 0.004781, 
921                                         -0.105257, 0.025320, 1.079907]),
922                 'direction':'forward'
923             })
924
925         cs.fromReferenceTransforms = []
926         return cs
927
928     # Full conversion
929     REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "REDlogFilm", name="REDlogFilm")
930     configData['colorSpaces'].append(REDlogFilmDRAGON)
931
932     REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "REDlogFilm", name="REDlogFilm")
933     configData['colorSpaces'].append(REDlogFilmREDcolor2)
934
935     REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "REDlogFilm", name="REDlogFilm")
936     configData['colorSpaces'].append(REDlogFilmREDcolor3)
937
938     # Linearization only
939     REDlogFilmDRAGON = createREDlogFilm("", "REDlogFilm", name="REDlogFilm")
940     configData['colorSpaces'].append(REDlogFilmDRAGON)
941
942     # Primaries only
943     REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "", name="REDlogFilm")
944     configData['colorSpaces'].append(REDlogFilmDRAGON)
945
946     REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "", name="REDlogFilm")
947     configData['colorSpaces'].append(REDlogFilmREDcolor2)
948
949     REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "", name="REDlogFilm")
950     configData['colorSpaces'].append(REDlogFilmREDcolor3)
951
952     #
953     # Canon-Log to ACES
954     #
955     def createCanonLog(gamut, transferFunction, name='Canon-Log'):
956         name = "%s - %s" % (transferFunction, gamut)
957         if transferFunction == "":
958             name = "Linear - %s" % gamut
959         if gamut == "":
960             name = "%s" % transferFunction
961
962         cs = ColorSpace(name)
963         cs.description = name
964         cs.equalityGroup = ''
965         cs.family = 'Canon'
966         cs.isData=False
967
968         def legalToFull(codeValue):
969             return (codeValue - 64.0)/(940.0 - 64.0)
970
971         def canonLogToLinear(codeValue):
972             # log = fullToLegal(c1 * log10(c2*linear + 1) + c3)
973             # linear = (pow(10, (legalToFul(log) - c3)/c1) - 1)/c2
974             c1 = 0.529136
975             c2 = 10.1596
976             c3 = 0.0730597
977
978             linear = (pow(10.0, (legalToFull(codeValue) - c3)/c1) -1.0)/c2
979             linear = 0.9 * linear
980             #print( codeValue, linear )
981             return linear
982
983         cs.toReferenceTransforms = []
984
985         if transferFunction == "Canon-Log":
986             data = array.array('f', "\0" * lutResolution1d * 4)
987             for c in range(lutResolution1d):
988                 data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1))
989
990             lut = "%s_to_linear.spi1d" % transferFunction
991             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
992
993             cs.toReferenceTransforms.append( {
994                 'type':'lutFile', 
995                 'path':lut, 
996                 'interpolation':'linear', 
997                 'direction':'forward'
998             } )
999
1000         if gamut == 'Rec. 709 Daylight':
1001             cs.toReferenceTransforms.append( {
1002                 'type':'matrix',
1003                 'matrix':[0.561538969, 0.402060105, 0.036400926, 0.0, 
1004                             0.092739623, 0.924121198, -0.016860821, 0.0, 
1005                             0.084812961, 0.006373835, 0.908813204, 0.0, 
1006                             0,0,0,1.0],
1007                 'direction':'forward'
1008             })
1009         elif gamut == 'Rec. 709 Tungsten':
1010             cs.toReferenceTransforms.append( {
1011                 'type':'matrix',
1012                 'matrix':[0.566996399, 0.365079418, 0.067924183, 0.0, 
1013                             0.070901044, 0.880331008, 0.048767948, 0.0, 
1014                             0.073013542, -0.066540862, 0.99352732, 0.0, 
1015                             0,0,0,1.0],
1016                 'direction':'forward'
1017             })
1018         elif gamut == 'DCI-P3 Daylight':
1019             cs.toReferenceTransforms.append( {
1020                 'type':'matrix',
1021                 'matrix':[0.607160575, 0.299507286, 0.093332140, 0.0, 
1022                             0.004968120, 1.050982224, -0.055950343, 0.0, 
1023                             -0.007839939, 0.000809127, 1.007030813, 0.0, 
1024                             0,0,0,1.0],
1025                 'direction':'forward'
1026             })
1027         elif gamut == 'DCI-P3 Tungsten':
1028             cs.toReferenceTransforms.append( {
1029                 'type':'matrix',
1030                 'matrix':[0.650279125, 0.253880169, 0.095840706, 0.0, 
1031                             -0.026137986, 1.017900530, 0.008237456, 0.0, 
1032                             0.007757558, -0.063081669, 1.055324110, 0.0, 
1033                             0,0,0,1.0],
1034                 'direction':'forward'
1035             })
1036         elif gamut == 'Cinema Gamut Daylight':
1037             cs.toReferenceTransforms.append( {
1038                 'type':'matrix',
1039                 'matrix':[0.763064455, 0.149021161, 0.087914384, 0.0, 
1040                             0.003657457, 1.10696038, -0.110617837, 0.0, 
1041                             -0.009407794,-0.218383305, 1.227791099, 0.0, 
1042                             0,0,0,1.0],
1043                 'direction':'forward'
1044             })
1045         elif gamut == 'Cinema Gamut Tungsten':
1046             cs.toReferenceTransforms.append( {
1047                 'type':'matrix',
1048                 'matrix':[0.817416293, 0.090755698, 0.091828009, 0.0, 
1049                             -0.035361374, 1.065690585, -0.030329211, 0.0, 
1050                             0.010390366, -0.299271107, 1.288880741, 0.0, 
1051                             0,0,0,1.0],
1052                 'direction':'forward'
1053             })
1054
1055         cs.fromReferenceTransforms = []
1056         return cs
1057
1058     # Full conversion
1059     CanonLog1 = createCanonLog("Rec. 709 Daylight", "Canon-Log", name="Canon-Log")
1060     configData['colorSpaces'].append(CanonLog1)
1061
1062     CanonLog2 = createCanonLog("Rec. 709 Tungsten", "Canon-Log", name="Canon-Log")
1063     configData['colorSpaces'].append(CanonLog2)
1064
1065     CanonLog3 = createCanonLog("DCI-P3 Daylight", "Canon-Log", name="Canon-Log")
1066     configData['colorSpaces'].append(CanonLog3)
1067
1068     CanonLog4 = createCanonLog("DCI-P3 Tungsten", "Canon-Log", name="Canon-Log")
1069     configData['colorSpaces'].append(CanonLog4)
1070
1071     CanonLog5 = createCanonLog("Cinema Gamut Daylight", "Canon-Log", name="Canon-Log")
1072     configData['colorSpaces'].append(CanonLog5)
1073
1074     CanonLog6 = createCanonLog("Cinema Gamut Tungsten", "Canon-Log", name="Canon-Log")
1075     configData['colorSpaces'].append(CanonLog6)
1076
1077     # Linearization only
1078     CanonLog7 = createCanonLog('', "Canon-Log", name="Canon-Log")
1079     configData['colorSpaces'].append(CanonLog7)
1080
1081     # Primaries only
1082     CanonLog8 = createCanonLog("Rec. 709 Daylight", "", name="Canon-Log")
1083     configData['colorSpaces'].append(CanonLog8)
1084
1085     CanonLog9 = createCanonLog("Rec. 709 Tungsten", "", name="Canon-Log")
1086     configData['colorSpaces'].append(CanonLog9)
1087
1088     CanonLog10 = createCanonLog("DCI-P3 Daylight", "", name="Canon-Log")
1089     configData['colorSpaces'].append(CanonLog10)
1090
1091     CanonLog11 = createCanonLog("DCI-P3 Tungsten", "", name="Canon-Log")
1092     configData['colorSpaces'].append(CanonLog11)
1093
1094     CanonLog12 = createCanonLog("Cinema Gamut Daylight", "", name="Canon-Log")
1095     configData['colorSpaces'].append(CanonLog12)
1096
1097     CanonLog13 = createCanonLog("Cinema Gamut Tungsten", "", name="Canon-Log")
1098     configData['colorSpaces'].append(CanonLog13)
1099
1100     #
1101     # SLog to ACES
1102     #
1103     def createSlog(gamut, transferFunction, name='S-Log3'):
1104         name = "%s - %s" % (transferFunction, gamut)
1105         if transferFunction == "":
1106             name = "Linear - %s" % gamut
1107         if gamut == "":
1108             name = "%s" % transferFunction
1109
1110         cs = ColorSpace(name)
1111         cs.description = name
1112         cs.equalityGroup = ''
1113         cs.family = 'Sony'
1114         cs.isData=False
1115
1116         def sLog1ToLinear(SLog):
1117             b = 64.
1118             ab = 90.
1119             w = 940.
1120
1121             if (SLog >= ab):
1122                 lin = ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) * 0.9
1123             else:
1124                 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 5.) * 0.9 
1125             return lin
1126
1127         def sLog2ToLinear(SLog):
1128             b = 64.
1129             ab = 90.
1130             w = 940.
1131
1132             if (SLog >= ab):
1133                 lin = ( 219. * ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) / 155.) * 0.9
1134             else:
1135                 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 3.53881278538813) * 0.9
1136             return lin
1137
1138         def sLog3ToLinear(codeValue):
1139             if codeValue >= (171.2102946929):
1140                 linear = pow(10.0, ((codeValue - 420.0) / 261.5)) * (0.18 + 0.01) - 0.01
1141             else:
1142                 linear = (codeValue - 95.0)*0.01125000/(171.2102946929 - 95.0)
1143             #print( codeValue, linear )
1144             return linear
1145
1146         cs.toReferenceTransforms = []
1147
1148         if transferFunction == "S-Log1":
1149             data = array.array('f', "\0" * lutResolution1d * 4)
1150             for c in range(lutResolution1d):
1151                 data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1))
1152
1153             lut = "%s_to_linear.spi1d" % transferFunction
1154             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1155
1156             #print( "Writing %s" % lut)
1157
1158             cs.toReferenceTransforms.append( {
1159                 'type':'lutFile', 
1160                 'path':lut, 
1161                 'interpolation':'linear', 
1162                 'direction':'forward'
1163             } )
1164         elif transferFunction == "S-Log2":
1165             data = array.array('f', "\0" * lutResolution1d * 4)
1166             for c in range(lutResolution1d):
1167                 data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1))
1168
1169             lut = "%s_to_linear.spi1d" % transferFunction
1170             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1171
1172             #print( "Writing %s" % lut)
1173
1174             cs.toReferenceTransforms.append( {
1175                 'type':'lutFile', 
1176                 'path':lut, 
1177                 'interpolation':'linear', 
1178                 'direction':'forward'
1179             } )
1180         elif transferFunction == "S-Log3":
1181             data = array.array('f', "\0" * lutResolution1d * 4)
1182             for c in range(lutResolution1d):
1183                 data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1))
1184
1185             lut = "%s_to_linear.spi1d" % transferFunction
1186             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1187
1188             #print( "Writing %s" % lut)
1189
1190             cs.toReferenceTransforms.append( {
1191                 'type':'lutFile', 
1192                 'path':lut, 
1193                 'interpolation':'linear', 
1194                 'direction':'forward'
1195             } )
1196
1197         if gamut == 'S-Gamut':
1198             cs.toReferenceTransforms.append( {
1199                 'type':'matrix',
1200                 'matrix':mat44FromMat33([0.754338638, 0.133697046, 0.111968437,
1201                                         0.021198141, 1.005410934, -0.026610548, 
1202                                         -0.009756991, 0.004508563, 1.005253201]),
1203                 'direction':'forward'
1204             })
1205         elif gamut == 'S-Gamut Daylight':
1206             cs.toReferenceTransforms.append( {
1207                 'type':'matrix',
1208                 'matrix':mat44FromMat33([0.8764457030, 0.0145411681, 0.1090131290,
1209                                         0.0774075345, 0.9529571767, -0.0303647111, 
1210                                         0.0573564351, -0.1151066335, 1.0577501984]),
1211                 'direction':'forward'
1212             })
1213         elif gamut == 'S-Gamut Tungsten':
1214             cs.toReferenceTransforms.append( {
1215                 'type':'matrix',
1216                 'matrix':mat44FromMat33([1.0110238740, -0.1362526051, 0.1252287310, 
1217                             0.1011994504, 0.9562196265, -0.0574190769,
1218                             0.0600766530, -0.1010185315, 1.0409418785]),
1219                 'direction':'forward'
1220             })
1221         elif gamut == 'S-Gamut3.Cine':
1222             cs.toReferenceTransforms.append( {
1223                 'type':'matrix',
1224                 'matrix':mat44FromMat33([0.6387886672, 0.2723514337, 0.0888598992, 
1225                                         -0.0039159061, 1.0880732308, -0.0841573249, 
1226                                         -0.0299072021, -0.0264325799, 1.0563397820]),
1227                 'direction':'forward'
1228             })
1229         elif gamut == 'S-Gamut3':
1230             cs.toReferenceTransforms.append( {
1231                 'type':'matrix',
1232                 'matrix':mat44FromMat33([0.7529825954, 0.1433702162, 0.1036471884, 
1233                             0.0217076974, 1.0153188355, -0.0370265329, 
1234                             -0.0094160528, 0.0033704179, 1.0060456349]),
1235                 'direction':'forward'
1236             })
1237
1238         cs.fromReferenceTransforms = []
1239         return cs
1240
1241     # SLog1
1242     SLog1SGamut = createSlog("S-Gamut", "S-Log1", name="S-Log")
1243     configData['colorSpaces'].append(SLog1SGamut)
1244
1245     # SLog2
1246     SLog2SGamut = createSlog("S-Gamut", "S-Log2", name="S-Log2")
1247     configData['colorSpaces'].append(SLog2SGamut)
1248
1249     SLog2SGamutDaylight = createSlog("S-Gamut Daylight", "S-Log2", name="S-Log2")
1250     configData['colorSpaces'].append(SLog2SGamutDaylight)
1251
1252     SLog2SGamutTungsten = createSlog("S-Gamut Tungsten", "S-Log2", name="S-Log2")
1253     configData['colorSpaces'].append(SLog2SGamutTungsten)
1254
1255     # SLog3
1256     SLog3SGamut3Cine = createSlog("S-Gamut3.Cine", "S-Log3", name="S-Log3")
1257     configData['colorSpaces'].append(SLog3SGamut3Cine)
1258
1259     SLog3SGamut3 = createSlog("S-Gamut3", "S-Log3", name="S-Log3")
1260     configData['colorSpaces'].append(SLog3SGamut3)
1261
1262     # Linearization only
1263     SLog1 = createSlog("", "S-Log1", name="S-Log")
1264     configData['colorSpaces'].append(SLog1)
1265
1266     SLog2 = createSlog("", "S-Log2", name="S-Log2")
1267     configData['colorSpaces'].append(SLog2)
1268
1269     SLog3 = createSlog("", "S-Log3", name="S-Log3")
1270     configData['colorSpaces'].append(SLog3)
1271
1272     # Primaries only
1273     SGamut = createSlog("S-Gamut", "", name="S-Log")
1274     configData['colorSpaces'].append(SGamut)
1275
1276     SGamutDaylight = createSlog("S-Gamut Daylight", "", name="S-Log2")
1277     configData['colorSpaces'].append(SGamutDaylight)
1278
1279     SGamutTungsten = createSlog("S-Gamut Tungsten", "", name="S-Log2")
1280     configData['colorSpaces'].append(SGamutTungsten)
1281
1282     SGamut3Cine = createSlog("S-Gamut3.Cine", "", name="S-Log3")
1283     configData['colorSpaces'].append(SGamut3Cine)
1284
1285     SGamut3 = createSlog("S-Gamut3", "", name="S-Log3")
1286     configData['colorSpaces'].append(SGamut3)
1287
1288     #
1289     # LogC to ACES
1290     #
1291     def createLogC(gamut, transferFunction, exposureIndex, name='LogC'):
1292         name = "%s (EI%s) - %s" % (transferFunction, exposureIndex, gamut)
1293         if transferFunction == "":
1294             name = "Linear - %s" % gamut
1295         if gamut == "":
1296             name = "%s (EI%s)" % (transferFunction, exposureIndex)
1297
1298         cs = ColorSpace(name)
1299         cs.description = name
1300         cs.equalityGroup = ''
1301         cs.family = 'ARRI'
1302         cs.isData=False
1303
1304         # Globals
1305         IDT_maker_version = "0.08"
1306
1307         nominalEI = 400.0
1308         blackSignal = 0.003907
1309         midGraySignal = 0.01
1310         encodingGain = 0.256598
1311         encodingOffset = 0.391007
1312
1313         def gainForEI(EI) :
1314             return (math.log(EI/nominalEI)/math.log(2) * (0.89 - 1) / 3 + 1) * encodingGain
1315
1316         def LogCInverseParametersForEI(EI) :
1317             cut = 1.0 / 9.0
1318             slope = 1.0 / (cut * math.log(10))
1319             offset = math.log10(cut) - slope * cut
1320             gain = EI / nominalEI
1321             gray = midGraySignal / gain
1322             # The higher the EI, the lower the gamma
1323             encGain = gainForEI(EI)
1324             encOffset = encodingOffset
1325             for i in range(0,3) :
1326                 nz = ((95.0 / 1023.0 - encOffset) / encGain - offset) / slope
1327                 encOffset = encodingOffset - math.log10(1 + nz) * encGain
1328             # Calculate some intermediate values
1329             a = 1.0 / gray
1330             b = nz - blackSignal / gray
1331             e = slope * a * encGain
1332             f = encGain * (slope * b + offset) + encOffset
1333             # Manipulations so we can return relative exposure
1334             s = 4 / (0.18 * EI)
1335             t = blackSignal
1336             b = b + a * t
1337             a = a * s
1338             f = f + e * t
1339             e = e * s
1340             return { 'a' : a,
1341                      'b' : b,
1342                      'cut' : (cut - b) / a,
1343                      'c' : encGain,
1344                      'd' : encOffset,
1345                      'e' : e,
1346                      'f' : f }
1347
1348         def logCtoLinear(codeValue, exposureIndex):
1349             p = LogCInverseParametersForEI(exposureIndex)
1350             breakpoint = p['e'] * p['cut'] + p['f']
1351             if (codeValue > breakpoint):
1352                 linear = (pow(10,(codeValue/1023.0 - p['d']) / p['c']) - p['b']) / p['a']
1353             else:
1354                 linear = (codeValue/1023.0 - p['f']) / p['e']
1355
1356             #print( codeValue, linear )
1357             return linear
1358
1359
1360         cs.toReferenceTransforms = []
1361
1362         if transferFunction == "V3 LogC":
1363             data = array.array('f', "\0" * lutResolution1d * 4)
1364             for c in range(lutResolution1d):
1365                 data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex))
1366
1367             lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex))
1368             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1369
1370             #print( "Writing %s" % lut)
1371             cs.toReferenceTransforms.append( {
1372                 'type':'lutFile', 
1373                 'path':lut, 
1374                 'interpolation':'linear', 
1375                 'direction':'forward'
1376             } )
1377
1378         if gamut == 'Wide Gamut':
1379             cs.toReferenceTransforms.append( {
1380                 'type':'matrix',
1381                 'matrix':mat44FromMat33([0.680206, 0.236137, 0.083658, 
1382                             0.085415, 1.017471, -0.102886, 
1383                             0.002057, -0.062563, 1.060506]),
1384                 'direction':'forward'
1385             })
1386
1387         cs.fromReferenceTransforms = []
1388         return cs
1389
1390     transferFunction = "V3 LogC"
1391     gamut = "Wide Gamut"
1392     #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]
1393     EIs = [160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600, 2000, 2560, 3200]
1394     defaultEI = 800
1395
1396     # Full conversion
1397     for EI in EIs:
1398         LogCEIfull = createLogC(gamut, transferFunction, EI, name="LogC")
1399         configData['colorSpaces'].append(LogCEIfull)
1400
1401     # Linearization only
1402     for EI in [800]:
1403         LogCEIlinearization = createLogC("", transferFunction, EI, name="LogC")
1404         configData['colorSpaces'].append(LogCEIlinearization)
1405
1406     # Primaries
1407     LogCEIprimaries = createLogC(gamut, "", defaultEI, name="LogC")
1408     configData['colorSpaces'].append(LogCEIprimaries)
1409
1410     #
1411     # Generic log transform
1412     #
1413     def createGenericLog(name='log', 
1414         minValue=0.0, 
1415         maxValue=1.0, 
1416         inputScale=1.0,
1417         middleGrey=0.18,
1418         minExposure=-6.0,
1419         maxExposure=6.5,
1420         lutResolution1d=lutResolution1d):
1421         cs = ColorSpace(name)
1422         cs.description = "The %s color space" % name
1423         cs.equalityGroup = name
1424         cs.family = 'Utility'
1425         cs.isData=False
1426
1427         ctls = [
1428             #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
1429             '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
1430         ]
1431         lut = "%s_to_aces.spi1d" % name
1432
1433         generate1dLUTFromCTL( lutDir + "/" + lut, 
1434             ctls, 
1435             lutResolution1d, 
1436             'float', 
1437             inputScale,
1438             1.0, 
1439             {
1440                 'middleGrey'  : middleGrey,
1441                 'minExposure' : minExposure,
1442                 'maxExposure' : maxExposure
1443             },
1444             cleanup, 
1445             acesCTLReleaseDir,
1446             minValue,
1447             maxValue)
1448
1449         cs.toReferenceTransforms = []
1450         cs.toReferenceTransforms.append( {
1451             'type':'lutFile', 
1452             'path':lut, 
1453             'interpolation':'linear', 
1454             'direction':'forward'
1455         } )
1456
1457         cs.fromReferenceTransforms = []
1458         return cs
1459
1460     #
1461     # ACES LMTs
1462     #
1463     def createACESLMT(lmtName, 
1464         lmtValues,
1465         shaperInfo,
1466         lutResolution1d=1024, 
1467         lutResolution3d=64, 
1468         cleanup=True):
1469         cs = ColorSpace("%s" % lmtName)
1470         cs.description = "The ACES Look Transform: %s" % lmtName
1471         cs.equalityGroup = ''
1472         cs.family = 'Look'
1473         cs.isData=False
1474
1475         import pprint
1476         pprint.pprint( lmtValues )
1477
1478         #
1479         # Generate the shaper transform
1480         #
1481         (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1482
1483         shaperLut = "%s_to_aces.spi1d" % shaperName
1484         if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1485             ctls = [
1486                 shaperToACESCTL % acesCTLReleaseDir
1487             ]
1488             generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
1489                 ctls, 
1490                 lutResolution1d, 
1491                 'float', 
1492                 1.0/shaperInputScale,
1493                 1.0, 
1494                 shaperParams,
1495                 cleanup, 
1496                 acesCTLReleaseDir)
1497
1498         shaperOCIOTransform = {
1499             'type':'lutFile', 
1500             'path':shaperLut, 
1501             'interpolation':'linear', 
1502             'direction':'inverse'
1503         }
1504
1505         #
1506         # Generate the forward transform
1507         #
1508         cs.fromReferenceTransforms = []
1509
1510         if 'transformCTL' in lmtValues:
1511             ctls = [
1512                 shaperToACESCTL % acesCTLReleaseDir, 
1513                 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
1514             ]
1515             lut = "%s.%s.spi3d" % (shaperName, lmtName)
1516
1517             generate3dLUTFromCTL( lutDir + "/" + lut, 
1518                 ctls, 
1519                 lutResolution3d, 
1520                 'float', 
1521                 1.0/shaperInputScale,
1522                 1.0, 
1523                 shaperParams,
1524                 cleanup, 
1525                 acesCTLReleaseDir )
1526
1527             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1528             cs.fromReferenceTransforms.append( {
1529                 'type':'lutFile', 
1530                 'path':lut, 
1531                 'interpolation':'tetrahedral', 
1532                 'direction':'forward'
1533             } )
1534
1535         #
1536         # Generate the inverse transform
1537         #
1538         cs.toReferenceTransforms = []
1539
1540         if 'transformCTLInverse' in lmtValues:
1541             ctls = [
1542                 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1543                 shaperFromACESCTL % acesCTLReleaseDir
1544             ]
1545             lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
1546
1547             generate3dLUTFromCTL( lutDir + "/" + lut, 
1548                 ctls, 
1549                 lutResolution3d, 
1550                 'half', 
1551                 1.0,
1552                 shaperInputScale, 
1553                 shaperParams,
1554                 cleanup, 
1555                 acesCTLReleaseDir )
1556
1557             cs.toReferenceTransforms.append( {
1558                 'type':'lutFile', 
1559                 'path':lut, 
1560                 'interpolation':'tetrahedral', 
1561                 'direction':'forward'
1562             } )
1563
1564             shaperInverse = shaperOCIOTransform.copy()
1565             shaperInverse['direction'] = 'forward'
1566             cs.toReferenceTransforms.append( shaperInverse )
1567
1568         return cs
1569
1570     #
1571     # LMT Shaper
1572     #
1573
1574     lmtLutResolution1d = max(4096, lutResolution1d)
1575     lmtLutResolution3d = max(65, lutResolution3d)
1576
1577     # Log 2 shaper
1578     lmtShaperName = 'lmtShaper'
1579     lmtParams = {
1580         'middleGrey'  : 0.18,
1581         'minExposure' : -10.0,
1582         'maxExposure' : 6.5
1583     }
1584     lmtShaper = createGenericLog(name=lmtShaperName, 
1585         middleGrey=lmtParams['middleGrey'], 
1586         minExposure=lmtParams['minExposure'], 
1587         maxExposure=lmtParams['maxExposure'],
1588         lutResolution1d=lmtLutResolution1d)
1589     configData['colorSpaces'].append(lmtShaper)
1590
1591     shaperInputScale_genericLog2 = 1.0
1592
1593     # Log 2 shaper name and CTL transforms bundled up
1594     lmtShaperData = [
1595         lmtShaperName, 
1596         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1597         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1598         #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1599         #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1600         shaperInputScale_genericLog2,
1601         lmtParams
1602     ]
1603
1604     sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
1605     print( sortedLMTs )
1606     for lmt in sortedLMTs:
1607         (lmtName, lmtValues) = lmt
1608         cs = createACESLMT(
1609             lmtValues['transformUserName'], 
1610             lmtValues,
1611             lmtShaperData,
1612             lmtLutResolution1d,
1613             lmtLutResolution3d,
1614             cleanup)
1615         configData['colorSpaces'].append(cs)
1616
1617     #
1618     # ACES RRT with the supplied ODT
1619     #
1620     def createACESRRTplusODT(odtName, 
1621         odtValues,
1622         shaperInfo,
1623         lutResolution1d=1024, 
1624         lutResolution3d=64, 
1625         cleanup=True):
1626         cs = ColorSpace("%s" % odtName)
1627         cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
1628         cs.equalityGroup = ''
1629         cs.family = 'Output'
1630         cs.isData=False
1631
1632         import pprint
1633         pprint.pprint( odtValues )
1634
1635         #
1636         # Generate the shaper transform
1637         #
1638         #if 'shaperCTL' in odtValues:
1639         (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1640
1641         if 'legalRange' in odtValues:
1642             shaperParams['legalRange'] = odtValues['legalRange']
1643         else:
1644             shaperParams['legalRange'] = 0
1645
1646         shaperLut = "%s_to_aces.spi1d" % shaperName
1647         if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1648             ctls = [
1649                 shaperToACESCTL % acesCTLReleaseDir
1650             ]
1651             generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
1652                 ctls, 
1653                 lutResolution1d, 
1654                 'float', 
1655                 1.0/shaperInputScale,
1656                 1.0, 
1657                 shaperParams,
1658                 cleanup, 
1659                 acesCTLReleaseDir)
1660
1661         shaperOCIOTransform = {
1662             'type':'lutFile', 
1663             'path':shaperLut, 
1664             'interpolation':'linear', 
1665             'direction':'inverse'
1666         }
1667
1668         #
1669         # Generate the forward transform
1670         #
1671         cs.fromReferenceTransforms = []
1672
1673         if 'transformLUT' in odtValues:
1674             # Copy into the lut dir
1675             transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1676             lut = lutDir + "/" + transformLUTFileName
1677             shutil.copy(odtValues['transformLUT'], lut)
1678
1679             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1680             cs.fromReferenceTransforms.append( {
1681                 'type':'lutFile', 
1682                 'path': transformLUTFileName, 
1683                 'interpolation':'tetrahedral', 
1684                 'direction':'forward'
1685             } )
1686         elif 'transformCTL' in odtValues:
1687             #shaperLut
1688
1689             ctls = [
1690                 shaperToACESCTL % acesCTLReleaseDir, 
1691                 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir, 
1692                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1693             ]
1694             lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1695
1696             generate3dLUTFromCTL( lutDir + "/" + lut, 
1697                 #shaperLUT,
1698                 ctls, 
1699                 lutResolution3d, 
1700                 'float', 
1701                 1.0/shaperInputScale,
1702                 1.0, 
1703                 shaperParams,
1704                 cleanup, 
1705                 acesCTLReleaseDir )
1706
1707             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1708             cs.fromReferenceTransforms.append( {
1709                 'type':'lutFile', 
1710                 'path':lut, 
1711                 'interpolation':'tetrahedral', 
1712                 'direction':'forward'
1713             } )
1714
1715         #
1716         # Generate the inverse transform
1717         #
1718         cs.toReferenceTransforms = []
1719
1720         if 'transformLUTInverse' in odtValues:
1721             # Copy into the lut dir
1722             transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
1723             lut = lutDir + "/" + transformLUTInverseFileName
1724             shutil.copy(odtValues['transformLUTInverse'], lut)
1725
1726             cs.toReferenceTransforms.append( {
1727                 'type':'lutFile', 
1728                 'path': transformLUTInverseFileName, 
1729                 'interpolation':'tetrahedral', 
1730                 'direction':'forward'
1731             } )
1732
1733             shaperInverse = shaperOCIOTransform.copy()
1734             shaperInverse['direction'] = 'forward'
1735             cs.toReferenceTransforms.append( shaperInverse )
1736         elif 'transformCTLInverse' in odtValues:
1737             ctls = [
1738                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1739                 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1740                 shaperFromACESCTL % acesCTLReleaseDir
1741             ]
1742             lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1743
1744             generate3dLUTFromCTL( lutDir + "/" + lut, 
1745                 #None,
1746                 ctls, 
1747                 lutResolution3d, 
1748                 'half', 
1749                 1.0,
1750                 shaperInputScale, 
1751                 shaperParams,
1752                 cleanup, 
1753                 acesCTLReleaseDir )
1754
1755             cs.toReferenceTransforms.append( {
1756                 'type':'lutFile', 
1757                 'path':lut, 
1758                 'interpolation':'tetrahedral', 
1759                 'direction':'forward'
1760             } )
1761
1762             shaperInverse = shaperOCIOTransform.copy()
1763             shaperInverse['direction'] = 'forward'
1764             cs.toReferenceTransforms.append( shaperInverse )
1765
1766         return cs
1767
1768     #
1769     # RRT/ODT shaper options
1770     #
1771     shaperData = {}
1772
1773     # Log 2 shaper
1774     log2ShaperName = shaperName
1775     log2Params = {
1776         'middleGrey'  : 0.18,
1777         'minExposure' : -6.0,
1778         'maxExposure' : 6.5
1779     }
1780     log2Shaper = createGenericLog(name=log2ShaperName, 
1781         middleGrey=log2Params['middleGrey'], 
1782         minExposure=log2Params['minExposure'], 
1783         maxExposure=log2Params['maxExposure'])
1784     configData['colorSpaces'].append(log2Shaper)
1785
1786     shaperInputScale_genericLog2 = 1.0
1787
1788     # Log 2 shaper name and CTL transforms bundled up
1789     log2ShaperData = [
1790         log2ShaperName, 
1791         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1792         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1793         #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1794         #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1795         shaperInputScale_genericLog2,
1796         log2Params
1797     ]
1798
1799     shaperData[log2ShaperName] = log2ShaperData
1800
1801     #
1802     # Shaper that also includes the AP1 primaries
1803     # - Needed for some LUT baking steps
1804     #
1805     log2ShaperAP1 = createGenericLog(name=log2ShaperName, 
1806         middleGrey=log2Params['middleGrey'], 
1807         minExposure=log2Params['minExposure'], 
1808         maxExposure=log2Params['maxExposure'])
1809     log2ShaperAP1.name = "%s AP1" % log2ShaperAP1.name
1810     # AP1 primaries to AP0 primaries
1811     log2ShaperAP1.toReferenceTransforms.append( {
1812         'type':'matrix',
1813         'matrix':mat44FromMat33(acesAP1toAP0),
1814         'direction':'forward'
1815     })
1816     configData['colorSpaces'].append(log2ShaperAP1)
1817
1818     #
1819     # Choose your shaper
1820     #
1821     # XXX
1822     # Shaper name. Should really be automated or made a user choice
1823     #
1824     # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
1825     #shaperName = 'log2Shaper'
1826
1827     #if shaperName in shaperData:
1828     #    rrtShaperName = shaperName
1829     #    rrtShaper = shaperData[shaperName]
1830     #else:
1831
1832     rrtShaperName = log2ShaperName
1833     rrtShaper = log2ShaperData
1834
1835     #
1836     # RRT + ODT Combinations
1837     #
1838     #for odtName, odtValues in odtInfo.iteritems():
1839     sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1840     print( sortedOdts )
1841     for odt in sortedOdts:
1842         (odtName, odtValues) = odt
1843
1844         # Have to handle ODTs that can generate either legal or full output
1845         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1846                 'Academy.Rec709_100nits_dim.a1.0.0',
1847                 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1848             odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1849         else:
1850             odtNameLegal = odtValues['transformUserName']
1851
1852         odtLegal = odtValues.copy()
1853         odtLegal['legalRange'] = 1
1854
1855         cs = createACESRRTplusODT(
1856             odtNameLegal, 
1857             odtLegal,
1858             rrtShaper,
1859             lutResolution1d,
1860             lutResolution3d,
1861             cleanup)
1862         configData['colorSpaces'].append(cs)
1863
1864         # Create a display entry using this color space
1865         configData['displays'][odtNameLegal] = { 
1866             'Linear':ACES, 
1867             'Log':ACEScc, 
1868             'Output Transform':cs }
1869
1870         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1871                 'Academy.Rec709_100nits_dim.a1.0.0', 
1872                 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1873
1874             print( "Generating full range ODT for %s" % odtName)
1875
1876             odtNameFull = "%s - Full" % odtValues['transformUserName']
1877             odtFull = odtValues.copy()
1878             odtFull['legalRange'] = 0
1879
1880             csFull = createACESRRTplusODT(
1881                 odtNameFull, 
1882                 odtFull,
1883                 rrtShaper,
1884                 lutResolution1d,
1885                 lutResolution3d,
1886                 cleanup)
1887             configData['colorSpaces'].append(csFull)
1888
1889             # Create a display entry using this color space
1890             configData['displays'][odtNameFull] = { 
1891                 'Linear':ACES, 
1892                 'Log':ACEScc, 
1893                 'Output Transform':csFull }
1894
1895
1896     print( "generateLUTs - end" )
1897     return configData
1898
1899 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1900
1901     # Add the legal and full variations into this list
1902     odtInfoC = dict(odtInfo)
1903     for odtCTLName, odtValues in odtInfo.iteritems():
1904         if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1905             'Academy.Rec709_100nits_dim.a1.0.0',
1906             'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1907                 odtName = odtValues["transformUserName"]
1908
1909                 odtValuesLegal = dict(odtValues)
1910                 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1911                 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1912
1913                 odtValuesFull = dict(odtValues)
1914                 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1915                 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1916                 
1917                 del( odtInfoC[odtCTLName] )
1918
1919     for odtCTLName, odtValues in odtInfoC.iteritems():
1920         odtPrefix = odtValues["transformUserNamePrefix"]
1921         odtName = odtValues["transformUserName"]
1922
1923         # For Photoshop
1924         for inputspace in ["ACEScc", "ACESproxy"]:
1925             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1926             args += ["--outputspace", "%s" % odtName ]
1927             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1928             args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
1929             args += ["--cubesize", str(lutResolution3d) ]
1930             args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1931
1932             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1933             bakeLUT.execute()    
1934
1935         # For Flame, Lustre
1936         for inputspace in ["ACEScc", "ACESproxy"]:
1937             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1938             args += ["--outputspace", "%s" % odtName ]
1939             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1940             args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
1941             args += ["--cubesize", str(lutResolution3d) ]
1942
1943             fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1944             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1945             bakeLUT.execute()    
1946
1947             largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1948             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1949             bakeLUT.execute()
1950
1951         # For Maya, Houdini
1952         for inputspace in ["ACEScg", "ACES2065-1"]:
1953             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1954             args += ["--outputspace", "%s" % odtName ]
1955             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1956             if inputspace == 'ACEScg':
1957                 linShaperName = "%s AP1" % shaperName 
1958             else:
1959                 linShaperName = shaperName
1960             args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ] 
1961             
1962             args += ["--cubesize", str(lutResolution3d) ]
1963
1964             margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
1965             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
1966             bakeLUT.execute()    
1967
1968             hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
1969             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
1970             bakeLUT.execute()    
1971
1972
1973 def createConfigDir(configDir, bakeSecondaryLUTs):
1974     dirs = [configDir, "%s/luts" % configDir]
1975     if bakeSecondaryLUTs:
1976         dirs.extend(["%s/baked" % configDir, 
1977             "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
1978             "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
1979             "%s/baked/maya" % configDir])
1980
1981     for d in dirs:
1982         if not os.path.exists(d):
1983             os.mkdir(d)
1984
1985 def getTransformInfo(ctlTransform):
1986     fp = open(ctlTransform, 'rb')
1987
1988     # Read lines
1989     lines = fp.readlines()
1990
1991     # Grab transform ID and User Name
1992     transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1993     #print( transformID )
1994     transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
1995     transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
1996     #print( transformUserName )
1997     fp.close()
1998
1999     return (transformID, transformUserName, transformUserNamePrefix)
2000
2001 # For versions after WGR9
2002 def getODTInfo(acesCTLReleaseDir):
2003     # Credit to Alex Fry for the original approach here
2004     odtDir = os.path.join(acesCTLReleaseDir, "odt")
2005     allodt = []
2006     for dirName, subdirList, fileList in os.walk(odtDir):
2007         for fname in fileList:
2008             allodt.append((os.path.join(dirName,fname)))
2009
2010     odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
2011
2012     #print odtCTLs
2013
2014     odts = {}
2015
2016     for odtCTL in odtCTLs:
2017         odtTokens = os.path.split(odtCTL)
2018         #print( odtTokens )
2019
2020         # Handle nested directories
2021         odtPathTokens = os.path.split(odtTokens[-2])
2022         odtDir = odtPathTokens[-1]
2023         while odtPathTokens[-2][-3:] != 'odt':
2024             odtPathTokens = os.path.split(odtPathTokens[-2])
2025             odtDir = os.path.join(odtPathTokens[-1], odtDir)
2026
2027         # Build full name
2028         #print( "odtDir : %s" % odtDir )
2029         transformCTL = odtTokens[-1]
2030         #print( transformCTL )
2031         odtName = string.join(transformCTL.split('.')[1:-1], '.')
2032         #print( odtName )
2033
2034         # Find id, user name and user name prefix
2035         (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2036             "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
2037
2038         # Find inverse
2039         transformCTLInverse = "InvODT.%s.ctl" % odtName
2040         if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
2041             transformCTLInverse = None
2042         #print( transformCTLInverse )
2043
2044         # Add to list of ODTs
2045         odts[odtName] = {}
2046         odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
2047         if transformCTLInverse != None:
2048             odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
2049
2050         odts[odtName]['transformID'] = transformID
2051         odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
2052         odts[odtName]['transformUserName'] = transformUserName
2053
2054         print( "ODT : %s" % odtName )
2055         print( "\tTransform ID               : %s" % transformID )
2056         print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2057         print( "\tTransform User Name        : %s" % transformUserName )
2058         print( "\tForward ctl                : %s" % odts[odtName]['transformCTL'])
2059         if 'transformCTLInverse' in odts[odtName]:
2060             print( "\tInverse ctl                : %s" % odts[odtName]['transformCTLInverse'])
2061         else:
2062             print( "\tInverse ctl                : %s" % "None" )
2063
2064     print( "\n" )
2065
2066     return odts
2067
2068 # For versions after WGR9
2069 def getLMTInfo(acesCTLReleaseDir):
2070     # Credit to Alex Fry for the original approach here
2071     lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
2072     alllmt = []
2073     for dirName, subdirList, fileList in os.walk(lmtDir):
2074         for fname in fileList:
2075             alllmt.append((os.path.join(dirName,fname)))
2076
2077     lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
2078
2079     #print lmtCTLs
2080
2081     lmts = {}
2082
2083     for lmtCTL in lmtCTLs:
2084         lmtTokens = os.path.split(lmtCTL)
2085         #print( lmtTokens )
2086
2087         # Handle nested directories
2088         lmtPathTokens = os.path.split(lmtTokens[-2])
2089         lmtDir = lmtPathTokens[-1]
2090         while lmtPathTokens[-2][-3:] != 'ctl':
2091             lmtPathTokens = os.path.split(lmtPathTokens[-2])
2092             lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
2093
2094         # Build full name
2095         #print( "lmtDir : %s" % lmtDir )
2096         transformCTL = lmtTokens[-1]
2097         #print( transformCTL )
2098         lmtName = string.join(transformCTL.split('.')[1:-1], '.')
2099         #print( lmtName )
2100
2101         # Find id, user name and user name prefix
2102         (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2103             "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
2104
2105         # Find inverse
2106         transformCTLInverse = "InvLMT.%s.ctl" % lmtName
2107         if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
2108             transformCTLInverse = None
2109         #print( transformCTLInverse )
2110
2111         # Add to list of LMTs
2112         lmts[lmtName] = {}
2113         lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
2114         if transformCTLInverse != None:
2115             lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
2116
2117         lmts[lmtName]['transformID'] = transformID
2118         lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
2119         lmts[lmtName]['transformUserName'] = transformUserName
2120
2121         print( "LMT : %s" % lmtName )
2122         print( "\tTransform ID               : %s" % transformID )
2123         print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2124         print( "\tTransform User Name        : %s" % transformUserName )
2125         print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
2126         if 'transformCTLInverse' in lmts[lmtName]:
2127             print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
2128         else:
2129             print( "\t Inverse ctl : %s" % "None" )
2130
2131     print( "\n" )
2132
2133     return lmts
2134
2135 #
2136 # Create the ACES config
2137 #
2138 def createACESConfig(acesCTLReleaseDir, 
2139     configDir, 
2140     lutResolution1d=4096, 
2141     lutResolution3d=64, 
2142     bakeSecondaryLUTs=True,
2143     cleanup=True):
2144
2145     # Get ODT names and CTL paths
2146     odtInfo = getODTInfo(acesCTLReleaseDir)
2147
2148     # Get ODT names and CTL paths
2149     lmtInfo = getLMTInfo(acesCTLReleaseDir)
2150
2151     # Create config dir
2152     createConfigDir(configDir, bakeSecondaryLUTs)
2153
2154     # Generate config data and LUTs for different transforms
2155     lutDir = "%s/luts" % configDir
2156     shaperName = 'outputShaper'
2157     configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
2158     
2159     # Create the config using the generated LUTs
2160     print( "Creating generic config")
2161     config = createConfig(configData)
2162     print( "\n\n\n" )
2163
2164     # Write the config to disk
2165     writeConfig(config, "%s/config.ocio" % configDir )
2166
2167     # Create a config that will work well with Nuke using the previously generated LUTs
2168     print( "Creating Nuke-specific config")
2169     nuke_config = createConfig(configData, nuke=True)
2170     print( "\n\n\n" )
2171
2172     # Write the config to disk
2173     writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
2174
2175     # Bake secondary LUTs using the config
2176     if bakeSecondaryLUTs:
2177         generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
2178
2179 #
2180 # Main
2181 #
2182 def main():
2183     import optparse
2184
2185     p = optparse.OptionParser(description='An OCIO config generation script',
2186                                 prog='createACESConfig',
2187                                 version='createACESConfig 0.1',
2188                                 usage='%prog [options]')
2189     p.add_option('--acesCTLDir', '-a', default=None)
2190     p.add_option('--configDir', '-c', default=None)
2191     p.add_option('--lutResolution1d', default=4096)
2192     p.add_option('--lutResolution3d', default=64)
2193     p.add_option('--dontBakeSecondaryLUTs', action="store_true")
2194     p.add_option('--keepTempImages', action="store_true")
2195
2196     options, arguments = p.parse_args()
2197
2198     #
2199     # Get options
2200     # 
2201     acesCTLDir = options.acesCTLDir
2202     configDir  = options.configDir
2203     lutResolution1d  = int(options.lutResolution1d)
2204     lutResolution3d  = int(options.lutResolution3d)
2205     bakeSecondaryLUTs  = not(options.dontBakeSecondaryLUTs)
2206     cleanupTempImages  = not(options.keepTempImages)
2207
2208     try:
2209         argsStart = sys.argv.index('--') + 1
2210         args = sys.argv[argsStart:]
2211     except:
2212         argsStart = len(sys.argv)+1
2213         args = []
2214
2215     print( "command line : \n%s\n" % " ".join(sys.argv) )
2216
2217     if configDir == None:
2218         print( "process: No ACES CTL directory specified" )
2219         return
2220  
2221     #
2222     # Generate the configuration
2223     #
2224     createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
2225 # main
2226
2227 if __name__ == '__main__':
2228     main()