Add docstrings skeletons.
[OpenColorIO-Configs.git] / aces_1.0.0 / python / aces_ocio / create_aces_config.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 usage from python:
6
7 >>> import sys
8 >>> sys.path.append("/path/to/script")
9 >>> import create_aces_config as cac
10 >>> acesReleaseCTLDir = "/path/to/github/checkout/releases/v0.7.1/transforms/ctl"
11 >>> configDir = "/path/to/config/dir"
12 >>> cac.createACESConfig(acesReleaseCTLDir, configDir, 1024, 33, True)
13
14 usage from command line, from the directory with 'create_aces_config.py':
15
16 $ 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
17
18
19 build instructions for osx for needed packages.
20
21 #opencolorio
22 brew install -vd opencolorio --with-python
23
24 #openimageio
25 brew tap homebrew/science
26
27 # optional installs
28 brew install -vd libRaw
29 brew install -vd OpenCV
30
31 brew install -vd openimageio --with-python
32
33 #ctl
34 brew install -vd CTL
35
36 #opencolorio - again.
37 # this time, 'ociolutimage' will build because openimageio is installed
38 brew uninstall -vd opencolorio
39 brew install -vd opencolorio --with-python
40 """
41
42 import math
43 import numpy
44 import os
45 import shutil
46 import string
47 import sys
48
49 # TODO: This restores the capability of running the script without having
50 # added the package to PYTHONPATH, this is ugly and should ideally replaced by
51 # dedicated executable in a /bin directory.
52 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
53
54 import PyOpenColorIO as OCIO
55
56 import aces_ocio.createARRIColorSpaces as arri
57 import aces_ocio.createCanonColorSpaces as canon
58 import aces_ocio.createREDColorSpaces as red
59 import aces_ocio.createSonyColorSpaces as sony
60 import aces_ocio.generateLUT as genlut
61 from aces_ocio.process import Process
62 from aces_ocio.util import ColorSpace, mat44FromMat33
63
64 __author__ = 'ACES Developers'
65 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
66 __license__ = ''
67 __maintainer__ = 'ACES Developers'
68 __email__ = 'aces@oscars.org'
69 __status__ = 'Production'
70
71 __all__ = ['ACES_OCIO_CTL_DIRECTORY_ENVIRON',
72            'ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON',
73            'setConfigDefaultRoles',
74            'writeConfig',
75            'generateOCIOTransform',
76            'createConfig',
77            'generateLUTs',
78            'generateBakedLUTs',
79            'createConfigDir',
80            'getTransformInfo',
81            'getODTInfo',
82            'getLMTInfo',
83            'createACESConfig',
84            'main']
85
86 ACES_OCIO_CTL_DIRECTORY_ENVIRON = 'ACES_OCIO_CTL_DIRECTORY'
87 ACES_OCIO_CONFIGURATION_DIRECTORY_ENVIRON = 'ACES_OCIO_CONFIGURATION_DIRECTORY'
88
89
90 def setConfigDefaultRoles(config,
91                           color_picking="",
92                           color_timing="",
93                           compositing_log="",
94                           data="",
95                           default="",
96                           matte_paint="",
97                           reference="",
98                           scene_linear="",
99                           texture_paint=""):
100     """
101     Sets given *OCIO* configuration default roles.
102
103     Parameters
104     ----------
105     config : config
106         *OCIO* configuration.
107     color_picking : str or unicode
108         Color picking role title.
109     color_timing : str or unicode
110         Color timing role title.
111     compositing_log : str or unicode
112         Compositing log role title.
113     data : str or unicode
114         Data role title.
115     default : str or unicode
116         Default role title.
117     matte_paint : str or unicode
118         Matte painting role title.
119     reference : str or unicode
120         Reference role title.
121     scene_linear : str or unicode
122         Scene linear role title.
123     texture_paint : str or unicode
124         Texture painting role title.
125
126     Returns
127     -------
128     bool
129          Definition success.
130     """
131
132     if color_picking:
133         config.setRole(OCIO.Constants.ROLE_COLOR_PICKING, color_picking)
134     if color_timing:
135         config.setRole(OCIO.Constants.ROLE_COLOR_TIMING, color_timing)
136     if compositing_log:
137         config.setRole(OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log)
138     if data:
139         config.setRole(OCIO.Constants.ROLE_DATA, data)
140     if default:
141         config.setRole(OCIO.Constants.ROLE_DEFAULT, default)
142     if matte_paint:
143         config.setRole(OCIO.Constants.ROLE_MATTE_PAINT, matte_paint)
144     if reference:
145         config.setRole(OCIO.Constants.ROLE_REFERENCE, reference)
146     if scene_linear:
147         config.setRole(OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear)
148     if texture_paint:
149         config.setRole(OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint)
150
151     return True
152
153
154 def writeConfig(config, configPath, sanityCheck=True):
155     """
156     Writes the configuration to given path.
157
158     Parameters
159     ----------
160     parameter : type
161         Parameter description.
162
163     Returns
164     -------
165     type
166          Return value description.
167     """
168
169     if sanityCheck:
170         try:
171             config.sanityCheck()
172         except Exception, e:
173             print e
174             print "Configuration was not written due to a failed Sanity Check"
175             return
176             # sys.exit()
177
178     fileHandle = open(configPath, mode='w')
179     fileHandle.write(config.serialize())
180     fileHandle.close()
181
182
183 def generateOCIOTransform(transforms):
184     """
185     Object description.
186
187     Parameters
188     ----------
189     parameter : type
190         Parameter description.
191
192     Returns
193     -------
194     type
195          Return value description.
196     """
197
198     # print("Generating transforms")
199
200     interpolationOptions = {
201         'linear': OCIO.Constants.INTERP_LINEAR,
202         'nearest': OCIO.Constants.INTERP_NEAREST,
203         'tetrahedral': OCIO.Constants.INTERP_TETRAHEDRAL
204     }
205     directionOptions = {
206         'forward': OCIO.Constants.TRANSFORM_DIR_FORWARD,
207         'inverse': OCIO.Constants.TRANSFORM_DIR_INVERSE
208     }
209
210     ocioTransforms = []
211
212     for transform in transforms:
213         if transform['type'] == 'lutFile':
214             ocioTransform = OCIO.FileTransform(
215                 src=transform['path'],
216                 interpolation=interpolationOptions[transform['interpolation']],
217                 direction=directionOptions[transform['direction']])
218             ocioTransforms.append(ocioTransform)
219         elif transform['type'] == 'matrix':
220             ocioTransform = OCIO.MatrixTransform()
221             # MatrixTransform member variables can't be initialized directly.
222             # Each must be set individually.
223             ocioTransform.setMatrix(transform['matrix'])
224
225             if 'offset' in transform:
226                 ocioTransform.setOffset(transform['offset'])
227
228             if 'direction' in transform:
229                 ocioTransform.setDirection(
230                     directionOptions[transform['direction']])
231
232             ocioTransforms.append(ocioTransform)
233         elif transform['type'] == 'exponent':
234             ocioTransform = OCIO.ExponentTransform()
235             ocioTransform.setValue(transform['value'])
236             ocioTransforms.append(ocioTransform)
237         elif transform['type'] == 'log':
238             ocioTransform = OCIO.LogTransform(
239                 base=transform['base'],
240                 direction=directionOptions[transform['direction']])
241
242             ocioTransforms.append(ocioTransform)
243         else:
244             print("Ignoring unknown transform type : %s" % transform['type'])
245
246     # Build a group transform if necessary
247     if len(ocioTransforms) > 1:
248         transformG = OCIO.GroupTransform()
249         for transform in ocioTransforms:
250             transformG.push_back(transform)
251         transform = transformG
252
253     # Or take the first transform from the list
254     else:
255         transform = ocioTransforms[0]
256
257     return transform
258
259
260 def createConfig(configData, nuke=False):
261     """
262     Object description.
263
264     Parameters
265     ----------
266     parameter : type
267         Parameter description.
268
269     Returns
270     -------
271     type
272          Return value description.
273     """
274
275     # Create the config
276     config = OCIO.Config()
277
278     #
279     # Set config wide values
280     #
281     config.setDescription("An ACES config generated from python")
282     config.setSearchPath("luts")
283
284     #
285     # Define the reference color space
286     #
287     referenceData = configData['referenceColorSpace']
288     print("Adding the reference color space : %s" % referenceData.name)
289
290     # Create a color space
291     reference = OCIO.ColorSpace(
292         name=referenceData.name,
293         bitDepth=referenceData.bitDepth,
294         description=referenceData.description,
295         equalityGroup=referenceData.equalityGroup,
296         family=referenceData.family,
297         isData=referenceData.isData,
298         allocation=referenceData.allocationType,
299         allocationVars=referenceData.allocationVars)
300
301     # Add to config
302     config.addColorSpace(reference)
303
304     #
305     # Create the rest of the color spaces
306     #
307     for colorspace in sorted(configData['colorSpaces']):
308         print("Creating new color space : %s" % colorspace.name)
309
310         ocioColorspace = OCIO.ColorSpace(
311             name=colorspace.name,
312             bitDepth=colorspace.bitDepth,
313             description=colorspace.description,
314             equalityGroup=colorspace.equalityGroup,
315             family=colorspace.family,
316             isData=colorspace.isData,
317             allocation=colorspace.allocationType,
318             allocationVars=colorspace.allocationVars)
319
320         if colorspace.toReferenceTransforms != []:
321             print("Generating To-Reference transforms")
322             ocioTransform = generateOCIOTransform(
323                 colorspace.toReferenceTransforms)
324             ocioColorspace.setTransform(
325                 ocioTransform,
326                 OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE)
327
328         if colorspace.fromReferenceTransforms != []:
329             print("Generating From-Reference transforms")
330             ocioTransform = generateOCIOTransform(
331                 colorspace.fromReferenceTransforms)
332             ocioColorspace.setTransform(
333                 ocioTransform,
334                 OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE)
335
336         config.addColorSpace(ocioColorspace)
337
338         print("")
339
340     #
341     # Define the views and displays
342     #
343     displays = []
344     views = []
345
346     # Generic display and view setup
347     if not nuke:
348         for display, viewList in configData['displays'].iteritems():
349             for viewName, colorspace in viewList.iteritems():
350                 config.addDisplay(display, viewName, colorspace.name)
351                 if not (viewName in views):
352                     views.append(viewName)
353             displays.append(display)
354     # A Nuke specific set of views and displays
355     #
356     # XXX
357     # A few names: Output Transform, ACES, ACEScc, are hard-coded here.
358     # Would be better to automate.
359     #
360     else:
361         for display, viewList in configData['displays'].iteritems():
362             for viewName, colorspace in viewList.iteritems():
363                 if (viewName == 'Output Transform'):
364                     viewName = 'View'
365                     config.addDisplay(display, viewName, colorspace.name)
366                     if not (viewName in views):
367                         views.append(viewName)
368             displays.append(display)
369
370         config.addDisplay('linear', 'View', 'ACES2065-1')
371         displays.append('linear')
372         config.addDisplay('log', 'View', 'ACEScc')
373         displays.append('log')
374
375     # Set active displays and views
376     config.setActiveDisplays(','.join(sorted(displays)))
377     config.setActiveViews(','.join(views))
378
379     #
380     # Need to generalize this at some point
381     #
382
383     # Add Default Roles
384     setConfigDefaultRoles(config,
385                           color_picking=reference.getName(),
386                           color_timing=reference.getName(),
387                           compositing_log=reference.getName(),
388                           data=reference.getName(),
389                           default=reference.getName(),
390                           matte_paint=reference.getName(),
391                           reference=reference.getName(),
392                           scene_linear=reference.getName(),
393                           texture_paint=reference.getName())
394
395     # Check to make sure we didn't screw something up
396     config.sanityCheck()
397
398     return config
399
400
401 def generateLUTs(odtInfo,
402                  lmtInfo,
403                  shaperName,
404                  acesCTLReleaseDir,
405                  lutDir,
406                  lutResolution1d=4096,
407                  lutResolution3d=64,
408                  cleanup=True):
409     """
410     Object description.
411
412     Parameters
413     ----------
414     parameter : type
415         Parameter description.
416
417     Returns
418     -------
419     dict
420          Colorspaces and transforms converting between those colorspaces and
421          the reference colorspace, *ACES*.
422     """
423
424     print("generateLUTs - begin")
425     configData = {}
426
427     #
428     # Define the reference color space
429     #
430     ACES = ColorSpace('ACES2065-1')
431     ACES.description = (
432         'The Academy Color Encoding System reference color space')
433     ACES.equalityGroup = ''
434     ACES.family = 'ACES'
435     ACES.isData = False
436     ACES.allocationType = OCIO.Constants.ALLOCATION_LG2
437     ACES.allocationVars = [-15, 6]
438
439     configData['referenceColorSpace'] = ACES
440
441     #
442     # Define the displays
443     #
444     configData['displays'] = {}
445
446     #
447     # Define the other color spaces
448     #
449     configData['colorSpaces'] = []
450
451     # Matrix converting ACES AP1 primaries to AP0
452     acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
453                     0.0447945634, 0.8596711185, 0.0955343182,
454                     -0.0055258826, 0.0040252103, 1.0015006723]
455
456     # Matrix converting ACES AP0 primaries to XYZ
457     acesAP0toXYZ = [0.9525523959, 0.0000000000, 0.0000936786,
458                     0.3439664498, 0.7281660966, -0.0721325464,
459                     0.0000000000, 0.0000000000, 1.0088251844]
460
461     #
462     # ACEScc
463     #
464     def createACEScc(name='ACEScc',
465                      minValue=0.0,
466                      maxValue=1.0,
467                      inputScale=1.0):
468         cs = ColorSpace(name)
469         cs.description = "The %s color space" % name
470         cs.equalityGroup = ''
471         cs.family = 'ACES'
472         cs.isData = False
473
474         ctls = [
475             '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
476             # This transform gets back to the AP1 primaries
477             # Useful as the 1d LUT is only covering the transfer function
478             # The primaries switch is covered by the matrix below
479             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
480         ]
481         lut = "%s_to_ACES.spi1d" % name
482
483         # Remove spaces and parentheses
484         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
485
486         genlut.generate1dLUTFromCTL(
487             lutDir + "/" + lut,
488             ctls,
489             lutResolution1d,
490             'float',
491             inputScale,
492             1.0,
493             {},
494             cleanup,
495             acesCTLReleaseDir,
496             minValue,
497             maxValue)
498
499         cs.toReferenceTransforms = []
500         cs.toReferenceTransforms.append({
501             'type': 'lutFile',
502             'path': lut,
503             'interpolation': 'linear',
504             'direction': 'forward'
505         })
506
507         # AP1 primaries to AP0 primaries
508         cs.toReferenceTransforms.append({
509             'type': 'matrix',
510             'matrix': mat44FromMat33(acesAP1toAP0),
511             'direction': 'forward'
512         })
513
514         cs.fromReferenceTransforms = []
515         return cs
516
517     ACEScc = createACEScc()
518     configData['colorSpaces'].append(ACEScc)
519
520     #
521     # ACESproxy
522     #
523     def createACESProxy(name='ACESproxy'):
524         cs = ColorSpace(name)
525         cs.description = "The %s color space" % name
526         cs.equalityGroup = ''
527         cs.family = 'ACES'
528         cs.isData = False
529
530         ctls = [
531             '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % (
532                 acesCTLReleaseDir),
533             # This transform gets back to the AP1 primaries
534             # Useful as the 1d LUT is only covering the transfer function
535             # The primaries switch is covered by the matrix below
536             '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
537         ]
538         lut = "%s_to_aces.spi1d" % name
539
540         # Remove spaces and parentheses
541         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
542
543         genlut.generate1dLUTFromCTL(
544             lutDir + "/" + lut,
545             ctls,
546             lutResolution1d,
547             'uint16',
548             64.0,
549             1.0,
550             {},
551             cleanup,
552             acesCTLReleaseDir)
553
554         cs.toReferenceTransforms = []
555         cs.toReferenceTransforms.append({
556             'type': 'lutFile',
557             'path': lut,
558             'interpolation': 'linear',
559             'direction': 'forward'
560         })
561
562         # AP1 primaries to AP0 primaries
563         cs.toReferenceTransforms.append({
564             'type': 'matrix',
565             'matrix': mat44FromMat33(acesAP1toAP0),
566             'direction': 'forward'
567         })
568
569         cs.fromReferenceTransforms = []
570         return cs
571
572     ACESproxy = createACESProxy()
573     configData['colorSpaces'].append(ACESproxy)
574
575     #
576     # ACEScg
577     #
578     def createACEScg(name='ACEScg'):
579         cs = ColorSpace(name)
580         cs.description = "The %s color space" % name
581         cs.equalityGroup = ''
582         cs.family = 'ACES'
583         cs.isData = False
584
585         cs.toReferenceTransforms = []
586
587         # AP1 primaries to AP0 primaries
588         cs.toReferenceTransforms.append({
589             'type': 'matrix',
590             'matrix': mat44FromMat33(acesAP1toAP0),
591             'direction': 'forward'
592         })
593
594         cs.fromReferenceTransforms = []
595         return cs
596
597     ACEScg = createACEScg()
598     configData['colorSpaces'].append(ACEScg)
599
600     #
601     # ADX
602     #
603     def createADX(bitdepth=10, name='ADX'):
604         name = "%s%s" % (name, bitdepth)
605         cs = ColorSpace(name)
606         cs.description = "%s color space - used for film scans" % name
607         cs.equalityGroup = ''
608         cs.family = 'ADX'
609         cs.isData = False
610
611         if bitdepth == 10:
612             cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT10
613             adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
614                           0.0, 1023.0 / 500.0, 0.0, 0.0,
615                           0.0, 0.0, 1023.0 / 500.0, 0.0,
616                           0.0, 0.0, 0.0, 1.0]
617             offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
618         elif bitdepth == 16:
619             cs.bitDepth = bitDepth = OCIO.Constants.BIT_DEPTH_UINT16
620             adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
621                           0.0, 65535.0 / 8000.0, 0.0, 0.0,
622                           0.0, 0.0, 65535.0 / 8000.0, 0.0,
623                           0.0, 0.0, 0.0, 1.0]
624             offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
625                       0.0]
626
627         cs.toReferenceTransforms = []
628
629         # Convert from ADX to Channel-Dependent Density
630         cs.toReferenceTransforms.append({
631             'type': 'matrix',
632             'matrix': adx_to_cdd,
633             'offset': offset,
634             'direction': 'forward'
635         })
636
637         # Convert from Channel-Dependent Density to Channel-Independent Density
638         cs.toReferenceTransforms.append({
639             'type': 'matrix',
640             'matrix': [0.75573, 0.22197, 0.02230, 0,
641                        0.05901, 0.96928, -0.02829, 0,
642                        0.16134, 0.07406, 0.76460, 0,
643                        0.0, 0.0, 0.0, 1.0],
644             'direction': 'forward'
645         })
646
647         # Copied from Alex Fry's adx_cid_to_rle.py
648         def createCIDtoRLELUT():
649             def interpolate1D(x, xp, fp):
650                 return numpy.interp(x, xp, fp)
651
652             LUT_1D_xp = [-0.190000000000000,
653                          0.010000000000000,
654                          0.028000000000000,
655                          0.054000000000000,
656                          0.095000000000000,
657                          0.145000000000000,
658                          0.220000000000000,
659                          0.300000000000000,
660                          0.400000000000000,
661                          0.500000000000000,
662                          0.600000000000000]
663
664             LUT_1D_fp = [-6.000000000000000,
665                          -2.721718645000000,
666                          -2.521718645000000,
667                          -2.321718645000000,
668                          -2.121718645000000,
669                          -1.921718645000000,
670                          -1.721718645000000,
671                          -1.521718645000000,
672                          -1.321718645000000,
673                          -1.121718645000000,
674                          -0.926545676714876]
675
676             REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
677                       math.log(0.18, 10.0))
678
679             def cid_to_rle(x):
680                 if x <= 0.6:
681                     return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
682                 return (100.0 / 55.0) * x - REF_PT
683
684             def Fit(value, fromMin, fromMax, toMin, toMax):
685                 if fromMin == fromMax:
686                     raise ValueError("fromMin == fromMax")
687                 return (value - fromMin) / (fromMax - fromMin) * (
688                     toMax - toMin) + toMin
689
690             NUM_SAMPLES = 2 ** 12
691             RANGE = (-0.19, 3.0)
692             data = []
693             for i in xrange(NUM_SAMPLES):
694                 x = i / (NUM_SAMPLES - 1.0)
695                 x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
696                 data.append(cid_to_rle(x))
697
698             lut = 'ADX_CID_to_RLE.spi1d'
699             genlut.writeSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data,
700                               NUM_SAMPLES, 1)
701
702             return lut
703
704         # Convert Channel Independent Density values to Relative Log Exposure
705         # values.
706         lut = createCIDtoRLELUT()
707         cs.toReferenceTransforms.append({
708             'type': 'lutFile',
709             'path': lut,
710             'interpolation': 'linear',
711             'direction': 'forward'
712         })
713
714         # Convert Relative Log Exposure values to Relative Exposure values
715         cs.toReferenceTransforms.append({
716             'type': 'log',
717             'base': 10,
718             'direction': 'inverse'
719         })
720
721         # Convert Relative Exposure values to ACES values
722         cs.toReferenceTransforms.append({
723             'type': 'matrix',
724             'matrix': [0.72286, 0.12630, 0.15084, 0,
725                        0.11923, 0.76418, 0.11659, 0,
726                        0.01427, 0.08213, 0.90359, 0,
727                        0.0, 0.0, 0.0, 1.0],
728             'direction': 'forward'
729         })
730
731         cs.fromReferenceTransforms = []
732         return cs
733
734     ADX10 = createADX(bitdepth=10)
735     configData['colorSpaces'].append(ADX10)
736
737     ADX16 = createADX(bitdepth=16)
738     configData['colorSpaces'].append(ADX16)
739
740     #
741     # Camera Input Transforms
742     #
743
744     # RED color spaces to ACES
745     redColorSpaces = red.createColorSpaces(lutDir, lutResolution1d)
746     for cs in redColorSpaces:
747         configData['colorSpaces'].append(cs)
748
749     # Canon-Log to ACES
750     canonColorSpaces = canon.createColorSpaces(lutDir, lutResolution1d)
751     for cs in canonColorSpaces:
752         configData['colorSpaces'].append(cs)
753
754     # SLog to ACES
755     sonyColorSpaces = sony.createColorSpaces(lutDir, lutResolution1d)
756     for cs in sonyColorSpaces:
757         configData['colorSpaces'].append(cs)
758
759     # LogC to ACES
760     arriColorSpaces = arri.createColorSpaces(lutDir, lutResolution1d)
761     for cs in arriColorSpaces:
762         configData['colorSpaces'].append(cs)
763
764     #
765     # Generic log transform
766     #
767     def createGenericLog(name='log',
768                          minValue=0.0,
769                          maxValue=1.0,
770                          inputScale=1.0,
771                          middleGrey=0.18,
772                          minExposure=-6.0,
773                          maxExposure=6.5,
774                          lutResolution1d=lutResolution1d):
775         cs = ColorSpace(name)
776         cs.description = "The %s color space" % name
777         cs.equalityGroup = name
778         cs.family = 'Utility'
779         cs.isData = False
780
781         ctls = [
782             '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % (
783                 acesCTLReleaseDir)
784         ]
785         lut = "%s_to_aces.spi1d" % name
786
787         # Remove spaces and parentheses
788         lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
789
790         genlut.generate1dLUTFromCTL(
791             lutDir + "/" + lut,
792             ctls,
793             lutResolution1d,
794             'float',
795             inputScale,
796             1.0,
797             {
798                 'middleGrey': middleGrey,
799                 'minExposure': minExposure,
800                 'maxExposure': maxExposure
801             },
802             cleanup,
803             acesCTLReleaseDir,
804             minValue,
805             maxValue)
806
807         cs.toReferenceTransforms = []
808         cs.toReferenceTransforms.append({
809             'type': 'lutFile',
810             'path': lut,
811             'interpolation': 'linear',
812             'direction': 'forward'
813         })
814
815         cs.fromReferenceTransforms = []
816         return cs
817
818     #
819     # ACES LMTs
820     #
821     def createACESLMT(lmtName,
822                       lmtValues,
823                       shaperInfo,
824                       lutResolution1d=1024,
825                       lutResolution3d=64,
826                       cleanup=True):
827         cs = ColorSpace("%s" % lmtName)
828         cs.description = "The ACES Look Transform: %s" % lmtName
829         cs.equalityGroup = ''
830         cs.family = 'Look'
831         cs.isData = False
832
833         import pprint
834
835         pprint.pprint(lmtValues)
836
837         #
838         # Generate the shaper transform
839         #
840         (shaperName,
841          shaperToACESCTL,
842          shaperFromACESCTL,
843          shaperInputScale,
844          shaperParams) = shaperInfo
845
846         shaperLut = "%s_to_aces.spi1d" % shaperName
847         if (not os.path.exists(lutDir + "/" + shaperLut)):
848             ctls = [
849                 shaperToACESCTL % acesCTLReleaseDir
850             ]
851
852             # Remove spaces and parentheses
853             shaperLut = shaperLut.replace(
854                 ' ', '_').replace(')', '_').replace('(', '_')
855
856             genlut.generate1dLUTFromCTL(
857                 lutDir + "/" + shaperLut,
858                 ctls,
859                 lutResolution1d,
860                 'float',
861                 1.0 / shaperInputScale,
862                 1.0,
863                 shaperParams,
864                 cleanup,
865                 acesCTLReleaseDir)
866
867         shaperOCIOTransform = {
868             'type': 'lutFile',
869             'path': shaperLut,
870             'interpolation': 'linear',
871             'direction': 'inverse'
872         }
873
874         #
875         # Generate the forward transform
876         #
877         cs.fromReferenceTransforms = []
878
879         if 'transformCTL' in lmtValues:
880             ctls = [
881                 shaperToACESCTL % acesCTLReleaseDir,
882                 '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
883             ]
884             lut = "%s.%s.spi3d" % (shaperName, lmtName)
885
886             # Remove spaces and parentheses
887             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
888
889             genlut.generate3dLUTFromCTL(
890                 lutDir + "/" + lut,
891                 ctls,
892                 lutResolution3d,
893                 'float',
894                 1.0 / shaperInputScale,
895                 1.0,
896                 shaperParams,
897                 cleanup,
898                 acesCTLReleaseDir)
899
900             cs.fromReferenceTransforms.append(shaperOCIOTransform)
901             cs.fromReferenceTransforms.append({
902                 'type': 'lutFile',
903                 'path': lut,
904                 'interpolation': 'tetrahedral',
905                 'direction': 'forward'
906             })
907
908         #
909         # Generate the inverse transform
910         #
911         cs.toReferenceTransforms = []
912
913         if 'transformCTLInverse' in lmtValues:
914             ctls = [
915                 '%s/%s' % (
916                     acesCTLReleaseDir, odtValues['transformCTLInverse']),
917                 shaperFromACESCTL % acesCTLReleaseDir
918             ]
919             lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
920
921             # Remove spaces and parentheses
922             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
923
924             genlut.generate3dLUTFromCTL(
925                 lutDir + "/" + lut,
926                 ctls,
927                 lutResolution3d,
928                 'half',
929                 1.0,
930                 shaperInputScale,
931                 shaperParams,
932                 cleanup,
933                 acesCTLReleaseDir)
934
935             cs.toReferenceTransforms.append({
936                 'type': 'lutFile',
937                 'path': lut,
938                 'interpolation': 'tetrahedral',
939                 'direction': 'forward'
940             })
941
942             shaperInverse = shaperOCIOTransform.copy()
943             shaperInverse['direction'] = 'forward'
944             cs.toReferenceTransforms.append(shaperInverse)
945
946         return cs
947
948     #
949     # LMT Shaper
950     #
951
952     lmtLutResolution1d = max(4096, lutResolution1d)
953     lmtLutResolution3d = max(65, lutResolution3d)
954
955     # Log 2 shaper
956     lmtShaperName = 'LMT Shaper'
957     lmtParams = {
958         'middleGrey': 0.18,
959         'minExposure': -10.0,
960         'maxExposure': 6.5
961     }
962     lmtShaper = createGenericLog(name=lmtShaperName,
963                                  middleGrey=lmtParams['middleGrey'],
964                                  minExposure=lmtParams['minExposure'],
965                                  maxExposure=lmtParams['maxExposure'],
966                                  lutResolution1d=lmtLutResolution1d)
967     configData['colorSpaces'].append(lmtShaper)
968
969     shaperInputScale_genericLog2 = 1.0
970
971     # Log 2 shaper name and CTL transforms bundled up
972     lmtShaperData = [
973         lmtShaperName,
974         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
975         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
976         shaperInputScale_genericLog2,
977         lmtParams
978     ]
979
980     sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
981     print(sortedLMTs)
982     for lmt in sortedLMTs:
983         (lmtName, lmtValues) = lmt
984         cs = createACESLMT(
985             lmtValues['transformUserName'],
986             lmtValues,
987             lmtShaperData,
988             lmtLutResolution1d,
989             lmtLutResolution3d,
990             cleanup)
991         configData['colorSpaces'].append(cs)
992
993     #
994     # ACES RRT with the supplied ODT
995     #
996     def createACESRRTplusODT(odtName,
997                              odtValues,
998                              shaperInfo,
999                              lutResolution1d=1024,
1000                              lutResolution3d=64,
1001                              cleanup=True):
1002         cs = ColorSpace("%s" % odtName)
1003         cs.description = "%s - %s Output Transform" % (
1004             odtValues['transformUserNamePrefix'], odtName)
1005         cs.equalityGroup = ''
1006         cs.family = 'Output'
1007         cs.isData = False
1008
1009         import pprint
1010
1011         pprint.pprint(odtValues)
1012
1013         #
1014         # Generate the shaper transform
1015         #
1016         # if 'shaperCTL' in odtValues:
1017         (shaperName,
1018          shaperToACESCTL,
1019          shaperFromACESCTL,
1020          shaperInputScale,
1021          shaperParams) = shaperInfo
1022
1023         if 'legalRange' in odtValues:
1024             shaperParams['legalRange'] = odtValues['legalRange']
1025         else:
1026             shaperParams['legalRange'] = 0
1027
1028         shaperLut = "%s_to_aces.spi1d" % shaperName
1029         if (not os.path.exists(lutDir + "/" + shaperLut)):
1030             ctls = [
1031                 shaperToACESCTL % acesCTLReleaseDir
1032             ]
1033
1034             # Remove spaces and parentheses
1035             shaperLut = shaperLut.replace(
1036                 ' ', '_').replace(')', '_').replace('(', '_')
1037
1038             genlut.generate1dLUTFromCTL(
1039                 lutDir + "/" + shaperLut,
1040                 ctls,
1041                 lutResolution1d,
1042                 'float',
1043                 1.0 / shaperInputScale,
1044                 1.0,
1045                 shaperParams,
1046                 cleanup,
1047                 acesCTLReleaseDir)
1048
1049         shaperOCIOTransform = {
1050             'type': 'lutFile',
1051             'path': shaperLut,
1052             'interpolation': 'linear',
1053             'direction': 'inverse'
1054         }
1055
1056         #
1057         # Generate the forward transform
1058         #
1059         cs.fromReferenceTransforms = []
1060
1061         if 'transformLUT' in odtValues:
1062             # Copy into the lut dir
1063             transformLUTFileName = os.path.basename(odtValues['transformLUT'])
1064             lut = lutDir + "/" + transformLUTFileName
1065             shutil.copy(odtValues['transformLUT'], lut)
1066
1067             cs.fromReferenceTransforms.append(shaperOCIOTransform)
1068             cs.fromReferenceTransforms.append({
1069                 'type': 'lutFile',
1070                 'path': transformLUTFileName,
1071                 'interpolation': 'tetrahedral',
1072                 'direction': 'forward'
1073             })
1074         elif 'transformCTL' in odtValues:
1075             # shaperLut
1076
1077             ctls = [
1078                 shaperToACESCTL % acesCTLReleaseDir,
1079                 '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir,
1080                 '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
1081             ]
1082             lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
1083
1084             # Remove spaces and parentheses
1085             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1086
1087             genlut.generate3dLUTFromCTL(lutDir + "/" + lut,
1088                                         # shaperLUT,
1089                                         ctls,
1090                                         lutResolution3d,
1091                                         'float',
1092                                         1.0 / shaperInputScale,
1093                                         1.0,
1094                                         shaperParams,
1095                                         cleanup,
1096                                         acesCTLReleaseDir)
1097
1098             cs.fromReferenceTransforms.append(shaperOCIOTransform)
1099             cs.fromReferenceTransforms.append({
1100                 'type': 'lutFile',
1101                 'path': lut,
1102                 'interpolation': 'tetrahedral',
1103                 'direction': 'forward'
1104             })
1105
1106         #
1107         # Generate the inverse transform
1108         #
1109         cs.toReferenceTransforms = []
1110
1111         if 'transformLUTInverse' in odtValues:
1112             # Copy into the lut dir
1113             transformLUTInverseFileName = os.path.basename(
1114                 odtValues['transformLUTInverse'])
1115             lut = lutDir + "/" + transformLUTInverseFileName
1116             shutil.copy(odtValues['transformLUTInverse'], lut)
1117
1118             cs.toReferenceTransforms.append({
1119                 'type': 'lutFile',
1120                 'path': transformLUTInverseFileName,
1121                 'interpolation': 'tetrahedral',
1122                 'direction': 'forward'
1123             })
1124
1125             shaperInverse = shaperOCIOTransform.copy()
1126             shaperInverse['direction'] = 'forward'
1127             cs.toReferenceTransforms.append(shaperInverse)
1128         elif 'transformCTLInverse' in odtValues:
1129             ctls = [
1130                 '%s/odt/%s' % (
1131                     acesCTLReleaseDir, odtValues['transformCTLInverse']),
1132                 '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
1133                 shaperFromACESCTL % acesCTLReleaseDir
1134             ]
1135             lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
1136
1137             # Remove spaces and parentheses
1138             lut = lut.replace(' ', '_').replace(')', '_').replace('(', '_')
1139
1140             genlut.generate3dLUTFromCTL(
1141                 lutDir + "/" + lut,
1142                 # None,
1143                 ctls,
1144                 lutResolution3d,
1145                 'half',
1146                 1.0,
1147                 shaperInputScale,
1148                 shaperParams,
1149                 cleanup,
1150                 acesCTLReleaseDir)
1151
1152             cs.toReferenceTransforms.append({
1153                 'type': 'lutFile',
1154                 'path': lut,
1155                 'interpolation': 'tetrahedral',
1156                 'direction': 'forward'
1157             })
1158
1159             shaperInverse = shaperOCIOTransform.copy()
1160             shaperInverse['direction'] = 'forward'
1161             cs.toReferenceTransforms.append(shaperInverse)
1162
1163         return cs
1164
1165     #
1166     # RRT/ODT shaper options
1167     #
1168     shaperData = {}
1169
1170     # Log 2 shaper
1171     log2ShaperName = shaperName
1172     log2Params = {
1173         'middleGrey': 0.18,
1174         'minExposure': -6.0,
1175         'maxExposure': 6.5
1176     }
1177     log2Shaper = createGenericLog(name=log2ShaperName,
1178                                   middleGrey=log2Params['middleGrey'],
1179                                   minExposure=log2Params['minExposure'],
1180                                   maxExposure=log2Params['maxExposure'])
1181     configData['colorSpaces'].append(log2Shaper)
1182
1183     shaperInputScale_genericLog2 = 1.0
1184
1185     # Log 2 shaper name and CTL transforms bundled up
1186     log2ShaperData = [
1187         log2ShaperName,
1188         '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
1189         '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
1190         shaperInputScale_genericLog2,
1191         log2Params
1192     ]
1193
1194     shaperData[log2ShaperName] = log2ShaperData
1195
1196     #
1197     # Shaper that also includes the AP1 primaries
1198     # - Needed for some LUT baking steps
1199     #
1200     log2ShaperAP1 = createGenericLog(name=log2ShaperName,
1201                                      middleGrey=log2Params['middleGrey'],
1202                                      minExposure=log2Params['minExposure'],
1203                                      maxExposure=log2Params['maxExposure'])
1204     log2ShaperAP1.name = "%s - AP1" % log2ShaperAP1.name
1205     # AP1 primaries to AP0 primaries
1206     log2ShaperAP1.toReferenceTransforms.append({
1207         'type': 'matrix',
1208         'matrix': mat44FromMat33(acesAP1toAP0),
1209         'direction': 'forward'
1210     })
1211     configData['colorSpaces'].append(log2ShaperAP1)
1212
1213     #
1214     # Choose your shaper
1215     #
1216     rrtShaperName = log2ShaperName
1217     rrtShaper = log2ShaperData
1218
1219     #
1220     # RRT + ODT Combinations
1221     #
1222     sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
1223     print(sortedOdts)
1224     for odt in sortedOdts:
1225         (odtName, odtValues) = odt
1226
1227         # Have to handle ODTs that can generate either legal or full output
1228         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1229                        'Academy.Rec709_100nits_dim.a1.0.0',
1230                        'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1231             odtNameLegal = '%s - Legal' % odtValues['transformUserName']
1232         else:
1233             odtNameLegal = odtValues['transformUserName']
1234
1235         odtLegal = odtValues.copy()
1236         odtLegal['legalRange'] = 1
1237
1238         cs = createACESRRTplusODT(
1239             odtNameLegal,
1240             odtLegal,
1241             rrtShaper,
1242             lutResolution1d,
1243             lutResolution3d,
1244             cleanup)
1245         configData['colorSpaces'].append(cs)
1246
1247         # Create a display entry using this color space
1248         configData['displays'][odtNameLegal] = {
1249             'Linear': ACES,
1250             'Log': ACEScc,
1251             'Output Transform': cs}
1252
1253         if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1254                        'Academy.Rec709_100nits_dim.a1.0.0',
1255                        'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1256             print("Generating full range ODT for %s" % odtName)
1257
1258             odtNameFull = "%s - Full" % odtValues['transformUserName']
1259             odtFull = odtValues.copy()
1260             odtFull['legalRange'] = 0
1261
1262             csFull = createACESRRTplusODT(
1263                 odtNameFull,
1264                 odtFull,
1265                 rrtShaper,
1266                 lutResolution1d,
1267                 lutResolution3d,
1268                 cleanup)
1269             configData['colorSpaces'].append(csFull)
1270
1271             # Create a display entry using this color space
1272             configData['displays'][odtNameFull] = {
1273                 'Linear': ACES,
1274                 'Log': ACEScc,
1275                 'Output Transform': csFull}
1276
1277     #
1278     # Generic Matrix transform
1279     #
1280     def createGenericMatrix(name='matrix',
1281                             fromReferenceValues=[],
1282                             toReferenceValues=[]):
1283         cs = ColorSpace(name)
1284         cs.description = "The %s color space" % name
1285         cs.equalityGroup = name
1286         cs.family = 'Utility'
1287         cs.isData = False
1288
1289         cs.toReferenceTransforms = []
1290         if toReferenceValues != []:
1291             for matrix in toReferenceValues:
1292                 cs.toReferenceTransforms.append({
1293                     'type': 'matrix',
1294                     'matrix': mat44FromMat33(matrix),
1295                     'direction': 'forward'
1296                 })
1297
1298         cs.fromReferenceTransforms = []
1299         if fromReferenceValues != []:
1300             for matrix in fromReferenceValues:
1301                 cs.fromReferenceTransforms.append({
1302                     'type': 'matrix',
1303                     'matrix': mat44FromMat33(matrix),
1304                     'direction': 'forward'
1305                 })
1306
1307         return cs
1308
1309     cs = createGenericMatrix('XYZ', fromReferenceValues=[acesAP0toXYZ])
1310     configData['colorSpaces'].append(cs)
1311
1312     cs = createGenericMatrix('Linear - AP1', toReferenceValues=[acesAP1toAP0])
1313     configData['colorSpaces'].append(cs)
1314
1315     # ACES to Linear, P3D60 primaries
1316     xyzToP3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
1317                   -0.8325796487, 1.7692317536, 0.0237127115,
1318                   0.0388233815, -0.0824996856, 1.0363685997]
1319
1320     cs = createGenericMatrix('Linear - P3-D60',
1321                              fromReferenceValues=[acesAP0toXYZ, xyzToP3D60])
1322     configData['colorSpaces'].append(cs)
1323
1324     # ACES to Linear, P3D60 primaries
1325     xyzToP3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
1326                   -0.7951680258, 1.6897320548, 0.0226471906,
1327                   0.0412418914, -0.0876390192, 1.1009293786]
1328
1329     cs = createGenericMatrix('Linear - P3-DCI',
1330                              fromReferenceValues=[acesAP0toXYZ, xyzToP3DCI])
1331     configData['colorSpaces'].append(cs)
1332
1333     # ACES to Linear, Rec 709 primaries
1334     xyzToRec709 = [3.2409699419, -1.5373831776, -0.4986107603,
1335                    -0.9692436363, 1.8759675015, 0.0415550574,
1336                    0.0556300797, -0.2039769589, 1.0569715142]
1337
1338     cs = createGenericMatrix('Linear - Rec.709',
1339                              fromReferenceValues=[acesAP0toXYZ, xyzToRec709])
1340     configData['colorSpaces'].append(cs)
1341
1342     # ACES to Linear, Rec 2020 primaries
1343     xyzToRec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
1344                     -0.6666843518, 1.6164812366, 0.0157685458,
1345                     0.0176398574, -0.0427706133, 0.9421031212]
1346
1347     cs = createGenericMatrix('Linear - Rec.2020',
1348                              fromReferenceValues=[acesAP0toXYZ, xyzToRec2020])
1349     configData['colorSpaces'].append(cs)
1350
1351     print("generateLUTs - end")
1352     return configData
1353
1354
1355 def generateBakedLUTs(odtInfo,
1356                       shaperName,
1357                       bakedDir,
1358                       configPath,
1359                       lutResolution1d,
1360                       lutResolution3d,
1361                       lutResolutionShaper=1024):
1362     """
1363     Object description.
1364
1365     Parameters
1366     ----------
1367     parameter : type
1368         Parameter description.
1369
1370     Returns
1371     -------
1372     type
1373          Return value description.
1374     """
1375
1376     # Add the legal and full variations into this list
1377     odtInfoC = dict(odtInfo)
1378     for odtCTLName, odtValues in odtInfo.iteritems():
1379         if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0',
1380                           'Academy.Rec709_100nits_dim.a1.0.0',
1381                           'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
1382             odtName = odtValues["transformUserName"]
1383
1384             odtValuesLegal = dict(odtValues)
1385             odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
1386             odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
1387
1388             odtValuesFull = dict(odtValues)
1389             odtValuesFull["transformUserName"] = "%s - Full" % odtName
1390             odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
1391
1392             del (odtInfoC[odtCTLName])
1393
1394     for odtCTLName, odtValues in odtInfoC.iteritems():
1395         odtPrefix = odtValues["transformUserNamePrefix"]
1396         odtName = odtValues["transformUserName"]
1397
1398         # For Photoshop
1399         for inputspace in ["ACEScc", "ACESproxy"]:
1400             args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1401             args += ["--outputspace", "%s" % odtName]
1402             args += ["--description",
1403                      "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1404             args += ["--shaperspace", shaperName, "--shapersize",
1405                      str(lutResolutionShaper)]
1406             args += ["--cubesize", str(lutResolution3d)]
1407             args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (
1408                 bakedDir, odtName, inputspace)]
1409
1410             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1411                               args=args)
1412             bakeLUT.execute()
1413
1414             # For Flame, Lustre
1415         for inputspace in ["ACEScc", "ACESproxy"]:
1416             args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1417             args += ["--outputspace", "%s" % odtName]
1418             args += ["--description",
1419                      "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1420             args += ["--shaperspace", shaperName, "--shapersize",
1421                      str(lutResolutionShaper)]
1422             args += ["--cubesize", str(lutResolution3d)]
1423
1424             fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (
1425                 bakedDir, odtName, inputspace)]
1426             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1427                               args=(args + fargs))
1428             bakeLUT.execute()
1429
1430             largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (
1431                 bakedDir, odtName, inputspace)]
1432             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1433                               args=(args + largs))
1434             bakeLUT.execute()
1435
1436         # For Maya, Houdini
1437         for inputspace in ["ACEScg", "ACES2065-1"]:
1438             args = ["--iconfig", configPath, "-v", "--inputspace", inputspace]
1439             args += ["--outputspace", "%s" % odtName]
1440             args += ["--description",
1441                      "%s - %s for %s data" % (odtPrefix, odtName, inputspace)]
1442             if inputspace == 'ACEScg':
1443                 linShaperName = "%s - AP1" % shaperName
1444             else:
1445                 linShaperName = shaperName
1446             args += ["--shaperspace", linShaperName, "--shapersize",
1447                      str(lutResolutionShaper)]
1448
1449             args += ["--cubesize", str(lutResolution3d)]
1450
1451             margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (
1452                 bakedDir, odtName, inputspace)]
1453             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1454                               args=(args + margs))
1455             bakeLUT.execute()
1456
1457             hargs = ["--format", "houdini",
1458                      "%s/houdini/%s for %s Houdini.lut" % (
1459                          bakedDir, odtName, inputspace)]
1460             bakeLUT = Process(description="bake a LUT", cmd="ociobakelut",
1461                               args=(args + hargs))
1462             bakeLUT.execute()
1463
1464
1465 def createConfigDir(configDir, bakeSecondaryLUTs):
1466     """
1467     Object description.
1468
1469     Parameters
1470     ----------
1471     parameter : type
1472         Parameter description.
1473
1474     Returns
1475     -------
1476     type
1477          Return value description.
1478     """
1479
1480     dirs = [configDir, "%s/luts" % configDir]
1481     if bakeSecondaryLUTs:
1482         dirs.extend(["%s/baked" % configDir,
1483                      "%s/baked/flame" % configDir,
1484                      "%s/baked/photoshop" % configDir,
1485                      "%s/baked/houdini" % configDir,
1486                      "%s/baked/lustre" % configDir,
1487                      "%s/baked/maya" % configDir])
1488
1489     for d in dirs:
1490         if not os.path.exists(d):
1491             os.mkdir(d)
1492
1493
1494 def getTransformInfo(ctlTransform):
1495     """
1496     Object description.
1497
1498     Parameters
1499     ----------
1500     parameter : type
1501         Parameter description.
1502
1503     Returns
1504     -------
1505     type
1506          Return value description.
1507     """
1508
1509     fp = open(ctlTransform, 'rb')
1510
1511     # Read lines
1512     lines = fp.readlines()
1513
1514     # Grab transform ID and User Name
1515     transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
1516     # print(transformID)
1517     transformUserName = '-'.join(
1518         lines[2][3:].split('<')[1].split('>')[1].split('-')[
1519         1:]).lstrip().rstrip()
1520     transformUserNamePrefix = \
1521         lines[2][3:].split('<')[1].split('>')[1].split('-')[
1522             0].lstrip().rstrip()
1523     # print(transformUserName)
1524     fp.close()
1525
1526     return transformID, transformUserName, transformUserNamePrefix
1527
1528
1529 def getODTInfo(acesCTLReleaseDir):
1530     """
1531     Object description.
1532
1533     For versions after WGR9.
1534
1535     Parameters
1536     ----------
1537     parameter : type
1538         Parameter description.
1539
1540     Returns
1541     -------
1542     type
1543          Return value description.
1544     """
1545
1546     # Credit to Alex Fry for the original approach here
1547     odtDir = os.path.join(acesCTLReleaseDir, "odt")
1548     allodt = []
1549     for dirName, subdirList, fileList in os.walk(odtDir):
1550         for fname in fileList:
1551             allodt.append((os.path.join(dirName, fname)))
1552
1553     odtCTLs = [x for x in allodt if
1554                ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
1555
1556     # print odtCTLs
1557
1558     odts = {}
1559
1560     for odtCTL in odtCTLs:
1561         odtTokens = os.path.split(odtCTL)
1562         # print(odtTokens)
1563
1564         # Handle nested directories
1565         odtPathTokens = os.path.split(odtTokens[-2])
1566         odtDir = odtPathTokens[-1]
1567         while odtPathTokens[-2][-3:] != 'odt':
1568             odtPathTokens = os.path.split(odtPathTokens[-2])
1569             odtDir = os.path.join(odtPathTokens[-1], odtDir)
1570
1571         # Build full name
1572         # print("odtDir : %s" % odtDir)
1573         transformCTL = odtTokens[-1]
1574         # print(transformCTL)
1575         odtName = string.join(transformCTL.split('.')[1:-1], '.')
1576         # print(odtName)
1577
1578         # Find id, user name and user name prefix
1579         (transformID, transformUserName,
1580          transformUserNamePrefix) = getTransformInfo(
1581             "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL))
1582
1583         # Find inverse
1584         transformCTLInverse = "InvODT.%s.ctl" % odtName
1585         if not os.path.exists(
1586                 os.path.join(odtTokens[-2], transformCTLInverse)):
1587             transformCTLInverse = None
1588         # print(transformCTLInverse)
1589
1590         # Add to list of ODTs
1591         odts[odtName] = {}
1592         odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
1593         if transformCTLInverse != None:
1594             odts[odtName]['transformCTLInverse'] = os.path.join(
1595                 odtDir, transformCTLInverse)
1596
1597         odts[odtName]['transformID'] = transformID
1598         odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
1599         odts[odtName]['transformUserName'] = transformUserName
1600
1601         print("ODT : %s" % odtName)
1602         print("\tTransform ID               : %s" % transformID)
1603         print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1604         print("\tTransform User Name        : %s" % transformUserName)
1605         print("\tForward ctl                : %s" % (
1606             odts[odtName]['transformCTL']))
1607         if 'transformCTLInverse' in odts[odtName]:
1608             print("\tInverse ctl                : %s" % (
1609                 odts[odtName]['transformCTLInverse']))
1610         else:
1611             print("\tInverse ctl                : %s" % "None")
1612
1613     print("\n")
1614
1615     return odts
1616
1617
1618 def getLMTInfo(acesCTLReleaseDir):
1619     """
1620     Object description.
1621
1622     For versions after WGR9.
1623
1624     Parameters
1625     ----------
1626     parameter : type
1627         Parameter description.
1628
1629     Returns
1630     -------
1631     type
1632          Return value description.
1633     """
1634
1635     # Credit to Alex Fry for the original approach here
1636     lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
1637     alllmt = []
1638     for dirName, subdirList, fileList in os.walk(lmtDir):
1639         for fname in fileList:
1640             alllmt.append((os.path.join(dirName, fname)))
1641
1642     lmtCTLs = [x for x in alllmt if
1643                ("InvLMT" not in x) and ("README" not in x) and (
1644                    os.path.split(x)[-1][0] != '.')]
1645
1646     # print lmtCTLs
1647
1648     lmts = {}
1649
1650     for lmtCTL in lmtCTLs:
1651         lmtTokens = os.path.split(lmtCTL)
1652         # print(lmtTokens)
1653
1654         # Handle nested directories
1655         lmtPathTokens = os.path.split(lmtTokens[-2])
1656         lmtDir = lmtPathTokens[-1]
1657         while lmtPathTokens[-2][-3:] != 'ctl':
1658             lmtPathTokens = os.path.split(lmtPathTokens[-2])
1659             lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
1660
1661         # Build full name
1662         # print("lmtDir : %s" % lmtDir)
1663         transformCTL = lmtTokens[-1]
1664         # print(transformCTL)
1665         lmtName = string.join(transformCTL.split('.')[1:-1], '.')
1666         # print(lmtName)
1667
1668         # Find id, user name and user name prefix
1669         (transformID, transformUserName,
1670          transformUserNamePrefix) = getTransformInfo(
1671             "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL))
1672
1673         # Find inverse
1674         transformCTLInverse = "InvLMT.%s.ctl" % lmtName
1675         if not os.path.exists(
1676                 os.path.join(lmtTokens[-2], transformCTLInverse)):
1677             transformCTLInverse = None
1678         # print(transformCTLInverse)
1679
1680         # Add to list of LMTs
1681         lmts[lmtName] = {}
1682         lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
1683         if transformCTLInverse != None:
1684             # TODO: Check unresolved *odtName* referemce.
1685             lmts[odtName]['transformCTLInverse'] = os.path.join(
1686                 lmtDir, transformCTLInverse)
1687
1688         lmts[lmtName]['transformID'] = transformID
1689         lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
1690         lmts[lmtName]['transformUserName'] = transformUserName
1691
1692         print("LMT : %s" % lmtName)
1693         print("\tTransform ID               : %s" % transformID)
1694         print("\tTransform User Name Prefix : %s" % transformUserNamePrefix)
1695         print("\tTransform User Name        : %s" % transformUserName)
1696         print("\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
1697         if 'transformCTLInverse' in lmts[lmtName]:
1698             print("\t Inverse ctl : %s" % (
1699                 lmts[lmtName]['transformCTLInverse']))
1700         else:
1701             print("\t Inverse ctl : %s" % "None")
1702
1703     print("\n")
1704
1705     return lmts
1706
1707
1708 def createACESConfig(acesCTLReleaseDir,
1709                      configDir,
1710                      lutResolution1d=4096,
1711                      lutResolution3d=64,
1712                      bakeSecondaryLUTs=True,
1713                      cleanup=True):
1714     """
1715     Creates the ACES configuration.
1716
1717     Parameters
1718     ----------
1719     parameter : type
1720         Parameter description.
1721
1722     Returns
1723     -------
1724     type
1725          Return value description.
1726     """
1727
1728     # Get ODT names and CTL paths
1729     odtInfo = getODTInfo(acesCTLReleaseDir)
1730
1731     # Get ODT names and CTL paths
1732     lmtInfo = getLMTInfo(acesCTLReleaseDir)
1733
1734     # Create config dir
1735     createConfigDir(configDir, bakeSecondaryLUTs)
1736
1737     # Generate config data and LUTs for different transforms
1738     lutDir = "%s/luts" % configDir
1739     shaperName = 'Output Shaper'
1740     configData = generateLUTs(odtInfo,
1741                               lmtInfo,
1742                               shaperName,
1743                               acesCTLReleaseDir,
1744                               lutDir,
1745                               lutResolution1d,
1746                               lutResolution3d,
1747                               cleanup)
1748
1749     # Create the config using the generated LUTs
1750     print("Creating generic config")
1751     config = createConfig(configData)
1752     print("\n\n\n")
1753
1754     # Write the config to disk
1755     writeConfig(config, "%s/config.ocio" % configDir)
1756
1757     # Create a config that will work well with Nuke using the previously
1758     # generated LUTs.
1759     print("Creating Nuke-specific config")
1760     nuke_config = createConfig(configData, nuke=True)
1761     print("\n\n\n")
1762
1763     # Write the config to disk
1764     writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir)
1765
1766     # Bake secondary LUTs using the config
1767     if bakeSecondaryLUTs:
1768         generateBakedLUTs(odtInfo,
1769                           shaperName,
1770                           "%s/baked" % configDir,
1771                           "%s/config.ocio" % configDir,
1772                           lutResolution1d,
1773                           lutResolution3d,
1774                           lutResolution1d)
1775
1776     return True
1777
1778
1779 def main():
1780     """
1781     Object description.
1782
1783     Parameters
1784     ----------
1785     parameter : type
1786         Parameter description.
1787
1788     Returns
1789     -------
1790     type
1791          Return value description.
1792     """
1793
1794     import optparse
1795
1796     p = optparse.OptionParser(description='An OCIO config generation script',
1797                               prog='createACESConfig',
1798                               version='createACESConfig 0.1',
1799                               usage='%prog [options]')
1800     p.add_option('--acesCTLDir', '-a', default=os.environ.get(
1801         'ACES_OCIO_CTL_DIRECTORY', None))
1802     p.add_option('--configDir', '-c', default=os.environ.get(
1803         'ACES_OCIO_CONFIGURATION_DIRECTORY', None))
1804     p.add_option('--lutResolution1d', default=4096)
1805     p.add_option('--lutResolution3d', default=64)
1806     p.add_option('--dontBakeSecondaryLUTs', action="store_true")
1807     p.add_option('--keepTempImages', action="store_true")
1808
1809     options, arguments = p.parse_args()
1810
1811     #
1812     # Get options
1813     #
1814     acesCTLDir = options.acesCTLDir
1815     configDir = options.configDir
1816     lutResolution1d = int(options.lutResolution1d)
1817     lutResolution3d = int(options.lutResolution3d)
1818     bakeSecondaryLUTs = not (options.dontBakeSecondaryLUTs)
1819     cleanupTempImages = not (options.keepTempImages)
1820
1821     try:
1822         argsStart = sys.argv.index('--') + 1
1823         args = sys.argv[argsStart:]
1824     except:
1825         argsStart = len(sys.argv) + 1
1826         args = []
1827
1828     print("command line : \n%s\n" % " ".join(sys.argv))
1829
1830     # TODO: Use assertion and mention environment variables.
1831     if not acesCTLDir:
1832         print("process: No ACES CTL directory specified")
1833         return
1834     if not configDir:
1835         print("process: No configuration directory specified")
1836         return
1837     #
1838     # Generate the configuration
1839     #
1840     return createACESConfig(acesCTLDir,
1841                             configDir,
1842                             lutResolution1d,
1843                             lutResolution3d,
1844                             bakeSecondaryLUTs,
1845                             cleanupTempImages)
1846
1847
1848 if __name__ == '__main__':
1849     main()