Update sanitisation code.
[OpenColorIO-Configs.git] / aces_1.0.0 / python / aces_ocio / create_aces_colorspaces.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Implements support for *ACES* colorspaces conversions and transfer functions.
6 """
7
8 import math
9 import numpy
10 import os
11 import pprint
12 import string
13 import shutil
14
15 import PyOpenColorIO as ocio
16
17 from aces_ocio.generate_lut import (
18     generate_1d_LUT_from_CTL,
19     generate_3d_LUT_from_CTL,
20     write_SPI_1d)
21 from aces_ocio.utilities import (
22     ColorSpace,
23     mat44_from_mat33,
24     sanitize,
25     compact)
26
27
28 __author__ = 'ACES Developers'
29 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
30 __license__ = ''
31 __maintainer__ = 'ACES Developers'
32 __email__ = 'aces@oscars.org'
33 __status__ = 'Production'
34
35 __all__ = ['ACES_AP1_TO_AP0',
36            'ACES_AP0_TO_XYZ',
37            'create_ACEScc',
38            'create_ACESproxy',
39            'create_ACEScg',
40            'create_ADX',
41            'create_generic_log',
42            'create_ACES_LMT',
43            'create_lmts',
44            'create_ACES_RRT_plus_ODT',
45            'create_odts',
46            'create_aces',
47            'get_transform_info',
48            'get_ODT_info',
49            'get_LMT_info',
50            'create_colorspaces']
51
52 # -------------------------------------------------------------------------
53 # *Matrices*
54 # -------------------------------------------------------------------------
55
56 # Matrix converting *ACES AP1* primaries to *AP0*.
57 ACES_AP1_TO_AP0 = [0.6954522414, 0.1406786965, 0.1638690622,
58                    0.0447945634, 0.8596711185, 0.0955343182,
59                    -0.0055258826, 0.0040252103, 1.0015006723]
60
61 # Matrix converting *ACES AP0* primaries to *XYZ*.
62 ACES_AP0_TO_XYZ = [0.9525523959, 0.0000000000, 0.0000936786,
63                    0.3439664498, 0.7281660966, -0.0721325464,
64                    0.0000000000, 0.0000000000, 1.0088251844]
65
66 # -------------------------------------------------------------------------
67 # *ACEScc*
68 # -------------------------------------------------------------------------
69 def create_ACEScc(aces_CTL_directory,
70                   lut_directory,
71                   lut_resolution_1d,
72                   cleanup,
73                   name='ACEScc',
74                   min_value=0.0,
75                   max_value=1.0,
76                   input_scale=1.0):
77     """
78     Object description.
79
80     Parameters
81     ----------
82     parameter : type
83         Parameter description.
84
85     Returns
86     -------
87     type
88          Return value description.
89     """
90
91     cs = ColorSpace(name)
92     cs.description = 'The %s color space' % name
93     cs.aliases = ["acescc_ap1"]
94     cs.equality_group = ''
95     cs.family = 'ACES'
96     cs.is_data = False
97
98     ctls = [os.path.join(aces_CTL_directory,
99                          'ACEScc',
100                          'ACEScsc.ACEScc_to_ACES.a1.0.0.ctl'),
101             # This transform gets back to the *AP1* primaries.
102             # Useful as the 1d LUT is only covering the transfer function.
103             # The primaries switch is covered by the matrix below:
104             os.path.join(aces_CTL_directory,
105                          'ACEScg',
106                          'ACEScsc.ACES_to_ACEScg.a1.0.0.ctl')]
107     lut = '%s_to_ACES.spi1d' % name
108
109     lut = sanitize(lut)
110
111     generate_1d_LUT_from_CTL(
112         os.path.join(lut_directory, lut),
113         ctls,
114         lut_resolution_1d,
115         'float',
116         input_scale,
117         1.0,
118         {},
119         cleanup,
120         aces_CTL_directory,
121         min_value,
122         max_value)
123
124     cs.to_reference_transforms = []
125     cs.to_reference_transforms.append({
126         'type': 'lutFile',
127         'path': lut,
128         'interpolation': 'linear',
129         'direction': 'forward'})
130
131     # *AP1* primaries to *AP0* primaries.
132     cs.to_reference_transforms.append({
133         'type': 'matrix',
134         'matrix': mat44_from_mat33(ACES_AP1_TO_AP0),
135         'direction': 'forward'})
136
137     cs.from_reference_transforms = []
138     return cs
139
140
141 # -------------------------------------------------------------------------
142 # *ACESproxy*
143 # -------------------------------------------------------------------------
144 def create_ACESproxy(aces_CTL_directory,
145                      lut_directory,
146                      lut_resolution_1d,
147                      cleanup,
148                      name='ACESproxy'):
149     """
150     Object description.
151
152     Parameters
153     ----------
154     parameter : type
155         Parameter description.
156
157     Returns
158     -------
159     type
160          Return value description.
161     """
162
163     cs = ColorSpace(name)
164     cs.description = 'The %s color space' % name
165     cs.aliases = ["acesproxy_ap1"]
166     cs.equality_group = ''
167     cs.family = 'ACES'
168     cs.is_data = False
169
170     ctls = [os.path.join(aces_CTL_directory,
171                          'ACESproxy',
172                          'ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl'),
173             # This transform gets back to the *AP1* primaries.
174             # Useful as the 1d LUT is only covering the transfer function.
175             # The primaries switch is covered by the matrix below:
176             os.path.join(aces_CTL_directory,
177                          'ACEScg',
178                          'ACEScsc.ACES_to_ACEScg.a1.0.0.ctl')]
179     lut = '%s_to_aces.spi1d' % name
180
181     lut = sanitize(lut)
182
183     generate_1d_LUT_from_CTL(
184         os.path.join(lut_directory, lut),
185         ctls,
186         lut_resolution_1d,
187         'uint16',
188         64.0,
189         1.0,
190         {},
191         cleanup,
192         aces_CTL_directory)
193
194     cs.to_reference_transforms = []
195     cs.to_reference_transforms.append({
196         'type': 'lutFile',
197         'path': lut,
198         'interpolation': 'linear',
199         'direction': 'forward'
200     })
201
202     # *AP1* primaries to *AP0* primaries.
203     cs.to_reference_transforms.append({
204         'type': 'matrix',
205         'matrix': mat44_from_mat33(ACES_AP1_TO_AP0),
206         'direction': 'forward'
207     })
208
209     cs.from_reference_transforms = []
210     return cs
211
212
213 # -------------------------------------------------------------------------
214 # *ACEScg*
215 # -------------------------------------------------------------------------
216 def create_ACEScg(aces_CTL_directory,
217                   lut_directory,
218                   lut_resolution_1d,
219                   cleanup,
220                   name='ACEScg'):
221     """
222     Object description.
223
224     Parameters
225     ----------
226     parameter : type
227         Parameter description.
228
229     Returns
230     -------
231     type
232          Return value description.
233     """
234
235     cs = ColorSpace(name)
236     cs.description = 'The %s color space' % name
237     cs.aliases = ["lin_ap1"]
238     cs.equality_group = ''
239     cs.family = 'ACES'
240     cs.is_data = False
241
242     cs.to_reference_transforms = []
243
244     # *AP1* primaries to *AP0* primaries.
245     cs.to_reference_transforms.append({
246         'type': 'matrix',
247         'matrix': mat44_from_mat33(ACES_AP1_TO_AP0),
248         'direction': 'forward'})
249
250     cs.from_reference_transforms = []
251     return cs
252
253
254 # -------------------------------------------------------------------------
255 # *ADX*
256 # -------------------------------------------------------------------------
257 def create_ADX(lut_directory,
258                lut_resolution_1d,
259                bit_depth=10,
260                name='ADX'):
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     name = '%s%s' % (name, bit_depth)
276     cs = ColorSpace(name)
277     cs.description = '%s color space - used for film scans' % name
278     cs.aliases = ["adx%s" % str(bit_depth)]
279     cs.equality_group = ''
280     cs.family = 'ADX'
281     cs.is_data = False
282
283     if bit_depth == 10:
284         cs.bit_depth = ocio.Constants.BIT_DEPTH_UINT10
285         adx_to_cdd = [1023.0 / 500.0, 0.0, 0.0, 0.0,
286                       0.0, 1023.0 / 500.0, 0.0, 0.0,
287                       0.0, 0.0, 1023.0 / 500.0, 0.0,
288                       0.0, 0.0, 0.0, 1.0]
289         offset = [-95.0 / 500.0, -95.0 / 500.0, -95.0 / 500.0, 0.0]
290     elif bit_depth == 16:
291         cs.bit_depth = ocio.Constants.BIT_DEPTH_UINT16
292         adx_to_cdd = [65535.0 / 8000.0, 0.0, 0.0, 0.0,
293                       0.0, 65535.0 / 8000.0, 0.0, 0.0,
294                       0.0, 0.0, 65535.0 / 8000.0, 0.0,
295                       0.0, 0.0, 0.0, 1.0]
296         offset = [-1520.0 / 8000.0, -1520.0 / 8000.0, -1520.0 / 8000.0,
297                   0.0]
298
299     cs.to_reference_transforms = []
300
301     # Converting from *ADX* to *Channel-Dependent Density*.
302     cs.to_reference_transforms.append({
303         'type': 'matrix',
304         'matrix': adx_to_cdd,
305         'offset': offset,
306         'direction': 'forward'})
307
308     # Convert from Channel-Dependent Density to Channel-Independent Density
309     cs.to_reference_transforms.append({
310         'type': 'matrix',
311         'matrix': [0.75573, 0.22197, 0.02230, 0,
312                    0.05901, 0.96928, -0.02829, 0,
313                    0.16134, 0.07406, 0.76460, 0,
314                    0.0, 0.0, 0.0, 1.0],
315         'direction': 'forward'})
316
317     # Copied from *Alex Fry*'s *adx_cid_to_rle.py*
318     def create_CID_to_RLE_LUT():
319
320         def interpolate_1D(x, xp, fp):
321             return numpy.interp(x, xp, fp)
322
323         LUT_1D_xp = [-0.190000000000000,
324                      0.010000000000000,
325                      0.028000000000000,
326                      0.054000000000000,
327                      0.095000000000000,
328                      0.145000000000000,
329                      0.220000000000000,
330                      0.300000000000000,
331                      0.400000000000000,
332                      0.500000000000000,
333                      0.600000000000000]
334
335         LUT_1D_fp = [-6.000000000000000,
336                      -2.721718645000000,
337                      -2.521718645000000,
338                      -2.321718645000000,
339                      -2.121718645000000,
340                      -1.921718645000000,
341                      -1.721718645000000,
342                      -1.521718645000000,
343                      -1.321718645000000,
344                      -1.121718645000000,
345                      -0.926545676714876]
346
347         REF_PT = ((7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) -
348                   math.log(0.18, 10.0))
349
350         def cid_to_rle(x):
351             if x <= 0.6:
352                 return interpolate_1D(x, LUT_1D_xp, LUT_1D_fp)
353             return (100.0 / 55.0) * x - REF_PT
354
355         def fit(value, from_min, from_max, to_min, to_max):
356             if from_min == from_max:
357                 raise ValueError('from_min == from_max')
358             return (value - from_min) / (from_max - from_min) * (
359                 to_max - to_min) + to_min
360
361         NUM_SAMPLES = 2 ** 12
362         RANGE = (-0.19, 3.0)
363         data = []
364         for i in xrange(NUM_SAMPLES):
365             x = i / (NUM_SAMPLES - 1.0)
366             x = fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
367             data.append(cid_to_rle(x))
368
369         lut = 'ADX_CID_to_RLE.spi1d'
370         write_SPI_1d(os.path.join(lut_directory, lut),
371                      RANGE[0],
372                      RANGE[1],
373                      data,
374                      NUM_SAMPLES, 1)
375
376         return lut
377
378     # Converting *Channel Independent Density* values to
379     # *Relative Log Exposure* values.
380     lut = create_CID_to_RLE_LUT()
381     cs.to_reference_transforms.append({
382         'type': 'lutFile',
383         'path': lut,
384         'interpolation': 'linear',
385         'direction': 'forward'})
386
387     # Converting *Relative Log Exposure* values to
388     # *Relative Exposure* values.
389     cs.to_reference_transforms.append({
390         'type': 'log',
391         'base': 10,
392         'direction': 'inverse'})
393
394     # Convert *Relative Exposure* values to *ACES* values.
395     cs.to_reference_transforms.append({
396         'type': 'matrix',
397         'matrix': [0.72286, 0.12630, 0.15084, 0,
398                    0.11923, 0.76418, 0.11659, 0,
399                    0.01427, 0.08213, 0.90359, 0,
400                    0.0, 0.0, 0.0, 1.0],
401         'direction': 'forward'})
402
403     cs.from_reference_transforms = []
404     return cs
405
406
407 # -------------------------------------------------------------------------
408 # *Generic Log Transform*
409 # -------------------------------------------------------------------------
410 def create_generic_log(aces_CTL_directory,
411                        lut_directory,
412                        lut_resolution_1d,
413                        cleanup,
414                        name='log',
415                        aliases=[],
416                        min_value=0.0,
417                        max_value=1.0,
418                        input_scale=1.0,
419                        middle_grey=0.18,
420                        min_exposure=-6.0,
421                        max_exposure=6.5):
422     """
423     Object description.
424
425     Parameters
426     ----------
427     parameter : type
428         Parameter description.
429
430     Returns
431     -------
432     type
433          Return value description.
434     """
435
436     cs = ColorSpace(name)
437     cs.description = 'The %s color space' % name
438     cs.aliases = aliases
439     cs.equality_group = name
440     cs.family = 'Utility'
441     cs.is_data = False
442
443     ctls = [os.path.join(
444         aces_CTL_directory,
445         'utilities',
446         'ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl')]
447     lut = '%s_to_aces.spi1d' % name
448
449     lut = sanitize(lut)
450
451     generate_1d_LUT_from_CTL(
452         os.path.join(lut_directory, lut),
453         ctls,
454         lut_resolution_1d,
455         'float',
456         input_scale,
457         1.0,
458         {'middleGrey': middle_grey,
459          'minExposure': min_exposure,
460          'maxExposure': max_exposure},
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     cs.from_reference_transforms = []
474     return cs
475
476
477 # -------------------------------------------------------------------------
478 # *Individual LMTs*
479 # -------------------------------------------------------------------------
480 def create_ACES_LMT(lmt_name,
481                     lmt_values,
482                     shaper_info,
483                     aces_CTL_directory,
484                     lut_directory,
485                     lut_resolution_1d=1024,
486                     lut_resolution_3d=64,
487                     cleanup=True,
488                     aliases=[]):
489     """
490     Object description.
491
492     Parameters
493     ----------
494     parameter : type
495         Parameter description.
496
497     Returns
498     -------
499     type
500          Return value description.
501     """
502
503     cs = ColorSpace('%s' % lmt_name)
504     cs.description = 'The ACES Look Transform: %s' % lmt_name
505     cs.aliases = aliases
506     cs.equality_group = ''
507     cs.family = 'Look'
508     cs.is_data = False
509
510     pprint.pprint(lmt_values)
511
512     # Generating the *shaper* transform.
513     (shaper_name,
514      shaper_to_ACES_CTL,
515      shaper_from_ACES_CTL,
516      shaper_input_scale,
517      shaper_params) = shaper_info
518
519     shaper_lut = '%s_to_aces.spi1d' % shaper_name
520     if not os.path.exists(os.path.join(lut_directory, shaper_lut)):
521         ctls = [shaper_to_ACES_CTL % aces_CTL_directory]
522
523         shaper_lut = sanitize(shaper_lut)
524
525         generate_1d_LUT_from_CTL(
526             os.path.join(lut_directory, shaper_lut),
527             ctls,
528             lut_resolution_1d,
529             'float',
530             1.0 / shaper_input_scale,
531             1.0,
532             shaper_params,
533             cleanup,
534             aces_CTL_directory)
535
536     shaper_OCIO_transform = {
537         'type': 'lutFile',
538         'path': shaper_lut,
539         'interpolation': 'linear',
540         'direction': 'inverse'}
541
542     # Generating the forward transform.
543     cs.from_reference_transforms = []
544
545     if 'transformCTL' in lmt_values:
546         ctls = [shaper_to_ACES_CTL % aces_CTL_directory,
547                 os.path.join(aces_CTL_directory,
548                              lmt_values['transformCTL'])]
549         lut = '%s.%s.spi3d' % (shaper_name, lmt_name)
550
551         lut = sanitize(lut)
552
553         generate_3d_LUT_from_CTL(
554             os.path.join(lut_directory, lut),
555             ctls,
556             lut_resolution_3d,
557             'float',
558             1.0 / shaper_input_scale,
559             1.0,
560             shaper_params,
561             cleanup,
562             aces_CTL_directory)
563
564         cs.from_reference_transforms.append(shaper_OCIO_transform)
565         cs.from_reference_transforms.append({
566             'type': 'lutFile',
567             'path': lut,
568             'interpolation': 'tetrahedral',
569             'direction': 'forward'
570         })
571
572     # Generating the inverse transform.
573     cs.to_reference_transforms = []
574
575     if 'transformCTLInverse' in lmt_values:
576         ctls = [os.path.join(aces_CTL_directory,
577                              # TODO: Investigate "odt_values" undeclared
578                              # variable.
579                              odt_values['transformCTLInverse']),
580                 shaper_from_ACES_CTL % aces_CTL_directory]
581         lut = 'Inverse.%s.%s.spi3d' % (odt_name, shaper_name)
582
583         lut = sanitize(lut)
584
585         generate_3d_LUT_from_CTL(
586             os.path.join(lut_directory, lut),
587             ctls,
588             lut_resolution_3d,
589             'half',
590             1.0,
591             shaper_input_scale,
592             shaper_params,
593             cleanup,
594             aces_CTL_directory)
595
596         cs.to_reference_transforms.append({
597             'type': 'lutFile',
598             'path': lut,
599             'interpolation': 'tetrahedral',
600             'direction': 'forward'})
601
602         shaper_inverse = shaper_OCIO_transform.copy()
603         shaper_inverse['direction'] = 'forward'
604         cs.to_reference_transforms.append(shaper_inverse)
605
606     return cs
607
608
609 # -------------------------------------------------------------------------
610 # *LMTs*
611 # -------------------------------------------------------------------------
612 def create_lmts(aces_CTL_directory,
613                 lut_directory,
614                 lut_resolution_1d,
615                 lut_resolution_3d,
616                 lmt_info,
617                 shaper_name,
618                 cleanup):
619     """
620     Object description.
621
622     Parameters
623     ----------
624     parameter : type
625         Parameter description.
626
627     Returns
628     -------
629     type
630          Return value description.
631     """
632
633     colorspaces = []
634
635     # -------------------------------------------------------------------------
636     # *LMT Shaper*
637     # -------------------------------------------------------------------------
638     lmt_lut_resolution_1d = max(4096, lut_resolution_1d)
639     lmt_lut_resolution_3d = max(65, lut_resolution_3d)
640
641     # Defining the *Log 2* shaper.
642     lmt_shaper_name = 'LMT Shaper'
643     lmt_shaper_name_aliases = ['crv_lmtshaper']
644     lmt_params = {
645         'middleGrey': 0.18,
646         'minExposure': -10.0,
647         'maxExposure': 6.5}
648
649     lmt_shaper = create_generic_log(aces_CTL_directory,
650                                     lut_directory,
651                                     lmt_lut_resolution_1d,
652                                     cleanup,
653                                     name=lmt_shaper_name,
654                                     middle_grey=lmt_params['middleGrey'],
655                                     min_exposure=lmt_params['minExposure'],
656                                     max_exposure=lmt_params['maxExposure'],
657                                     aliases=lmt_shaper_name_aliases)
658     colorspaces.append(lmt_shaper)
659
660     shaper_input_scale_generic_log2 = 1.0
661
662     # *Log 2* shaper name and *CTL* transforms bundled up.
663     lmt_shaper_data = [
664         lmt_shaper_name,
665         os.path.join('%s',
666                      'utilities',
667                      'ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl'),
668         os.path.join('%s',
669                      'utilities',
670                      'ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl'),
671         shaper_input_scale_generic_log2,
672         lmt_params]
673
674     sorted_LMTs = sorted(lmt_info.iteritems(), key=lambda x: x[1])
675     print(sorted_LMTs)
676     for lmt in sorted_LMTs:
677         (lmt_name, lmt_values) = lmt
678         lmt_aliases = ["look_%s" % compact(lmt_values['transformUserName'])]
679         cs = create_ACES_LMT(
680             lmt_values['transformUserName'],
681             lmt_values,
682             lmt_shaper_data,
683             aces_CTL_directory,
684             lut_directory,
685             lmt_lut_resolution_1d,
686             lmt_lut_resolution_3d,
687             cleanup,
688             lmt_aliases)
689         colorspaces.append(cs)
690
691     return colorspaces
692
693
694 # -------------------------------------------------------------------------
695 # *ACES RRT* with supplied *ODT*.
696 # -------------------------------------------------------------------------
697 def create_ACES_RRT_plus_ODT(odt_name,
698                              odt_values,
699                              shaper_info,
700                              aces_CTL_directory,
701                              lut_directory,
702                              lut_resolution_1d=1024,
703                              lut_resolution_3d=64,
704                              cleanup=True,
705                              aliases=[]):
706     """
707     Object description.
708
709     Parameters
710     ----------
711     parameter : type
712         Parameter description.
713
714     Returns
715     -------
716     type
717          Return value description.
718     """
719
720     cs = ColorSpace('%s' % odt_name)
721     cs.description = '%s - %s Output Transform' % (
722         odt_values['transformUserNamePrefix'], odt_name)
723     cs.aliases = aliases
724     cs.equality_group = ''
725     cs.family = 'Output'
726     cs.is_data = False
727
728     pprint.pprint(odt_values)
729
730     # Generating the *shaper* transform.
731     (shaper_name,
732      shaper_to_ACES_CTL,
733      shaper_from_ACES_CTL,
734      shaper_input_scale,
735      shaper_params) = shaper_info
736
737     if 'legalRange' in odt_values:
738         shaper_params['legalRange'] = odt_values['legalRange']
739     else:
740         shaper_params['legalRange'] = 0
741
742     shaper_lut = '%s_to_aces.spi1d' % shaper_name
743     if not os.path.exists(os.path.join(lut_directory, shaper_lut)):
744         ctls = [shaper_to_ACES_CTL % aces_CTL_directory]
745
746         shaper_lut = sanitize(shaper_lut)
747
748         generate_1d_LUT_from_CTL(
749             os.path.join(lut_directory, shaper_lut),
750             ctls,
751             lut_resolution_1d,
752             'float',
753             1.0 / shaper_input_scale,
754             1.0,
755             shaper_params,
756             cleanup,
757             aces_CTL_directory)
758
759     shaper_OCIO_transform = {
760         'type': 'lutFile',
761         'path': shaper_lut,
762         'interpolation': 'linear',
763         'direction': 'inverse'}
764
765     # Generating the *forward* transform.
766     cs.from_reference_transforms = []
767
768     if 'transformLUT' in odt_values:
769         transform_LUT_file_name = os.path.basename(
770             odt_values['transformLUT'])
771         lut = os.path.join(lut_directory, transform_LUT_file_name)
772         shutil.copy(odt_values['transformLUT'], lut)
773
774         cs.from_reference_transforms.append(shaper_OCIO_transform)
775         cs.from_reference_transforms.append({
776             'type': 'lutFile',
777             'path': transform_LUT_file_name,
778             'interpolation': 'tetrahedral',
779             'direction': 'forward'})
780     elif 'transformCTL' in odt_values:
781         ctls = [
782             shaper_to_ACES_CTL % aces_CTL_directory,
783             os.path.join(aces_CTL_directory,
784                          'rrt',
785                          'RRT.a1.0.0.ctl'),
786             os.path.join(aces_CTL_directory,
787                          'odt',
788                          odt_values['transformCTL'])]
789         lut = '%s.RRT.a1.0.0.%s.spi3d' % (shaper_name, odt_name)
790
791         lut = sanitize(lut)
792
793         generate_3d_LUT_from_CTL(
794             os.path.join(lut_directory, lut),
795             # shaperLUT,
796             ctls,
797             lut_resolution_3d,
798             'float',
799             1.0 / shaper_input_scale,
800             1.0,
801             shaper_params,
802             cleanup,
803             aces_CTL_directory)
804
805         cs.from_reference_transforms.append(shaper_OCIO_transform)
806         cs.from_reference_transforms.append({
807             'type': 'lutFile',
808             'path': lut,
809             'interpolation': 'tetrahedral',
810             'direction': 'forward'})
811
812     # Generating the *inverse* transform.
813     cs.to_reference_transforms = []
814
815     if 'transformLUTInverse' in odt_values:
816         transform_LUT_inverse_file_name = os.path.basename(
817             odt_values['transformLUTInverse'])
818         lut = os.path.join(lut_directory, transform_LUT_inverse_file_name)
819         shutil.copy(odt_values['transformLUTInverse'], lut)
820
821         cs.to_reference_transforms.append({
822             'type': 'lutFile',
823             'path': transform_LUT_inverse_file_name,
824             'interpolation': 'tetrahedral',
825             'direction': 'forward'})
826
827         shaper_inverse = shaper_OCIO_transform.copy()
828         shaper_inverse['direction'] = 'forward'
829         cs.to_reference_transforms.append(shaper_inverse)
830     elif 'transformCTLInverse' in odt_values:
831         ctls = [os.path.join(aces_CTL_directory,
832                              'odt',
833                              odt_values['transformCTLInverse']),
834                 os.path.join(aces_CTL_directory,
835                              'rrt',
836                              'InvRRT.a1.0.0.ctl'),
837                 shaper_from_ACES_CTL % aces_CTL_directory]
838         lut = 'InvRRT.a1.0.0.%s.%s.spi3d' % (odt_name, shaper_name)
839
840         lut = sanitize(lut)
841
842         generate_3d_LUT_from_CTL(
843             os.path.join(lut_directory, lut),
844             # None,
845             ctls,
846             lut_resolution_3d,
847             'half',
848             1.0,
849             shaper_input_scale,
850             shaper_params,
851             cleanup,
852             aces_CTL_directory)
853
854         cs.to_reference_transforms.append({
855             'type': 'lutFile',
856             'path': lut,
857             'interpolation': 'tetrahedral',
858             'direction': 'forward'})
859
860         shaper_inverse = shaper_OCIO_transform.copy()
861         shaper_inverse['direction'] = 'forward'
862         cs.to_reference_transforms.append(shaper_inverse)
863
864     return cs
865
866
867 # -------------------------------------------------------------------------
868 # *ODTs*
869 # -------------------------------------------------------------------------
870 def create_odts(aces_CTL_directory,
871                 lut_directory,
872                 lut_resolution_1d,
873                 lut_resolution_3d,
874                 odt_info,
875                 shaper_name,
876                 cleanup,
877                 linear_display_space,
878                 log_display_space):
879     """
880     Object description.
881
882     Parameters
883     ----------
884     parameter : type
885         Parameter description.
886
887     Returns
888     -------
889     type
890          Return value description.
891     """
892
893     colorspaces = []
894     displays = {}
895
896     # -------------------------------------------------------------------------
897     # *RRT / ODT* Shaper Options
898     # -------------------------------------------------------------------------
899     shaper_data = {}
900
901     # Defining the *Log 2* shaper.
902     log2_shaper_name = shaper_name
903     log2_shaper_name_aliases = ["crv_%s" % compact(shaper_name)]
904     log2_params = {
905         'middleGrey': 0.18,
906         'minExposure': -6.0,
907         'maxExposure': 6.5}
908
909     log2_shaper = create_generic_log(
910         aces_CTL_directory,
911         lut_directory,
912         lut_resolution_1d,
913         cleanup,
914         name=log2_shaper_name,
915         middle_grey=log2_params['middleGrey'],
916         min_exposure=log2_params['minExposure'],
917         max_exposure=log2_params['maxExposure'],
918         aliases=log2_shaper_name_aliases)
919     colorspaces.append(log2_shaper)
920
921     shaper_input_scale_generic_log2 = 1.0
922
923     # *Log 2* shaper name and *CTL* transforms bundled up.
924     log2_shaper_data = [
925         log2_shaper_name,
926         os.path.join('%s',
927                      'utilities',
928                      'ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl'),
929         os.path.join('%s',
930                      'utilities',
931                      'ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl'),
932         shaper_input_scale_generic_log2,
933         log2_params]
934
935     shaper_data[log2_shaper_name] = log2_shaper_data
936
937     # Shaper that also includes the AP1 primaries.
938     # Needed for some LUT baking steps.
939     log2_shaper_api1_name_aliases = ["%s_ap1" % compact(shaper_name)]
940     log2_shaper_AP1 = create_generic_log(
941         aces_CTL_directory,
942         lut_directory,
943         lut_resolution_1d,
944         cleanup,
945         name=log2_shaper_name,
946         middle_grey=log2_params['middleGrey'],
947         min_exposure=log2_params['minExposure'],
948         max_exposure=log2_params['maxExposure'],
949         aliases=log2_shaper_api1_name_aliases)
950     log2_shaper_AP1.name = '%s - AP1' % log2_shaper_AP1.name
951
952     # *AP1* primaries to *AP0* primaries.
953     log2_shaper_AP1.to_reference_transforms.append({
954         'type': 'matrix',
955         'matrix': mat44_from_mat33(ACES_AP1_TO_AP0),
956         'direction': 'forward'
957     })
958     colorspaces.append(log2_shaper_AP1)
959
960     rrt_shaper = log2_shaper_data
961
962     # *RRT + ODT* combinations.
963     sorted_odts = sorted(odt_info.iteritems(), key=lambda x: x[1])
964     print(sorted_odts)
965     for odt in sorted_odts:
966         (odt_name, odt_values) = odt
967
968         # Generating legal range transform for *ODTs* that can generate 
969         # either *legal* or *full* output.
970         if odt_values['transformHasFullLegalSwitch']:
971             odt_name_legal = '%s - Legal' % odt_values['transformUserName']
972         else:
973             odt_name_legal = odt_values['transformUserName']
974
975         odt_legal = odt_values.copy()
976         odt_legal['legalRange'] = 1
977
978         odt_aliases = ["out_%s" % compact(odt_name_legal)]
979
980         cs = create_ACES_RRT_plus_ODT(
981             odt_name_legal,
982             odt_legal,
983             rrt_shaper,
984             aces_CTL_directory,
985             lut_directory,
986             lut_resolution_1d,
987             lut_resolution_3d,
988             cleanup,
989             odt_aliases)
990         colorspaces.append(cs)
991
992         displays[odt_name_legal] = {
993             'Linear': linear_display_space,
994             'Log': log_display_space,
995             'Output Transform': cs}
996
997
998         # Generating full range transform for *ODTs* that can generate 
999         # either *legal* or *full* output.
1000         if odt_values['transformHasFullLegalSwitch']:
1001             print('Generating full range ODT for %s' % odt_name)
1002
1003             odt_name_full = '%s - Full' % odt_values['transformUserName']
1004             odt_full = odt_values.copy()
1005             odt_full['legalRange'] = 0
1006
1007             odt_full_aliases = ["out_%s" % compact(odt_name_full)]
1008
1009             cs_full = create_ACES_RRT_plus_ODT(
1010                 odt_name_full,
1011                 odt_full,
1012                 rrt_shaper,
1013                 aces_CTL_directory,
1014                 lut_directory,
1015                 lut_resolution_1d,
1016                 lut_resolution_3d,
1017                 cleanup,
1018                 odt_full_aliases)
1019             colorspaces.append(cs_full)
1020
1021             displays[odt_name_full] = {
1022                 'Linear': linear_display_space,
1023                 'Log': log_display_space,
1024                 'Output Transform': cs_full}
1025
1026     return (colorspaces, displays)
1027
1028
1029 def create_aces():
1030     """
1031     Object description.
1032
1033     Parameters
1034     ----------
1035     parameter : type
1036         Parameter description.
1037
1038     Returns
1039     -------
1040     type
1041          Return value description.
1042     """
1043
1044     # Defining the reference colorspace.
1045     ACES = ColorSpace('ACES2065-1')
1046     ACES.description = (
1047         'The Academy Color Encoding System reference color space')
1048     ACES.equality_group = ''
1049     ACES.aliases = ["lin_ap0", "aces"]
1050     ACES.family = 'ACES'
1051     ACES.is_data = False
1052     ACES.allocation_type = ocio.Constants.ALLOCATION_LG2
1053     ACES.allocation_vars = [-15, 6]
1054
1055     return ACES
1056
1057
1058 def get_transform_info(ctl_transform):
1059     """
1060     Object description.
1061
1062     Parameters
1063     ----------
1064     parameter : type
1065         Parameter description.
1066
1067     Returns
1068     -------
1069     type
1070          Return value description.
1071     """
1072
1073     with open(ctl_transform, 'rb') as fp:
1074         lines = fp.readlines()
1075
1076     # Retrieving the *transform ID* and *User Name*.
1077     transform_id = lines[1][3:].split('<')[1].split('>')[1].strip()
1078     transform_user_name = '-'.join(
1079         lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).strip()
1080     transform_user_name_prefix = (
1081         lines[2][3:].split('<')[1].split('>')[1].split('-')[0].strip())
1082
1083     # Figuring out if this transform has options for processing full and legal range
1084     transform_full_legal_switch = False
1085     for line in lines:
1086         if line.strip() == "input varying int legalRange = 0":
1087             # print( "%s has legal range flag" % transform_user_name)
1088             transform_full_legal_switch = True
1089             break
1090
1091     return (transform_id, transform_user_name, transform_user_name_prefix,
1092             transform_full_legal_switch)
1093
1094
1095 def get_ODT_info(aces_CTL_directory):
1096     """
1097     Object description.
1098
1099     For versions after WGR9.
1100
1101     Parameters
1102     ----------
1103     parameter : type
1104         Parameter description.
1105
1106     Returns
1107     -------
1108     type
1109          Return value description.
1110     """
1111
1112     # TODO: Investigate usage of *files_walker* definition here.
1113     # Credit to *Alex Fry* for the original approach here.
1114     odt_dir = os.path.join(aces_CTL_directory, 'odt')
1115     all_odt = []
1116     for dir_name, subdir_list, file_list in os.walk(odt_dir):
1117         for fname in file_list:
1118             all_odt.append((os.path.join(dir_name, fname)))
1119
1120     odt_CTLs = [x for x in all_odt if
1121                 ('InvODT' not in x) and (os.path.split(x)[-1][0] != '.')]
1122
1123     odts = {}
1124
1125     for odt_CTL in odt_CTLs:
1126         odt_tokens = os.path.split(odt_CTL)
1127
1128         # Handling nested directories.
1129         odt_path_tokens = os.path.split(odt_tokens[-2])
1130         odt_dir = odt_path_tokens[-1]
1131         while odt_path_tokens[-2][-3:] != 'odt':
1132             odt_path_tokens = os.path.split(odt_path_tokens[-2])
1133             odt_dir = os.path.join(odt_path_tokens[-1], odt_dir)
1134
1135         # Building full name,
1136         transform_CTL = odt_tokens[-1]
1137         odt_name = string.join(transform_CTL.split('.')[1:-1], '.')
1138
1139         # Finding id, user name and user name prefix.
1140         (transform_ID,
1141          transform_user_name,
1142          transform_user_name_prefix,
1143          transform_full_legal_switch) = get_transform_info(
1144             os.path.join(aces_CTL_directory, 'odt', odt_dir, transform_CTL))
1145
1146         # Finding inverse.
1147         transform_CTL_inverse = 'InvODT.%s.ctl' % odt_name
1148         if not os.path.exists(
1149                 os.path.join(odt_tokens[-2], transform_CTL_inverse)):
1150             transform_CTL_inverse = None
1151
1152         # Add to list of ODTs
1153         odts[odt_name] = {}
1154         odts[odt_name]['transformCTL'] = os.path.join(odt_dir, transform_CTL)
1155         if transform_CTL_inverse is not None:
1156             odts[odt_name]['transformCTLInverse'] = os.path.join(
1157                 odt_dir, transform_CTL_inverse)
1158
1159         odts[odt_name]['transformID'] = transform_ID
1160         odts[odt_name]['transformUserNamePrefix'] = transform_user_name_prefix
1161         odts[odt_name]['transformUserName'] = transform_user_name
1162         odts[odt_name][
1163             'transformHasFullLegalSwitch'] = transform_full_legal_switch
1164
1165         forward_CTL = odts[odt_name]['transformCTL']
1166
1167         print('ODT : %s' % odt_name)
1168         print('\tTransform ID               : %s' % transform_ID)
1169         print('\tTransform User Name Prefix : %s' % transform_user_name_prefix)
1170         print('\tTransform User Name        : %s' % transform_user_name)
1171         print(
1172             '\tHas Full / Legal Switch    : %s' % transform_full_legal_switch)
1173         print('\tForward ctl                : %s' % forward_CTL)
1174         if 'transformCTLInverse' in odts[odt_name]:
1175             inverse_CTL = odts[odt_name]['transformCTLInverse']
1176             print('\tInverse ctl                : %s' % inverse_CTL)
1177         else:
1178             print('\tInverse ctl                : %s' % 'None')
1179
1180     print('\n')
1181
1182     return odts
1183
1184
1185 def get_LMT_info(aces_CTL_directory):
1186     """
1187     Object description.
1188
1189     For versions after WGR9.
1190
1191     Parameters
1192     ----------
1193     parameter : type
1194         Parameter description.
1195
1196     Returns
1197     -------
1198     type
1199          Return value description.
1200     """
1201
1202     # TODO: Investigate refactoring with previous definition.
1203
1204     # Credit to Alex Fry for the original approach here
1205     lmt_dir = os.path.join(aces_CTL_directory, 'lmt')
1206     all_lmt = []
1207     for dir_name, subdir_list, file_list in os.walk(lmt_dir):
1208         for fname in file_list:
1209             all_lmt.append((os.path.join(dir_name, fname)))
1210
1211     lmt_CTLs = [x for x in all_lmt if
1212                 ('InvLMT' not in x) and ('README' not in x) and (
1213                     os.path.split(x)[-1][0] != '.')]
1214
1215     lmts = {}
1216
1217     for lmt_CTL in lmt_CTLs:
1218         lmt_tokens = os.path.split(lmt_CTL)
1219
1220         # Handlimg nested directories.
1221         lmt_path_tokens = os.path.split(lmt_tokens[-2])
1222         lmt_dir = lmt_path_tokens[-1]
1223         while lmt_path_tokens[-2][-3:] != 'ctl':
1224             lmt_path_tokens = os.path.split(lmt_path_tokens[-2])
1225             lmt_dir = os.path.join(lmt_path_tokens[-1], lmt_dir)
1226
1227         # Building full name.
1228         transform_CTL = lmt_tokens[-1]
1229         lmt_name = string.join(transform_CTL.split('.')[1:-1], '.')
1230
1231         # Finding id, user name and user name prefix.
1232         (transform_ID,
1233          transform_user_name,
1234          transform_user_name_prefix,
1235          transform_full_legal_switch) = get_transform_info(
1236             os.path.join(aces_CTL_directory, lmt_dir, transform_CTL))
1237
1238         # Finding inverse.
1239         transform_CTL_inverse = 'InvLMT.%s.ctl' % lmt_name
1240         if not os.path.exists(
1241                 os.path.join(lmt_tokens[-2], transform_CTL_inverse)):
1242             transform_CTL_inverse = None
1243
1244         lmts[lmt_name] = {}
1245         lmts[lmt_name]['transformCTL'] = os.path.join(lmt_dir, transform_CTL)
1246         if transform_CTL_inverse is not None:
1247             lmts[lmt_name]['transformCTLInverse'] = os.path.join(
1248                 lmt_dir, transform_CTL_inverse)
1249
1250         lmts[lmt_name]['transformID'] = transform_ID
1251         lmts[lmt_name]['transformUserNamePrefix'] = transform_user_name_prefix
1252         lmts[lmt_name]['transformUserName'] = transform_user_name
1253
1254         forward_CTL = lmts[lmt_name]['transformCTL']
1255
1256         print('LMT : %s' % lmt_name)
1257         print('\tTransform ID               : %s' % transform_ID)
1258         print('\tTransform User Name Prefix : %s' % transform_user_name_prefix)
1259         print('\tTransform User Name        : %s' % transform_user_name)
1260         print('\t Forward ctl               : %s' % forward_CTL)
1261         if 'transformCTLInverse' in lmts[lmt_name]:
1262             inverse_CTL = lmts[lmt_name]['transformCTLInverse']
1263             print('\t Inverse ctl                : %s' % inverse_CTL)
1264         else:
1265             print('\t Inverse ctl                : %s' % 'None')
1266
1267     print('\n')
1268
1269     return lmts
1270
1271
1272 def create_colorspaces(aces_CTL_directory,
1273                        lut_directory,
1274                        lut_resolution_1d,
1275                        lut_resolution_3d,
1276                        lmt_info,
1277                        odt_info,
1278                        shaper_name,
1279                        cleanup):
1280     """
1281     Generates the colorspace conversions.
1282
1283     Parameters
1284     ----------
1285     parameter : type
1286         Parameter description.
1287
1288     Returns
1289     -------
1290     type
1291          Return value description.
1292     """
1293
1294     colorspaces = []
1295
1296     ACES = create_aces()
1297
1298     ACEScc = create_ACEScc(aces_CTL_directory, lut_directory,
1299                            lut_resolution_1d, cleanup)
1300     colorspaces.append(ACEScc)
1301
1302     ACESproxy = create_ACESproxy(aces_CTL_directory, lut_directory,
1303                                  lut_resolution_1d, cleanup)
1304     colorspaces.append(ACESproxy)
1305
1306     ACEScg = create_ACEScg(aces_CTL_directory, lut_directory,
1307                            lut_resolution_1d, cleanup)
1308     colorspaces.append(ACEScg)
1309
1310     ADX10 = create_ADX(lut_directory, lut_resolution_1d, bit_depth=10)
1311     colorspaces.append(ADX10)
1312
1313     ADX16 = create_ADX(lut_directory, lut_resolution_1d, bit_depth=16)
1314     colorspaces.append(ADX16)
1315
1316     lmts = create_lmts(aces_CTL_directory,
1317                        lut_directory,
1318                        lut_resolution_1d,
1319                        lut_resolution_3d,
1320                        lmt_info,
1321                        shaper_name,
1322                        cleanup)
1323     colorspaces.extend(lmts)
1324
1325     odts, displays = create_odts(aces_CTL_directory,
1326                                  lut_directory,
1327                                  lut_resolution_1d,
1328                                  lut_resolution_3d,
1329                                  odt_info,
1330                                  shaper_name,
1331                                  cleanup,
1332                                  ACES,
1333                                  ACEScc)
1334     colorspaces.extend(odts)
1335
1336     return ACES, colorspaces, displays, ACEScc