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