Added AP0 to XYZ conversion.
[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 == 'REDcolor3':
914             cs.toReferenceTransforms.append( {
915                 'type':'matrix',
916                 'matrix':mat44FromMat33([0.512136, 0.360370, 0.127494, 
917                                         0.070377, 0.903884, 0.025737, 
918                                         -0.020824, 0.017671, 1.003123]),
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
930         cs.fromReferenceTransforms = []
931         return cs
932
933     # Full conversion
934     REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "REDlogFilm", name="REDlogFilm")
935     configData['colorSpaces'].append(REDlogFilmDRAGON)
936
937     REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "REDlogFilm", name="REDlogFilm")
938     configData['colorSpaces'].append(REDlogFilmREDcolor2)
939
940     REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "REDlogFilm", name="REDlogFilm")
941     configData['colorSpaces'].append(REDlogFilmREDcolor3)
942
943     # Linearization only
944     REDlogFilmDRAGON = createREDlogFilm("", "REDlogFilm", name="REDlogFilm")
945     configData['colorSpaces'].append(REDlogFilmDRAGON)
946
947     # Primaries only
948     REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "", name="REDlogFilm")
949     configData['colorSpaces'].append(REDlogFilmDRAGON)
950
951     REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "", name="REDlogFilm")
952     configData['colorSpaces'].append(REDlogFilmREDcolor2)
953
954     REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "", name="REDlogFilm")
955     configData['colorSpaces'].append(REDlogFilmREDcolor3)
956
957     #
958     # Canon-Log to ACES
959     #
960     def createCanonLog(gamut, transferFunction, name='Canon-Log'):
961         name = "%s - %s" % (transferFunction, gamut)
962         if transferFunction == "":
963             name = "Linear - %s" % gamut
964         if gamut == "":
965             name = "%s" % transferFunction
966
967         cs = ColorSpace(name)
968         cs.description = name
969         cs.equalityGroup = ''
970         cs.family = 'Canon'
971         cs.isData=False
972
973         def legalToFull(codeValue):
974             return (codeValue - 64.0)/(940.0 - 64.0)
975
976         def canonLogToLinear(codeValue):
977             # log = fullToLegal(c1 * log10(c2*linear + 1) + c3)
978             # linear = (pow(10, (legalToFul(log) - c3)/c1) - 1)/c2
979             c1 = 0.529136
980             c2 = 10.1596
981             c3 = 0.0730597
982
983             linear = (pow(10.0, (legalToFull(codeValue) - c3)/c1) -1.0)/c2
984             linear = 0.9 * linear
985             #print( codeValue, linear )
986             return linear
987
988         cs.toReferenceTransforms = []
989
990         if transferFunction == "Canon-Log":
991             data = array.array('f', "\0" * lutResolution1d * 4)
992             for c in range(lutResolution1d):
993                 data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1))
994
995             lut = "%s_to_linear.spi1d" % transferFunction
996             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
997
998             cs.toReferenceTransforms.append( {
999                 'type':'lutFile', 
1000                 'path':lut, 
1001                 'interpolation':'linear', 
1002                 'direction':'forward'
1003             } )
1004
1005         if gamut == 'Rec. 709 Daylight':
1006             cs.toReferenceTransforms.append( {
1007                 'type':'matrix',
1008                 'matrix':[0.561538969, 0.402060105, 0.036400926, 0.0, 
1009                             0.092739623, 0.924121198, -0.016860821, 0.0, 
1010                             0.084812961, 0.006373835, 0.908813204, 0.0, 
1011                             0,0,0,1.0],
1012                 'direction':'forward'
1013             })
1014         elif gamut == 'Rec. 709 Tungsten':
1015             cs.toReferenceTransforms.append( {
1016                 'type':'matrix',
1017                 'matrix':[0.566996399, 0.365079418, 0.067924183, 0.0, 
1018                             0.070901044, 0.880331008, 0.048767948, 0.0, 
1019                             0.073013542, -0.066540862, 0.99352732, 0.0, 
1020                             0,0,0,1.0],
1021                 'direction':'forward'
1022             })
1023         elif gamut == 'DCI-P3 Daylight':
1024             cs.toReferenceTransforms.append( {
1025                 'type':'matrix',
1026                 'matrix':[0.607160575, 0.299507286, 0.093332140, 0.0, 
1027                             0.004968120, 1.050982224, -0.055950343, 0.0, 
1028                             -0.007839939, 0.000809127, 1.007030813, 0.0, 
1029                             0,0,0,1.0],
1030                 'direction':'forward'
1031             })
1032         elif gamut == 'DCI-P3 Tungsten':
1033             cs.toReferenceTransforms.append( {
1034                 'type':'matrix',
1035                 'matrix':[0.650279125, 0.253880169, 0.095840706, 0.0, 
1036                             -0.026137986, 1.017900530, 0.008237456, 0.0, 
1037                             0.007757558, -0.063081669, 1.055324110, 0.0, 
1038                             0,0,0,1.0],
1039                 'direction':'forward'
1040             })
1041         elif gamut == 'Cinema Gamut Daylight':
1042             cs.toReferenceTransforms.append( {
1043                 'type':'matrix',
1044                 'matrix':[0.763064455, 0.149021161, 0.087914384, 0.0, 
1045                             0.003657457, 1.10696038, -0.110617837, 0.0, 
1046                             -0.009407794,-0.218383305, 1.227791099, 0.0, 
1047                             0,0,0,1.0],
1048                 'direction':'forward'
1049             })
1050         elif gamut == 'Cinema Gamut Tungsten':
1051             cs.toReferenceTransforms.append( {
1052                 'type':'matrix',
1053                 'matrix':[0.817416293, 0.090755698, 0.091828009, 0.0, 
1054                             -0.035361374, 1.065690585, -0.030329211, 0.0, 
1055                             0.010390366, -0.299271107, 1.288880741, 0.0, 
1056                             0,0,0,1.0],
1057                 'direction':'forward'
1058             })
1059
1060         cs.fromReferenceTransforms = []
1061         return cs
1062
1063     # Full conversion
1064     CanonLog1 = createCanonLog("Rec. 709 Daylight", "Canon-Log", name="Canon-Log")
1065     configData['colorSpaces'].append(CanonLog1)
1066
1067     CanonLog2 = createCanonLog("Rec. 709 Tungsten", "Canon-Log", name="Canon-Log")
1068     configData['colorSpaces'].append(CanonLog2)
1069
1070     CanonLog3 = createCanonLog("DCI-P3 Daylight", "Canon-Log", name="Canon-Log")
1071     configData['colorSpaces'].append(CanonLog3)
1072
1073     CanonLog4 = createCanonLog("DCI-P3 Tungsten", "Canon-Log", name="Canon-Log")
1074     configData['colorSpaces'].append(CanonLog4)
1075
1076     CanonLog5 = createCanonLog("Cinema Gamut Daylight", "Canon-Log", name="Canon-Log")
1077     configData['colorSpaces'].append(CanonLog5)
1078
1079     CanonLog6 = createCanonLog("Cinema Gamut Tungsten", "Canon-Log", name="Canon-Log")
1080     configData['colorSpaces'].append(CanonLog6)
1081
1082     # Linearization only
1083     CanonLog7 = createCanonLog('', "Canon-Log", name="Canon-Log")
1084     configData['colorSpaces'].append(CanonLog7)
1085
1086     # Primaries only
1087     CanonLog8 = createCanonLog("Rec. 709 Daylight", "", name="Canon-Log")
1088     configData['colorSpaces'].append(CanonLog8)
1089
1090     CanonLog9 = createCanonLog("Rec. 709 Tungsten", "", name="Canon-Log")
1091     configData['colorSpaces'].append(CanonLog9)
1092
1093     CanonLog10 = createCanonLog("DCI-P3 Daylight", "", name="Canon-Log")
1094     configData['colorSpaces'].append(CanonLog10)
1095
1096     CanonLog11 = createCanonLog("DCI-P3 Tungsten", "", name="Canon-Log")
1097     configData['colorSpaces'].append(CanonLog11)
1098
1099     CanonLog12 = createCanonLog("Cinema Gamut Daylight", "", name="Canon-Log")
1100     configData['colorSpaces'].append(CanonLog12)
1101
1102     CanonLog13 = createCanonLog("Cinema Gamut Tungsten", "", name="Canon-Log")
1103     configData['colorSpaces'].append(CanonLog13)
1104
1105     #
1106     # SLog to ACES
1107     #
1108     def createSlog(gamut, transferFunction, name='S-Log3'):
1109         name = "%s - %s" % (transferFunction, gamut)
1110         if transferFunction == "":
1111             name = "Linear - %s" % gamut
1112         if gamut == "":
1113             name = "%s" % transferFunction
1114
1115         cs = ColorSpace(name)
1116         cs.description = name
1117         cs.equalityGroup = ''
1118         cs.family = 'Sony'
1119         cs.isData=False
1120
1121         def sLog1ToLinear(SLog):
1122             b = 64.
1123             ab = 90.
1124             w = 940.
1125
1126             if (SLog >= ab):
1127                 lin = ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) * 0.9
1128             else:
1129                 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 5.) * 0.9 
1130             return lin
1131
1132         def sLog2ToLinear(SLog):
1133             b = 64.
1134             ab = 90.
1135             w = 940.
1136
1137             if (SLog >= ab):
1138                 lin = ( 219. * ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) / 155.) * 0.9
1139             else:
1140                 lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 3.53881278538813) * 0.9
1141             return lin
1142
1143         def sLog3ToLinear(codeValue):
1144             if codeValue >= (171.2102946929):
1145                 linear = pow(10.0, ((codeValue - 420.0) / 261.5)) * (0.18 + 0.01) - 0.01
1146             else:
1147                 linear = (codeValue - 95.0)*0.01125000/(171.2102946929 - 95.0)
1148             #print( codeValue, linear )
1149             return linear
1150
1151         cs.toReferenceTransforms = []
1152
1153         if transferFunction == "S-Log1":
1154             data = array.array('f', "\0" * lutResolution1d * 4)
1155             for c in range(lutResolution1d):
1156                 data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1))
1157
1158             lut = "%s_to_linear.spi1d" % transferFunction
1159             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1160
1161             #print( "Writing %s" % lut)
1162
1163             cs.toReferenceTransforms.append( {
1164                 'type':'lutFile', 
1165                 'path':lut, 
1166                 'interpolation':'linear', 
1167                 'direction':'forward'
1168             } )
1169         elif transferFunction == "S-Log2":
1170             data = array.array('f', "\0" * lutResolution1d * 4)
1171             for c in range(lutResolution1d):
1172                 data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1))
1173
1174             lut = "%s_to_linear.spi1d" % transferFunction
1175             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1176
1177             #print( "Writing %s" % lut)
1178
1179             cs.toReferenceTransforms.append( {
1180                 'type':'lutFile', 
1181                 'path':lut, 
1182                 'interpolation':'linear', 
1183                 'direction':'forward'
1184             } )
1185         elif transferFunction == "S-Log3":
1186             data = array.array('f', "\0" * lutResolution1d * 4)
1187             for c in range(lutResolution1d):
1188                 data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1))
1189
1190             lut = "%s_to_linear.spi1d" % transferFunction
1191             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1192
1193             #print( "Writing %s" % lut)
1194
1195             cs.toReferenceTransforms.append( {
1196                 'type':'lutFile', 
1197                 'path':lut, 
1198                 'interpolation':'linear', 
1199                 'direction':'forward'
1200             } )
1201
1202         if gamut == 'S-Gamut':
1203             cs.toReferenceTransforms.append( {
1204                 'type':'matrix',
1205                 'matrix':mat44FromMat33([0.754338638, 0.133697046, 0.111968437,
1206                                         0.021198141, 1.005410934, -0.026610548, 
1207                                         -0.009756991, 0.004508563, 1.005253201]),
1208                 'direction':'forward'
1209             })
1210         elif gamut == 'S-Gamut Daylight':
1211             cs.toReferenceTransforms.append( {
1212                 'type':'matrix',
1213                 'matrix':mat44FromMat33([0.8764457030, 0.0145411681, 0.1090131290,
1214                                         0.0774075345, 0.9529571767, -0.0303647111, 
1215                                         0.0573564351, -0.1151066335, 1.0577501984]),
1216                 'direction':'forward'
1217             })
1218         elif gamut == 'S-Gamut Tungsten':
1219             cs.toReferenceTransforms.append( {
1220                 'type':'matrix',
1221                 'matrix':mat44FromMat33([1.0110238740, -0.1362526051, 0.1252287310, 
1222                             0.1011994504, 0.9562196265, -0.0574190769,
1223                             0.0600766530, -0.1010185315, 1.0409418785]),
1224                 'direction':'forward'
1225             })
1226         elif gamut == 'S-Gamut3.Cine':
1227             cs.toReferenceTransforms.append( {
1228                 'type':'matrix',
1229                 'matrix':mat44FromMat33([0.6387886672, 0.2723514337, 0.0888598992, 
1230                                         -0.0039159061, 1.0880732308, -0.0841573249, 
1231                                         -0.0299072021, -0.0264325799, 1.0563397820]),
1232                 'direction':'forward'
1233             })
1234         elif gamut == 'S-Gamut3':
1235             cs.toReferenceTransforms.append( {
1236                 'type':'matrix',
1237                 'matrix':mat44FromMat33([0.7529825954, 0.1433702162, 0.1036471884, 
1238                             0.0217076974, 1.0153188355, -0.0370265329, 
1239                             -0.0094160528, 0.0033704179, 1.0060456349]),
1240                 'direction':'forward'
1241             })
1242
1243         cs.fromReferenceTransforms = []
1244         return cs
1245
1246     # SLog1
1247     SLog1SGamut = createSlog("S-Gamut", "S-Log1", name="S-Log")
1248     configData['colorSpaces'].append(SLog1SGamut)
1249
1250     # SLog2
1251     SLog2SGamut = createSlog("S-Gamut", "S-Log2", name="S-Log2")
1252     configData['colorSpaces'].append(SLog2SGamut)
1253
1254     SLog2SGamutDaylight = createSlog("S-Gamut Daylight", "S-Log2", name="S-Log2")
1255     configData['colorSpaces'].append(SLog2SGamutDaylight)
1256
1257     SLog2SGamutTungsten = createSlog("S-Gamut Tungsten", "S-Log2", name="S-Log2")
1258     configData['colorSpaces'].append(SLog2SGamutTungsten)
1259
1260     # SLog3
1261     SLog3SGamut3Cine = createSlog("S-Gamut3.Cine", "S-Log3", name="S-Log3")
1262     configData['colorSpaces'].append(SLog3SGamut3Cine)
1263
1264     SLog3SGamut3 = createSlog("S-Gamut3", "S-Log3", name="S-Log3")
1265     configData['colorSpaces'].append(SLog3SGamut3)
1266
1267     # Linearization only
1268     SLog1 = createSlog("", "S-Log1", name="S-Log")
1269     configData['colorSpaces'].append(SLog1)
1270
1271     SLog2 = createSlog("", "S-Log2", name="S-Log2")
1272     configData['colorSpaces'].append(SLog2)
1273
1274     SLog3 = createSlog("", "S-Log3", name="S-Log3")
1275     configData['colorSpaces'].append(SLog3)
1276
1277     # Primaries only
1278     SGamut = createSlog("S-Gamut", "", name="S-Log")
1279     configData['colorSpaces'].append(SGamut)
1280
1281     SGamutDaylight = createSlog("S-Gamut Daylight", "", name="S-Log2")
1282     configData['colorSpaces'].append(SGamutDaylight)
1283
1284     SGamutTungsten = createSlog("S-Gamut Tungsten", "", name="S-Log2")
1285     configData['colorSpaces'].append(SGamutTungsten)
1286
1287     SGamut3Cine = createSlog("S-Gamut3.Cine", "", name="S-Log3")
1288     configData['colorSpaces'].append(SGamut3Cine)
1289
1290     SGamut3 = createSlog("S-Gamut3", "", name="S-Log3")
1291     configData['colorSpaces'].append(SGamut3)
1292
1293     #
1294     # LogC to ACES
1295     #
1296     def createLogC(gamut, transferFunction, exposureIndex, name='LogC'):
1297         name = "%s (EI%s) - %s" % (transferFunction, exposureIndex, gamut)
1298         if transferFunction == "":
1299             name = "Linear - %s" % gamut
1300         if gamut == "":
1301             name = "%s (EI%s)" % (transferFunction, exposureIndex)
1302
1303         cs = ColorSpace(name)
1304         cs.description = name
1305         cs.equalityGroup = ''
1306         cs.family = 'ARRI'
1307         cs.isData=False
1308
1309         # Globals
1310         IDT_maker_version = "0.08"
1311
1312         nominalEI = 400.0
1313         blackSignal = 0.003907
1314         midGraySignal = 0.01
1315         encodingGain = 0.256598
1316         encodingOffset = 0.391007
1317
1318         def gainForEI(EI) :
1319             return (math.log(EI/nominalEI)/math.log(2) * (0.89 - 1) / 3 + 1) * encodingGain
1320
1321         def LogCInverseParametersForEI(EI) :
1322             cut = 1.0 / 9.0
1323             slope = 1.0 / (cut * math.log(10))
1324             offset = math.log10(cut) - slope * cut
1325             gain = EI / nominalEI
1326             gray = midGraySignal / gain
1327             # The higher the EI, the lower the gamma
1328             encGain = gainForEI(EI)
1329             encOffset = encodingOffset
1330             for i in range(0,3) :
1331                 nz = ((95.0 / 1023.0 - encOffset) / encGain - offset) / slope
1332                 encOffset = encodingOffset - math.log10(1 + nz) * encGain
1333             # Calculate some intermediate values
1334             a = 1.0 / gray
1335             b = nz - blackSignal / gray
1336             e = slope * a * encGain
1337             f = encGain * (slope * b + offset) + encOffset
1338             # Manipulations so we can return relative exposure
1339             s = 4 / (0.18 * EI)
1340             t = blackSignal
1341             b = b + a * t
1342             a = a * s
1343             f = f + e * t
1344             e = e * s
1345             return { 'a' : a,
1346                      'b' : b,
1347                      'cut' : (cut - b) / a,
1348                      'c' : encGain,
1349                      'd' : encOffset,
1350                      'e' : e,
1351                      'f' : f }
1352
1353         def logCtoLinear(codeValue, exposureIndex):
1354             p = LogCInverseParametersForEI(exposureIndex)
1355             breakpoint = p['e'] * p['cut'] + p['f']
1356             if (codeValue > breakpoint):
1357                 linear = (pow(10,(codeValue/1023.0 - p['d']) / p['c']) - p['b']) / p['a']
1358             else:
1359                 linear = (codeValue/1023.0 - p['f']) / p['e']
1360
1361             #print( codeValue, linear )
1362             return linear
1363
1364
1365         cs.toReferenceTransforms = []
1366
1367         if transferFunction == "V3 LogC":
1368             data = array.array('f', "\0" * lutResolution1d * 4)
1369             for c in range(lutResolution1d):
1370                 data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex))
1371
1372             lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex))
1373             WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
1374
1375             #print( "Writing %s" % lut)
1376             cs.toReferenceTransforms.append( {
1377                 'type':'lutFile', 
1378                 'path':lut, 
1379                 'interpolation':'linear', 
1380                 'direction':'forward'
1381             } )
1382
1383         if gamut == 'Wide Gamut':
1384             cs.toReferenceTransforms.append( {
1385                 'type':'matrix',
1386                 'matrix':mat44FromMat33([0.680206, 0.236137, 0.083658, 
1387                             0.085415, 1.017471, -0.102886, 
1388                             0.002057, -0.062563, 1.060506]),
1389                 'direction':'forward'
1390             })
1391
1392         cs.fromReferenceTransforms = []
1393         return cs
1394
1395     transferFunction = "V3 LogC"
1396     gamut = "Wide Gamut"
1397     #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]
1398     EIs = [160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600, 2000, 2560, 3200]
1399     defaultEI = 800
1400
1401     # Full conversion
1402     for EI in EIs:
1403         LogCEIfull = createLogC(gamut, transferFunction, EI, name="LogC")
1404         configData['colorSpaces'].append(LogCEIfull)
1405
1406     # Linearization only
1407     for EI in [800]:
1408         LogCEIlinearization = createLogC("", transferFunction, EI, name="LogC")
1409         configData['colorSpaces'].append(LogCEIlinearization)
1410
1411     # Primaries
1412     LogCEIprimaries = createLogC(gamut, "", defaultEI, name="LogC")
1413     configData['colorSpaces'].append(LogCEIprimaries)
1414
1415     #
1416     # Generic log transform
1417     #
1418     def createGenericLog(name='log', 
1419         minValue=0.0, 
1420         maxValue=1.0, 
1421         inputScale=1.0,
1422         middleGrey=0.18,
1423         minExposure=-6.0,
1424         maxExposure=6.5,
1425         lutResolution1d=lutResolution1d):
1426         cs = ColorSpace(name)
1427         cs.description = "The %s color space" % name
1428         cs.equalityGroup = name
1429         cs.family = 'Utility'
1430         cs.isData=False
1431
1432         ctls = [
1433             #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
1434             '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
1435         ]
1436         lut = "%s_to_aces.spi1d" % name
1437
1438         generate1dLUTFromCTL( lutDir + "/" + lut, 
1439             ctls, 
1440             lutResolution1d, 
1441             'float', 
1442             inputScale,
1443             1.0, 
1444             {
1445                 'middleGrey'  : middleGrey,
1446                 'minExposure' : minExposure,
1447                 'maxExposure' : maxExposure
1448             },
1449             cleanup, 
1450             acesCTLReleaseDir,
1451             minValue,
1452             maxValue)
1453
1454         cs.toReferenceTransforms = []
1455         cs.toReferenceTransforms.append( {
1456             'type':'lutFile', 
1457             'path':lut, 
1458             'interpolation':'linear', 
1459             'direction':'forward'
1460         } )
1461
1462         cs.fromReferenceTransforms = []
1463         return cs
1464
1465     #
1466     # ACES LMTs
1467     #
1468     def createACESLMT(lmtName, 
1469         lmtValues,
1470         shaperInfo,
1471         lutResolution1d=1024, 
1472         lutResolution3d=64, 
1473         cleanup=True):
1474         cs = ColorSpace("%s" % lmtName)
1475         cs.description = "The ACES Look Transform: %s" % lmtName
1476         cs.equalityGroup = ''
1477         cs.family = 'Look'
1478         cs.isData=False
1479
1480         import pprint
1481         pprint.pprint( lmtValues )
1482
1483         #
1484         # Generate the shaper transform
1485         #
1486         (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1487
1488         shaperLut = "%s_to_aces.spi1d" % shaperName
1489         if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1490             ctls = [
1491                 shaperToACESCTL % acesCTLReleaseDir
1492             ]
1493             generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
1494                 ctls, 
1495                 lutResolution1d, 
1496                 'float', 
1497                 1.0/shaperInputScale,
1498                 1.0, 
1499                 shaperParams,
1500                 cleanup, 
1501                 acesCTLReleaseDir)
1502
1503         shaperOCIOTransform = {
1504             'type':'lutFile', 
1505             'path':shaperLut, 
1506             'interpolation':'linear', 
1507             'direction':'inverse'
1508         }
1509
1510         #
1511         # Generate the forward transform
1512         #
1513         cs.fromReferenceTransforms = []
1514
1515         if 'transformCTL' in lmtValues:
1516             ctls = [
1517                 shaperToACESCTL % acesCTLReleaseDir, 
1518                 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
1519             ]
1520             lut = "%s.%s.spi3d" % (shaperName, lmtName)
1521
1522             generate3dLUTFromCTL( lutDir + "/" + lut, 
1523                 ctls, 
1524                 lutResolution3d, 
1525                 'float', 
1526                 1.0/shaperInputScale,
1527                 1.0, 
1528                 shaperParams,
1529                 cleanup, 
1530                 acesCTLReleaseDir )
1531
1532             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1533             cs.fromReferenceTransforms.append( {
1534                 'type':'lutFile', 
1535                 'path':lut, 
1536                 'interpolation':'tetrahedral', 
1537                 'direction':'forward'
1538             } )
1539
1540         #
1541         # Generate the inverse transform
1542         #
1543         cs.toReferenceTransforms = []
1544
1545         if 'transformCTLInverse' in lmtValues:
1546             ctls = [
1547                 '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1548                 shaperFromACESCTL % acesCTLReleaseDir
1549             ]
1550             lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
1551
1552             generate3dLUTFromCTL( lutDir + "/" + lut, 
1553                 ctls, 
1554                 lutResolution3d, 
1555                 'half', 
1556                 1.0,
1557                 shaperInputScale, 
1558                 shaperParams,
1559                 cleanup, 
1560                 acesCTLReleaseDir )
1561
1562             cs.toReferenceTransforms.append( {
1563                 'type':'lutFile', 
1564                 'path':lut, 
1565                 'interpolation':'tetrahedral', 
1566                 'direction':'forward'
1567             } )
1568
1569             shaperInverse = shaperOCIOTransform.copy()
1570             shaperInverse['direction'] = 'forward'
1571             cs.toReferenceTransforms.append( shaperInverse )
1572
1573         return cs
1574
1575     #
1576     # LMT Shaper
1577     #
1578
1579     lmtLutResolution1d = max(4096, lutResolution1d)
1580     lmtLutResolution3d = max(65, lutResolution3d)
1581
1582     # Log 2 shaper
1583     lmtShaperName = 'LMT Shaper'
1584     lmtParams = {
1585         'middleGrey'  : 0.18,
1586         'minExposure' : -10.0,
1587         'maxExposure' : 6.5
1588     }
1589     lmtShaper = createGenericLog(name=lmtShaperName, 
1590         middleGrey=lmtParams['middleGrey'], 
1591         minExposure=lmtParams['minExposure'], 
1592         maxExposure=lmtParams['maxExposure'],
1593         lutResolution1d=lmtLutResolution1d)
1594     configData['colorSpaces'].append(lmtShaper)
1595
1596     shaperInputScale_genericLog2 = 1.0
1597
1598     # Log 2 shaper name and CTL transforms bundled up
1599     lmtShaperData = [
1600         lmtShaperName, 
1601         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1602         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1603         #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1604         #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1605         shaperInputScale_genericLog2,
1606         lmtParams
1607     ]
1608
1609     sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
1610     print( sortedLMTs )
1611     for lmt in sortedLMTs:
1612         (lmtName, lmtValues) = lmt
1613         cs = createACESLMT(
1614             lmtValues['transformUserName'], 
1615             lmtValues,
1616             lmtShaperData,
1617             lmtLutResolution1d,
1618             lmtLutResolution3d,
1619             cleanup)
1620         configData['colorSpaces'].append(cs)
1621
1622     #
1623     # ACES RRT with the supplied ODT
1624     #
1625     def createACESRRTplusODT(odtName, 
1626         odtValues,
1627         shaperInfo,
1628         lutResolution1d=1024, 
1629         lutResolution3d=64, 
1630         cleanup=True):
1631         cs = ColorSpace("%s" % odtName)
1632         cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
1633         cs.equalityGroup = ''
1634         cs.family = 'Output'
1635         cs.isData=False
1636
1637         import pprint
1638         pprint.pprint( odtValues )
1639
1640         #
1641         # Generate the shaper transform
1642         #
1643         #if 'shaperCTL' in odtValues:
1644         (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
1645
1646         if 'legalRange' in odtValues:
1647             shaperParams['legalRange'] = odtValues['legalRange']
1648         else:
1649             shaperParams['legalRange'] = 0
1650
1651         shaperLut = "%s_to_aces.spi1d" % shaperName
1652         if( not os.path.exists( lutDir + "/" + shaperLut ) ):
1653             ctls = [
1654                 shaperToACESCTL % acesCTLReleaseDir
1655             ]
1656             generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
1657                 ctls, 
1658                 lutResolution1d, 
1659                 'float', 
1660                 1.0/shaperInputScale,
1661                 1.0, 
1662                 shaperParams,
1663                 cleanup, 
1664                 acesCTLReleaseDir)
1665
1666         shaperOCIOTransform = {
1667             'type':'lutFile', 
1668             'path':shaperLut, 
1669             'interpolation':'linear', 
1670             'direction':'inverse'
1671         }
1672
1673         #
1674         # Generate the forward transform
1675         #
1676         cs.fromReferenceTransforms = []
1677
1678         if 'transformLUT' in odtValues:
1679             # Copy into the lut dir
1680             transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1681             lut = lutDir + "/" + transformLUTFileName
1682             shutil.copy(odtValues['transformLUT'], lut)
1683
1684             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1685             cs.fromReferenceTransforms.append( {
1686                 'type':'lutFile', 
1687                 'path': transformLUTFileName, 
1688                 'interpolation':'tetrahedral', 
1689                 'direction':'forward'
1690             } )
1691         elif 'transformCTL' in odtValues:
1692             #shaperLut
1693
1694             ctls = [
1695                 shaperToACESCTL % acesCTLReleaseDir, 
1696                 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir, 
1697                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1698             ]
1699             lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1700
1701             generate3dLUTFromCTL( lutDir + "/" + lut, 
1702                 #shaperLUT,
1703                 ctls, 
1704                 lutResolution3d, 
1705                 'float', 
1706                 1.0/shaperInputScale,
1707                 1.0, 
1708                 shaperParams,
1709                 cleanup, 
1710                 acesCTLReleaseDir )
1711
1712             cs.fromReferenceTransforms.append( shaperOCIOTransform )
1713             cs.fromReferenceTransforms.append( {
1714                 'type':'lutFile', 
1715                 'path':lut, 
1716                 'interpolation':'tetrahedral', 
1717                 'direction':'forward'
1718             } )
1719
1720         #
1721         # Generate the inverse transform
1722         #
1723         cs.toReferenceTransforms = []
1724
1725         if 'transformLUTInverse' in odtValues:
1726             # Copy into the lut dir
1727             transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
1728             lut = lutDir + "/" + transformLUTInverseFileName
1729             shutil.copy(odtValues['transformLUTInverse'], lut)
1730
1731             cs.toReferenceTransforms.append( {
1732                 'type':'lutFile', 
1733                 'path': transformLUTInverseFileName, 
1734                 'interpolation':'tetrahedral', 
1735                 'direction':'forward'
1736             } )
1737
1738             shaperInverse = shaperOCIOTransform.copy()
1739             shaperInverse['direction'] = 'forward'
1740             cs.toReferenceTransforms.append( shaperInverse )
1741         elif 'transformCTLInverse' in odtValues:
1742             ctls = [
1743                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
1744                 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1745                 shaperFromACESCTL % acesCTLReleaseDir
1746             ]
1747             lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1748
1749             generate3dLUTFromCTL( lutDir + "/" + lut, 
1750                 #None,
1751                 ctls, 
1752                 lutResolution3d, 
1753                 'half', 
1754                 1.0,
1755                 shaperInputScale, 
1756                 shaperParams,
1757                 cleanup, 
1758                 acesCTLReleaseDir )
1759
1760             cs.toReferenceTransforms.append( {
1761                 'type':'lutFile', 
1762                 'path':lut, 
1763                 'interpolation':'tetrahedral', 
1764                 'direction':'forward'
1765             } )
1766
1767             shaperInverse = shaperOCIOTransform.copy()
1768             shaperInverse['direction'] = 'forward'
1769             cs.toReferenceTransforms.append( shaperInverse )
1770
1771         return cs
1772
1773     #
1774     # RRT/ODT shaper options
1775     #
1776     shaperData = {}
1777
1778     # Log 2 shaper
1779     log2ShaperName = shaperName
1780     log2Params = {
1781         'middleGrey'  : 0.18,
1782         'minExposure' : -6.0,
1783         'maxExposure' : 6.5
1784     }
1785     log2Shaper = createGenericLog(name=log2ShaperName, 
1786         middleGrey=log2Params['middleGrey'], 
1787         minExposure=log2Params['minExposure'], 
1788         maxExposure=log2Params['maxExposure'])
1789     configData['colorSpaces'].append(log2Shaper)
1790
1791     shaperInputScale_genericLog2 = 1.0
1792
1793     # Log 2 shaper name and CTL transforms bundled up
1794     log2ShaperData = [
1795         log2ShaperName, 
1796         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1797         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1798         #'%s/logShaper/logShaper16i_to_aces_param.ctl',
1799         #'%s/logShaper/aces_to_logShaper16i_param.ctl',
1800         shaperInputScale_genericLog2,
1801         log2Params
1802     ]
1803
1804     shaperData[log2ShaperName] = log2ShaperData
1805
1806     #
1807     # Shaper that also includes the AP1 primaries
1808     # - Needed for some LUT baking steps
1809     #
1810     log2ShaperAP1 = createGenericLog(name=log2ShaperName, 
1811         middleGrey=log2Params['middleGrey'], 
1812         minExposure=log2Params['minExposure'], 
1813         maxExposure=log2Params['maxExposure'])
1814     log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1815     # AP1 primaries to AP0 primaries
1816     log2ShaperAP1.toReferenceTransforms.append( {
1817         'type':'matrix',
1818         'matrix':mat44FromMat33(acesAP1toAP0),
1819         'direction':'forward'
1820     })
1821     configData['colorSpaces'].append(log2ShaperAP1)
1822
1823     #
1824     # Choose your shaper
1825     #
1826     # XXX
1827     # Shaper name. Should really be automated or made a user choice
1828     #
1829     # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
1830     #shaperName = 'log2Shaper'
1831
1832     #if shaperName in shaperData:
1833     #    rrtShaperName = shaperName
1834     #    rrtShaper = shaperData[shaperName]
1835     #else:
1836
1837     rrtShaperName = log2ShaperName
1838     rrtShaper = log2ShaperData
1839
1840     #
1841     # RRT + ODT Combinations
1842     #
1843     #for odtName, odtValues in odtInfo.iteritems():
1844     sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1845     print( sortedOdts )
1846     for odt in sortedOdts:
1847         (odtName, odtValues) = odt
1848
1849         # Have to handle ODTs that can generate either legal or full output
1850         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1851                 'Academy.Rec709_100nits_dim.a1.0.0',
1852                 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1853             odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1854         else:
1855             odtNameLegal = odtValues['transformUserName']
1856
1857         odtLegal = odtValues.copy()
1858         odtLegal['legalRange'] = 1
1859
1860         cs = createACESRRTplusODT(
1861             odtNameLegal, 
1862             odtLegal,
1863             rrtShaper,
1864             lutResolution1d,
1865             lutResolution3d,
1866             cleanup)
1867         configData['colorSpaces'].append(cs)
1868
1869         # Create a display entry using this color space
1870         configData['displays'][odtNameLegal] = { 
1871             'Linear':ACES, 
1872             'Log':ACEScc, 
1873             'Output Transform':cs }
1874
1875         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1876                 'Academy.Rec709_100nits_dim.a1.0.0', 
1877                 'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1878
1879             print( "Generating full range ODT for %s" % odtName)
1880
1881             odtNameFull = "%s - Full" % odtValues['transformUserName']
1882             odtFull = odtValues.copy()
1883             odtFull['legalRange'] = 0
1884
1885             csFull = createACESRRTplusODT(
1886                 odtNameFull, 
1887                 odtFull,
1888                 rrtShaper,
1889                 lutResolution1d,
1890                 lutResolution3d,
1891                 cleanup)
1892             configData['colorSpaces'].append(csFull)
1893
1894             # Create a display entry using this color space
1895             configData['displays'][odtNameFull] = { 
1896                 'Linear':ACES, 
1897                 'Log':ACEScc, 
1898                 'Output Transform':csFull }
1899
1900     #
1901     # Generic Matrix transform
1902     #
1903     def createGenericMatrix(name='matrix', 
1904         fromReferenceValues=[],
1905         toReferenceValues=[]):
1906         cs = ColorSpace(name)
1907         cs.description = "The %s color space" % name
1908         cs.equalityGroup = name
1909         cs.family = 'Utility'
1910         cs.isData=False
1911
1912         cs.toReferenceTransforms = []
1913         if toReferenceValues != []:
1914             cs.toReferenceTransforms.append( {
1915                 'type':'matrix',
1916                 'matrix':mat44FromMat33(toReferenceValues),
1917                 'direction':'forward'
1918             })
1919
1920         cs.fromReferenceTransforms = []
1921         if fromReferenceValues != []:
1922             cs.fromReferenceTransforms.append( {
1923                 'type':'matrix',
1924                 'matrix':mat44FromMat33(fromReferenceValues),
1925                 'direction':'forward'
1926             })
1927
1928         return cs
1929
1930     cs = createGenericMatrix('XYZ', fromReferenceValues=acesAP0toXYZ)
1931     configData['colorSpaces'].append(cs)   
1932
1933     cs = createGenericMatrix('Linear - AP1', toReferenceValues=acesAP1toAP0)
1934     configData['colorSpaces'].append(cs)   
1935
1936     print( "generateLUTs - end" )
1937     return configData
1938
1939 def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
1940
1941     # Add the legal and full variations into this list
1942     odtInfoC = dict(odtInfo)
1943     for odtCTLName, odtValues in odtInfo.iteritems():
1944         if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
1945             'Academy.Rec709_100nits_dim.a1.0.0',
1946             'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1947                 odtName = odtValues["transformUserName"]
1948
1949                 odtValuesLegal = dict(odtValues)
1950                 odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1951                 odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1952
1953                 odtValuesFull = dict(odtValues)
1954                 odtValuesFull["transformUserName"] = "%s - Full" % odtName
1955                 odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1956                 
1957                 del( odtInfoC[odtCTLName] )
1958
1959     for odtCTLName, odtValues in odtInfoC.iteritems():
1960         odtPrefix = odtValues["transformUserNamePrefix"]
1961         odtName = odtValues["transformUserName"]
1962
1963         # For Photoshop
1964         for inputspace in ["ACEScc", "ACESproxy"]:
1965             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1966             args += ["--outputspace", "%s" % odtName ]
1967             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1968             args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
1969             args += ["--cubesize", str(lutResolution3d) ]
1970             args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
1971
1972             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
1973             bakeLUT.execute()    
1974
1975         # For Flame, Lustre
1976         for inputspace in ["ACEScc", "ACESproxy"]:
1977             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1978             args += ["--outputspace", "%s" % odtName ]
1979             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1980             args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
1981             args += ["--cubesize", str(lutResolution3d) ]
1982
1983             fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
1984             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
1985             bakeLUT.execute()    
1986
1987             largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
1988             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
1989             bakeLUT.execute()
1990
1991         # For Maya, Houdini
1992         for inputspace in ["ACEScg", "ACES2065-1"]:
1993             args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
1994             args += ["--outputspace", "%s" % odtName ]
1995             args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
1996             if inputspace == 'ACEScg':
1997                 linShaperName = "%s - AP1" % shaperName 
1998             else:
1999                 linShaperName = shaperName
2000             args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ] 
2001             
2002             args += ["--cubesize", str(lutResolution3d) ]
2003
2004             margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
2005             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
2006             bakeLUT.execute()    
2007
2008             hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
2009             bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
2010             bakeLUT.execute()    
2011
2012
2013 def createConfigDir(configDir, bakeSecondaryLUTs):
2014     dirs = [configDir, "%s/luts" % configDir]
2015     if bakeSecondaryLUTs:
2016         dirs.extend(["%s/baked" % configDir, 
2017             "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
2018             "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
2019             "%s/baked/maya" % configDir])
2020
2021     for d in dirs:
2022         if not os.path.exists(d):
2023             os.mkdir(d)
2024
2025 def getTransformInfo(ctlTransform):
2026     fp = open(ctlTransform, 'rb')
2027
2028     # Read lines
2029     lines = fp.readlines()
2030
2031     # Grab transform ID and User Name
2032     transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
2033     #print( transformID )
2034     transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
2035     transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
2036     #print( transformUserName )
2037     fp.close()
2038
2039     return (transformID, transformUserName, transformUserNamePrefix)
2040
2041 # For versions after WGR9
2042 def getODTInfo(acesCTLReleaseDir):
2043     # Credit to Alex Fry for the original approach here
2044     odtDir = os.path.join(acesCTLReleaseDir, "odt")
2045     allodt = []
2046     for dirName, subdirList, fileList in os.walk(odtDir):
2047         for fname in fileList:
2048             allodt.append((os.path.join(dirName,fname)))
2049
2050     odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
2051
2052     #print odtCTLs
2053
2054     odts = {}
2055
2056     for odtCTL in odtCTLs:
2057         odtTokens = os.path.split(odtCTL)
2058         #print( odtTokens )
2059
2060         # Handle nested directories
2061         odtPathTokens = os.path.split(odtTokens[-2])
2062         odtDir = odtPathTokens[-1]
2063         while odtPathTokens[-2][-3:] != 'odt':
2064             odtPathTokens = os.path.split(odtPathTokens[-2])
2065             odtDir = os.path.join(odtPathTokens[-1], odtDir)
2066
2067         # Build full name
2068         #print( "odtDir : %s" % odtDir )
2069         transformCTL = odtTokens[-1]
2070         #print( transformCTL )
2071         odtName = string.join(transformCTL.split('.')[1:-1], '.')
2072         #print( odtName )
2073
2074         # Find id, user name and user name prefix
2075         (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2076             "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
2077
2078         # Find inverse
2079         transformCTLInverse = "InvODT.%s.ctl" % odtName
2080         if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
2081             transformCTLInverse = None
2082         #print( transformCTLInverse )
2083
2084         # Add to list of ODTs
2085         odts[odtName] = {}
2086         odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
2087         if transformCTLInverse != None:
2088             odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
2089
2090         odts[odtName]['transformID'] = transformID
2091         odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
2092         odts[odtName]['transformUserName'] = transformUserName
2093
2094         print( "ODT : %s" % odtName )
2095         print( "\tTransform ID               : %s" % transformID )
2096         print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2097         print( "\tTransform User Name        : %s" % transformUserName )
2098         print( "\tForward ctl                : %s" % odts[odtName]['transformCTL'])
2099         if 'transformCTLInverse' in odts[odtName]:
2100             print( "\tInverse ctl                : %s" % odts[odtName]['transformCTLInverse'])
2101         else:
2102             print( "\tInverse ctl                : %s" % "None" )
2103
2104     print( "\n" )
2105
2106     return odts
2107
2108 # For versions after WGR9
2109 def getLMTInfo(acesCTLReleaseDir):
2110     # Credit to Alex Fry for the original approach here
2111     lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
2112     alllmt = []
2113     for dirName, subdirList, fileList in os.walk(lmtDir):
2114         for fname in fileList:
2115             alllmt.append((os.path.join(dirName,fname)))
2116
2117     lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
2118
2119     #print lmtCTLs
2120
2121     lmts = {}
2122
2123     for lmtCTL in lmtCTLs:
2124         lmtTokens = os.path.split(lmtCTL)
2125         #print( lmtTokens )
2126
2127         # Handle nested directories
2128         lmtPathTokens = os.path.split(lmtTokens[-2])
2129         lmtDir = lmtPathTokens[-1]
2130         while lmtPathTokens[-2][-3:] != 'ctl':
2131             lmtPathTokens = os.path.split(lmtPathTokens[-2])
2132             lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
2133
2134         # Build full name
2135         #print( "lmtDir : %s" % lmtDir )
2136         transformCTL = lmtTokens[-1]
2137         #print( transformCTL )
2138         lmtName = string.join(transformCTL.split('.')[1:-1], '.')
2139         #print( lmtName )
2140
2141         # Find id, user name and user name prefix
2142         (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
2143             "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
2144
2145         # Find inverse
2146         transformCTLInverse = "InvLMT.%s.ctl" % lmtName
2147         if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
2148             transformCTLInverse = None
2149         #print( transformCTLInverse )
2150
2151         # Add to list of LMTs
2152         lmts[lmtName] = {}
2153         lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
2154         if transformCTLInverse != None:
2155             lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
2156
2157         lmts[lmtName]['transformID'] = transformID
2158         lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
2159         lmts[lmtName]['transformUserName'] = transformUserName
2160
2161         print( "LMT : %s" % lmtName )
2162         print( "\tTransform ID               : %s" % transformID )
2163         print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
2164         print( "\tTransform User Name        : %s" % transformUserName )
2165         print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
2166         if 'transformCTLInverse' in lmts[lmtName]:
2167             print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
2168         else:
2169             print( "\t Inverse ctl : %s" % "None" )
2170
2171     print( "\n" )
2172
2173     return lmts
2174
2175 #
2176 # Create the ACES config
2177 #
2178 def createACESConfig(acesCTLReleaseDir, 
2179     configDir, 
2180     lutResolution1d=4096, 
2181     lutResolution3d=64, 
2182     bakeSecondaryLUTs=True,
2183     cleanup=True):
2184
2185     # Get ODT names and CTL paths
2186     odtInfo = getODTInfo(acesCTLReleaseDir)
2187
2188     # Get ODT names and CTL paths
2189     lmtInfo = getLMTInfo(acesCTLReleaseDir)
2190
2191     # Create config dir
2192     createConfigDir(configDir, bakeSecondaryLUTs)
2193
2194     # Generate config data and LUTs for different transforms
2195     lutDir = "%s/luts" % configDir
2196     shaperName = 'Output Shaper'
2197     configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
2198     
2199     # Create the config using the generated LUTs
2200     print( "Creating generic config")
2201     config = createConfig(configData)
2202     print( "\n\n\n" )
2203
2204     # Write the config to disk
2205     writeConfig(config, "%s/config.ocio" % configDir )
2206
2207     # Create a config that will work well with Nuke using the previously generated LUTs
2208     print( "Creating Nuke-specific config")
2209     nuke_config = createConfig(configData, nuke=True)
2210     print( "\n\n\n" )
2211
2212     # Write the config to disk
2213     writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
2214
2215     # Bake secondary LUTs using the config
2216     if bakeSecondaryLUTs:
2217         generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
2218
2219 #
2220 # Main
2221 #
2222 def main():
2223     import optparse
2224
2225     p = optparse.OptionParser(description='An OCIO config generation script',
2226                                 prog='createACESConfig',
2227                                 version='createACESConfig 0.1',
2228                                 usage='%prog [options]')
2229     p.add_option('--acesCTLDir', '-a', default=None)
2230     p.add_option('--configDir', '-c', default=None)
2231     p.add_option('--lutResolution1d', default=4096)
2232     p.add_option('--lutResolution3d', default=64)
2233     p.add_option('--dontBakeSecondaryLUTs', action="store_true")
2234     p.add_option('--keepTempImages', action="store_true")
2235
2236     options, arguments = p.parse_args()
2237
2238     #
2239     # Get options
2240     # 
2241     acesCTLDir = options.acesCTLDir
2242     configDir  = options.configDir
2243     lutResolution1d  = int(options.lutResolution1d)
2244     lutResolution3d  = int(options.lutResolution3d)
2245     bakeSecondaryLUTs  = not(options.dontBakeSecondaryLUTs)
2246     cleanupTempImages  = not(options.keepTempImages)
2247
2248     try:
2249         argsStart = sys.argv.index('--') + 1
2250         args = sys.argv[argsStart:]
2251     except:
2252         argsStart = len(sys.argv)+1
2253         args = []
2254
2255     print( "command line : \n%s\n" % " ".join(sys.argv) )
2256
2257     if configDir == None:
2258         print( "process: No ACES CTL directory specified" )
2259         return
2260  
2261     #
2262     # Generate the configuration
2263     #
2264     createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
2265 # main
2266
2267 if __name__ == '__main__':
2268     main()