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