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