Added documentation for individual functions
[OpenColorIO-Configs.git] / aces_1.0.0 / python / aces_ocio / colorspaces / general.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Implements support for general colorspaces conversions and transfer functions.
6 """
7
8 from __future__ import division
9
10 import array
11 import os
12
13 import PyOpenColorIO as ocio
14
15 import aces_ocio.generate_lut as genlut
16 from aces_ocio.colorspaces import aces
17 from aces_ocio.utilities import ColorSpace, mat44_from_mat33
18
19 __author__ = 'ACES Developers'
20 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
21 __license__ = ''
22 __maintainer__ = 'ACES Developers'
23 __email__ = 'aces@oscars.org'
24 __status__ = 'Production'
25
26 __all__ = ['create_matrix_colorspace',
27            'create_transfer_colorspace',
28            'create_matrix_plus_transfer_colorspace',
29            'transfer_function_sRGB_to_linear',
30            'transfer_function_Rec709_to_linear',
31            'transfer_function_Rec2020_10bit_to_linear',
32            'transfer_function_Rec2020_12bit_to_linear',
33            'transfer_function_Rec1886_to_linear',
34            'create_colorspaces',
35            'create_raw']
36
37
38 # -------------------------------------------------------------------------
39 # *Matrix Transform*
40 # -------------------------------------------------------------------------
41 def create_matrix_colorspace(name='matrix',
42                              from_reference_values=None,
43                              to_reference_values=None,
44                              aliases=None):
45     """
46     Creates a ColorSpace that only uses *Matrix Transforms*
47
48     Parameters
49     ----------
50     name : str, optional
51         Aliases for this colorspace
52     from_reference_values : list of matrices
53         List of matrices to convert from the reference colorspace to this space        
54     to_reference_values : list of matrices
55         List of matrices to convert to the reference colorspace from this space
56     aliases : list of str, optional
57         Aliases for this colorspace
58
59     Returns
60     -------
61     ColorSpace
62          A *Matrix Transform*-based ColorSpace
63     """
64
65     if from_reference_values is None:
66         from_reference_values = []
67
68     if to_reference_values is None:
69         to_reference_values = []
70
71     if aliases is None:
72         aliases = []
73
74     cs = ColorSpace(name)
75     cs.description = 'The %s color space' % name
76     cs.aliases = aliases
77     cs.equality_group = name
78     cs.family = 'Utility'
79     cs.is_data = False
80
81     # A linear space needs allocation variables.
82     cs.allocation_type = ocio.Constants.ALLOCATION_UNIFORM
83     cs.allocation_vars = [0, 1]
84
85     cs.to_reference_transforms = []
86     if to_reference_values:
87         for matrix in to_reference_values:
88             cs.to_reference_transforms.append({
89                 'type': 'matrix',
90                 'matrix': mat44_from_mat33(matrix),
91                 'direction': 'forward'})
92
93     cs.from_reference_transforms = []
94     if from_reference_values:
95         for matrix in from_reference_values:
96             cs.from_reference_transforms.append({
97                 'type': 'matrix',
98                 'matrix': mat44_from_mat33(matrix),
99                 'direction': 'forward'})
100
101     return cs
102
103
104 # -------------------------------------------------------------------------
105 # *Transfer Function Transform*
106 # -------------------------------------------------------------------------
107 def create_transfer_colorspace(name='transfer',
108                                transfer_function_name='transfer_function',
109                                transfer_function=lambda x: x,
110                                lut_directory='/tmp',
111                                lut_resolution_1d=1024,
112                                aliases=None):
113     """
114     Creates a ColorSpace that only uses transfer functions encoded as 1D LUTs
115
116     Parameters
117     ----------
118     name : str, optional
119         Aliases for this colorspace
120     transfer_function_name : str, optional
121         The name of the transfer function
122     transfer_function : function, optional
123         The transfer function to be evaluated
124     lut_directory : str or unicode 
125         The directory to use when generating LUTs
126     lut_resolution_1d : int
127         The resolution of generated 1D LUTs
128     aliases : list of str
129         Aliases for this colorspace
130
131     Returns
132     -------
133     ColorSpace
134          A *LUT1D Transform*-based ColorSpace representing a transfer function
135     """
136
137     if aliases is None:
138         aliases = []
139
140     cs = ColorSpace(name)
141     cs.description = 'The %s color space' % name
142     cs.aliases = aliases
143     cs.equality_group = name
144     cs.family = 'Utility'
145     cs.is_data = False
146
147     # A linear space needs allocation variables.
148     cs.allocation_type = ocio.Constants.ALLOCATION_UNIFORM
149     cs.allocation_vars = [0, 1]
150
151     # Sampling the transfer function.
152     data = array.array('f', '\0' * lut_resolution_1d * 4)
153     for c in range(lut_resolution_1d):
154         data[c] = transfer_function(c / (lut_resolution_1d - 1))
155
156     # Writing the sampled data to a *LUT*.
157     lut = '%s_to_linear.spi1d' % transfer_function_name
158     genlut.write_SPI_1d(
159         os.path.join(lut_directory, lut),
160         0,
161         1,
162         data,
163         lut_resolution_1d,
164         1)
165
166     # Creating the *to_reference* transforms.
167     cs.to_reference_transforms = []
168     cs.to_reference_transforms.append({
169         'type': 'lutFile',
170         'path': lut,
171         'interpolation': 'linear',
172         'direction': 'forward'})
173
174     # Creating the *from_reference* transforms.
175     cs.from_reference_transforms = []
176
177     return cs
178
179
180 # -------------------------------------------------------------------------
181 # *Transfer Function + Matrix Transform*
182 # -------------------------------------------------------------------------
183 def create_matrix_plus_transfer_colorspace(
184         name='matrix_plus_transfer',
185         transfer_function_name='transfer_function',
186         transfer_function=lambda x: x,
187         lut_directory='/tmp',
188         lut_resolution_1d=1024,
189         from_reference_values=None,
190         to_reference_values=None,
191         aliases=None):
192     """
193     Creates a ColorSpace that uses transfer functions encoded as 1D LUTs and
194     matrice
195
196     Parameters
197     ----------
198     name : str, optional
199         Aliases for this colorspace
200     transfer_function_name : str, optional
201         The name of the transfer function
202     transfer_function : function, optional
203         The transfer function to be evaluated
204     lut_directory : str or unicode 
205         The directory to use when generating LUTs
206     lut_resolution_1d : int
207         The resolution of generated 1D LUTs
208     from_reference_values : list of matrices
209         List of matrices to convert from the reference colorspace to this space        
210     to_reference_values : list of matrices
211         List of matrices to convert to the reference colorspace from this space
212     aliases : list of str
213         Aliases for this colorspace
214
215     Returns
216     -------
217     ColorSpace
218          A *Matrx and LUT1D Transform*-based ColorSpace representing a transfer 
219          function and matrix
220     """
221
222     if from_reference_values is None:
223         from_reference_values = []
224
225     if to_reference_values is None:
226         to_reference_values = []
227
228     if aliases is None:
229         aliases = []
230
231     cs = ColorSpace(name)
232     cs.description = 'The %s color space' % name
233     cs.aliases = aliases
234     cs.equality_group = name
235     cs.family = 'Utility'
236     cs.is_data = False
237
238     # A linear space needs allocation variables.
239     cs.allocation_type = ocio.Constants.ALLOCATION_UNIFORM
240     cs.allocation_vars = [0, 1]
241
242     # Sampling the transfer function.
243     data = array.array('f', '\0' * lut_resolution_1d * 4)
244     for c in range(lut_resolution_1d):
245         data[c] = transfer_function(c / (lut_resolution_1d - 1))
246
247     # Writing the sampled data to a *LUT*.
248     lut = '%s_to_linear.spi1d' % transfer_function_name
249     genlut.write_SPI_1d(
250         os.path.join(lut_directory, lut),
251         0,
252         1,
253         data,
254         lut_resolution_1d,
255         1)
256
257     # Creating the *to_reference* transforms.
258     cs.to_reference_transforms = []
259     if to_reference_values:
260         cs.to_reference_transforms.append({
261             'type': 'lutFile',
262             'path': lut,
263             'interpolation': 'linear',
264             'direction': 'forward'})
265
266         for matrix in to_reference_values:
267             cs.to_reference_transforms.append({
268                 'type': 'matrix',
269                 'matrix': mat44_from_mat33(matrix),
270                 'direction': 'forward'})
271
272     # Creating the *from_reference* transforms.
273     cs.from_reference_transforms = []
274     if from_reference_values:
275         for matrix in from_reference_values:
276             cs.from_reference_transforms.append({
277                 'type': 'matrix',
278                 'matrix': mat44_from_mat33(matrix),
279                 'direction': 'forward'})
280
281         cs.from_reference_transforms.append({
282             'type': 'lutFile',
283             'path': lut,
284             'interpolation': 'linear',
285             'direction': 'inverse'})
286
287     return cs
288
289
290 # Transfer functions for standard colorspaces.
291 def transfer_function_sRGB_to_linear(v):
292     """
293     The sRGB (IEC 61966-2-1) transfer function
294
295     Parameters
296     ----------
297     v : float
298         The normalized value to pass through the function
299
300     Returns
301     -------
302     float
303         A converted value
304     """
305     a = 1.055
306     b = 0.04045
307     d = 12.92
308     g = 2.4
309
310     if v < b:
311         return v / d
312     return pow(((v + (a - 1)) / a), g)
313
314
315 def transfer_function_Rec709_to_linear(v):
316     """
317     The Rec.709 transfer function
318
319     Parameters
320     ----------
321     v : float
322         The normalized value to pass through the function
323
324     Returns
325     -------
326     float
327         A converted value
328     """
329     a = 1.099
330     b = 0.018
331     d = 4.5
332     g = (1.0 / 0.45)
333
334     if v < b * d:
335         return v / d
336
337     return pow(((v + (a - 1)) / a), g)
338
339
340 def transfer_function_Rec2020_10bit_to_linear(v):
341     """
342     The Rec.2020 10-bit transfer function
343
344     Parameters
345     ----------
346     v : float
347         The normalized value to pass through the function
348
349     Returns
350     -------
351     float
352         A converted value
353     """
354     a = 1.099
355     b = 0.018
356     d = 4.5
357     g = (1.0 / 0.45)
358
359     if v < b * d:
360         return v / d
361
362     return pow(((v + (a - 1)) / a), g)
363
364
365 def transfer_function_Rec2020_12bit_to_linear(v):
366     """
367     The Rec.2020 12-bit transfer function
368
369     Parameters
370     ----------
371     v : float
372         The normalized value to pass through the function
373
374     Returns
375     -------
376     float
377         A converted value
378     """
379     a = 1.0993
380     b = 0.0181
381     d = 4.5
382     g = (1.0 / 0.45)
383
384     if v < b * d:
385         return v / d
386
387     return pow(((v + (a - 1)) / a), g)
388
389
390 def transfer_function_Rec1886_to_linear(v):
391     """
392     The Rec.1886 transfer function
393
394     Parameters
395     ----------
396     v : float
397         The normalized value to pass through the function
398
399     Returns
400     -------
401     float
402         A converted value
403     """
404     g = 2.4
405     Lw = 1
406     Lb = 0
407
408     # Ignoring legal to full scaling for now.
409     # v = (1023.0*v - 64.0)/876.0
410
411     t = pow(Lw, 1.0 / g) - pow(Lb, 1.0 / g)
412     a = pow(t, g)
413     b = pow(Lb, 1.0 / g) / t
414
415     return a * pow(max((v + b), 0.0), g)
416
417
418 def create_colorspaces(lut_directory,
419                        lut_resolution_1d):
420     """
421     Generates the colorspace conversions.
422
423     Parameters
424     ----------
425     lut_directory : str or unicode 
426         The directory to use when generating LUTs
427     lut_resolution_1d : int
428         The resolution of generated 1D LUTs
429
430     Returns
431     -------
432     list
433          A list of colorspaces for general colorspaces and encodings 
434     """
435
436     colorspaces = []
437
438     # -------------------------------------------------------------------------
439     # XYZ
440     # -------------------------------------------------------------------------
441     cs = create_matrix_colorspace('XYZ-D60',
442                                   to_reference_values=[aces.ACES_XYZ_TO_AP0],
443                                   from_reference_values=[aces.ACES_AP0_TO_XYZ],
444                                   aliases=['lin_xyz_d60'])
445     colorspaces.append(cs)
446
447     # -------------------------------------------------------------------------
448     # P3-D60
449     # -------------------------------------------------------------------------
450     # *ACES* to *Linear*, *P3D60* primaries
451     XYZ_to_P3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
452                     -0.8325796487, 1.7692317536, 0.0237127115,
453                     0.0388233815, -0.0824996856, 1.0363685997]
454
455     cs = create_matrix_colorspace(
456         'Linear - P3-D60',
457         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_P3D60],
458         aliases=['lin_p3d60'])
459     colorspaces.append(cs)
460
461     # -------------------------------------------------------------------------
462     # P3-DCI
463     # -------------------------------------------------------------------------
464     # *ACES* to *Linear*, *P3DCI* primaries
465     XYZ_to_P3DCI = [2.7253940305, -1.0180030062, -0.4401631952,
466                     -0.7951680258, 1.6897320548, 0.0226471906,
467                     0.0412418914, -0.0876390192, 1.1009293786]
468
469     cs = create_matrix_colorspace(
470         'Linear - P3-DCI',
471         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_P3DCI],
472         aliases=['lin_p3dci'])
473     colorspaces.append(cs)
474
475     # -------------------------------------------------------------------------
476     # sRGB
477     # -------------------------------------------------------------------------
478     # *ACES* to *Linear*, *Rec. 709* primaries.
479     # *sRGB* and *Rec 709* use the same gamut.
480     XYZ_to_Rec709 = [3.2409699419, -1.5373831776, -0.4986107603,
481                      -0.9692436363, 1.8759675015, 0.0415550574,
482                      0.0556300797, -0.2039769589, 1.0569715142]
483
484     cs = create_matrix_colorspace(
485         'Linear - sRGB',
486         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
487         aliases=['lin_srgb'])
488     colorspaces.append(cs)
489
490     # *Linear* to *sRGB* Transfer Function*
491     cs = create_transfer_colorspace(
492         'Curve - sRGB',
493         'sRGB',
494         transfer_function_sRGB_to_linear,
495         lut_directory,
496         lut_resolution_1d,
497         aliases=['crv_srgb'])
498     colorspaces.append(cs)
499
500     # *ACES* to *sRGB* Primaries + Transfer Function*
501     cs = create_matrix_plus_transfer_colorspace(
502         'sRGB',
503         'sRGB',
504         transfer_function_sRGB_to_linear,
505         lut_directory,
506         lut_resolution_1d,
507         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
508         aliases=['srgb'])
509     colorspaces.append(cs)
510
511     # -------------------------------------------------------------------------
512     # Rec 709
513     # -------------------------------------------------------------------------
514     # *ACES* to *Linear*, *Rec. 709* primaries
515     XYZ_to_Rec709 = [3.2409699419, -1.5373831776, -0.4986107603,
516                      -0.9692436363, 1.8759675015, 0.0415550574,
517                      0.0556300797, -0.2039769589, 1.0569715142]
518
519     cs = create_matrix_colorspace(
520         'Linear - Rec.709',
521         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
522         aliases=['lin_rec709'])
523     colorspaces.append(cs)
524
525     # *Linear* to *Rec. 709* Transfer Function*
526     cs = create_transfer_colorspace(
527         'Curve - Rec.709',
528         'rec709',
529         transfer_function_Rec709_to_linear,
530         lut_directory,
531         lut_resolution_1d,
532         aliases=['crv_rec709'])
533     colorspaces.append(cs)
534
535     # *ACES* to *Rec. 709* Primaries + Transfer Function*
536     cs = create_matrix_plus_transfer_colorspace(
537         'Rec.709 - Camera',
538         'rec709',
539         transfer_function_Rec709_to_linear,
540         lut_directory,
541         lut_resolution_1d,
542         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
543         aliases=['rec709_camera'])
544     colorspaces.append(cs)
545
546     # -------------------------------------------------------------------------
547     # Rec 2020
548     # -------------------------------------------------------------------------
549     # *ACES* to *Linear*, *Rec. 2020* primaries
550     XYZ_to_Rec2020 = [1.7166511880, -0.3556707838, -0.2533662814,
551                       -0.6666843518, 1.6164812366, 0.0157685458,
552                       0.0176398574, -0.0427706133, 0.9421031212]
553
554     cs = create_matrix_colorspace(
555         'Linear - Rec.2020',
556         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec2020],
557         aliases=['lin_rec2020'])
558     colorspaces.append(cs)
559
560     # *Linear* to *Rec. 2020 10 bit* Transfer Function*
561     cs = create_transfer_colorspace(
562         'Curve - Rec.2020',
563         'rec2020',
564         transfer_function_Rec2020_10bit_to_linear,
565         lut_directory,
566         lut_resolution_1d,
567         aliases=['crv_rec2020'])
568     colorspaces.append(cs)
569
570     # *ACES* to *Rec. 2020 10 bit* Primaries + Transfer Function*
571     cs = create_matrix_plus_transfer_colorspace(
572         'Rec.2020 - Camera',
573         'rec2020',
574         transfer_function_Rec2020_10bit_to_linear,
575         lut_directory,
576         lut_resolution_1d,
577         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec2020],
578         aliases=['rec2020_camera'])
579     colorspaces.append(cs)
580
581     # -------------------------------------------------------------------------
582     # Rec 1886
583     # -------------------------------------------------------------------------
584     # *Linear* to *Rec.1886* Transfer Function*
585     cs = create_transfer_colorspace(
586         'Curve - Rec.1886',
587         'rec1886',
588         transfer_function_Rec1886_to_linear,
589         lut_directory,
590         lut_resolution_1d,
591         aliases=['crv_rec1886'])
592     colorspaces.append(cs)
593
594     # *ACES* to *sRGB* Primaries + Transfer Function*
595     cs = create_matrix_plus_transfer_colorspace(
596         'Rec.709 - Display',
597         'rec1886',
598         transfer_function_Rec1886_to_linear,
599         lut_directory,
600         lut_resolution_1d,
601         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
602         aliases=['rec709_display'])
603     colorspaces.append(cs)
604
605     # *ACES* to *sRGB* Primaries + Transfer Function*
606     cs = create_matrix_plus_transfer_colorspace(
607         'Rec.2020 - Display',
608         'rec1886',
609         transfer_function_Rec1886_to_linear,
610         lut_directory,
611         lut_resolution_1d,
612         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec2020],
613         aliases=['rec2020_display'])
614     colorspaces.append(cs)
615
616     # -------------------------------------------------------------------------
617     # ProPhoto
618     # -------------------------------------------------------------------------
619     # *ACES* to *Linear*, *Pro Photo* primaries
620     AP0_to_RIMM = [1.2412367771, -0.1685692287, -0.0726675484,
621                    0.0061203066, 1.083151174, -0.0892714806,
622                    -0.0032853314, 0.0099796402, 0.9933056912]
623
624     cs = create_matrix_colorspace(
625         'Linear - RIMM ROMM (ProPhoto)',
626         from_reference_values=[AP0_to_RIMM],
627         aliases=['lin_prophoto', 'lin_rimm'])
628     colorspaces.append(cs)
629
630     # -------------------------------------------------------------------------
631     # Adobe RGB
632     # -------------------------------------------------------------------------
633     # *ACES* to *Linear*, *Adobe RGB* primaries
634     AP0_to_ADOBERGB = [1.7245603168, -0.4199935942, -0.3045667227,
635                        -0.2764799142, 1.3727190877, -0.0962391734,
636                        -0.0261255258, -0.0901747807, 1.1163003065]
637
638     cs = create_matrix_colorspace(
639         'Linear - Adobe RGB',
640         from_reference_values=[AP0_to_ADOBERGB],
641         aliases=['lin_adobergb'])
642     colorspaces.append(cs)
643
644     # -------------------------------------------------------------------------
645     # Adobe Wide Gamut RGB
646     # -------------------------------------------------------------------------
647     # *ACES* to *Linear*, *Adobe Wide Gamut RGB* primaries
648     AP0_to_ADOBERGB = [1.3809814778, -0.1158594573, -0.2651220205,
649                        0.0057015535, 1.0402949043, -0.0459964578,
650                        -0.0038908746, -0.0597091815, 1.0636000561]
651
652     cs = create_matrix_colorspace(
653         'Linear - Adobe Wide Gamut RGB',
654         from_reference_values=[AP0_to_ADOBERGB],
655         aliases=['lin_adobewidegamutrgb'])
656     colorspaces.append(cs)
657
658     return colorspaces
659
660
661 def create_raw():
662     """
663     Creates the *raw* color space
664
665     Parameters
666     ----------
667     None
668
669     Returns
670     -------
671     ColorSpace
672          *raw* and all its identifying information
673     """
674     # *Raw* utility space
675     name = 'Raw'
676     raw = ColorSpace(name)
677     raw.description = 'The %s color space' % name
678     raw.aliases = ['raw']
679     raw.equality_group = name
680     raw.family = 'Utility'
681     raw.is_data = True
682
683     return raw