public version 0.2
[OpenColorIO-Configs.git] / aces_1.0.1 / python / aces_ocio / colorspaces / panasonic_gh4.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Implements support for *Panasonic GH4* colorspaces conversions and transfer
6 functions based on empirical data.
7 """
8 from panasonic_gh4_data import *
9
10 import numpy as np
11 from scipy import interpolate
12 import matplotlib.pyplot as plt
13 import math, sys
14
15 import array
16 import os
17
18 import PyOpenColorIO as ocio
19
20 import aces_ocio.generate_lut as genlut
21 from aces_ocio.utilities import ColorSpace
22
23 __author__ = 'Martin Schitter'
24 __copyright__ = 'Copyright (C) 2015 - Martin Schitter'
25 __license__ = 'GNU General Public License'
26 __maintainer__ = 'Martin Schitter'
27 __email__ = 'ms+aces@mur.at'
28 __status__ = 'Experimental'
29
30 __all__ = ['create_gh4_style',
31            'create_colorspaces']
32 styles = {
33     'Scenery': [0.0079310344827586213, 0.017241379310344827,
34     0.053448275862068968, 0.27517241379310342, 0.7501724137931034,
35     1.3010344827586207, 2.1315517241379309, 3.0501724137931037,
36     4.3113793103448277, 5.8546551724137927, 7.9153448275862068,
37     10.296551724137931, 13.276896551724137, 16.769310344827588,
38     21.641896551724138, 27.386034482758621, 34.62258620689655,
39     43.353448275862071, 53.682931034482756, 65.670000000000002,
40     80.268275862068961, 102.43344827586206, 116.53896551724138,
41     134.62586206896552, 156.84931034482759, 170.11293103448276,
42     184.20775862068965, 196.58258620689656, 207.89155172413794,
43     217.53241379310344, 227.18224137931034, 236.08120689655172,
44     244.85396551724139, 252.55293103448275, 254.95758620689656,
45     255.0, 255.0, 255.0, 255.0, 255.0],
46     
47     'Natural': [0.032758620689655175, 0.048275862068965517,
48     0.16224137931034482, 0.4613793103448276, 1.0034482758620689,
49     1.7708620689655172, 2.6732758620689654, 3.9575862068965519,
50     5.5139655172413793, 7.4403448275862072, 9.6510344827586199,
51     12.639310344827587, 16.452758620689654, 20.901551724137931,
52     26.867241379310343, 34.115862068965519, 43.008448275862072,
53     52.998448275862067, 64.548103448275867, 76.758448275862065,
54     90.370862068965522, 109.64431034482759, 121.71120689655173,
55     137.56103448275863, 157.60396551724139, 169.77672413793104,
56     182.39965517241379, 194.02293103448275, 205.00258620689655,
57     214.89431034482757, 224.41034482758621, 233.6396551724138,
58     242.20258620689654, 250.67862068965516, 254.0, 254.0, 254.0,
59     254.0, 254.0, 254.0],
60     
61     'Vivid': [0.0013793103448275861, 0.0025862068965517241,
62     0.024482758620689656, 0.11120689655172414, 0.16810344827586207,
63     0.79741379310344829, 1.2001724137931034, 1.9437931034482758,
64     2.9256896551724139, 4.0244827586206897, 5.4912068965517244,
65     7.490344827586207, 10.088448275862069, 13.246379310344828,
66     17.292586206896551, 22.015862068965518, 28.217068965517242,
67     35.784310344827588, 44.731379310344828, 56.00931034482759,
68     70.820689655172416, 93.825344827586207, 108.79879310344828,
69     128.2251724137931, 152.32620689655172, 166.44137931034481,
70     181.00758620689655, 193.65810344827585, 205.37741379310344,
71     215.1153448275862, 224.87586206896552, 234.14637931034483,
72     242.44327586206896, 250.86465517241379, 254.98724137931035,
73     255.0, 255.0, 255.0, 255.0, 255.0],
74     
75     'Cinelike-D': [0.21293103448275863, 0.27379310344827584,
76     0.55500000000000005, 1.0448275862068965, 1.5686206896551724,
77     2.7260344827586205, 3.9756896551724137, 5.5051724137931037,
78     7.6129310344827585, 10.310344827586206, 13.517931034482759,
79     17.027586206896551, 21.260862068965519, 25.73741379310345,
80     30.967241379310344, 37.214310344827588, 44.142931034482757,
81     51.230689655172412, 58.622068965517244, 66.37155172413793,
82     75.241034482758621, 88.354310344827582, 96.904655172413797,
83     108.34051724137932, 125.1453448275862, 136.35637931034483,
84     150.76827586206898, 168.86913793103449, 185.40758620689655,
85     200.0305172413793, 213.47120689655173, 227.06482758620689,
86     242.42689655172413, 253.55879310344827, 254.97620689655173,
87     255.0, 255.0, 255.0, 255.0, 255.0],
88     
89     'V-Log': [31.429137931034482, 31.752758620689654,
90     31.977413793103448, 32.531551724137934, 32.976551724137934,
91     33.822586206896553, 34.706206896551727, 36.100344827586206,
92     37.966551724137929, 40.231724137931032, 42.8301724137931,
93     46.389482758620687, 50.373103448275863, 54.91965517241379,
94     60.101724137931036, 65.1351724137931, 70.557586206896545,
95     76.224827586206899, 81.544655172413798, 87.275517241379305,
96     93.204999999999998, 100.89396551724138, 105.2244827586207,
97     111.05396551724138, 118.80051724137931, 123.33396551724138,
98     129.31517241379311, 135.62620689655174, 141.68793103448274,
99     148.09086206896552, 154.73741379310346, 160.67603448275861,
100     167.00999999999999, 173.17275862068965, 179.17706896551724,
101     185.62896551724137, 190.93706896551726, 191.0, 191.0, 191.0],
102     
103     'Standard': [0.019827586206896553, 0.030344827586206897,
104     0.1296551724137931, 0.39862068965517239, 0.84431034482758616,
105     1.5265517241379309, 2.3403448275862071, 3.3762068965517242,
106     4.9127586206896554, 6.6224137931034486, 8.8127586206896549,
107     11.51948275862069, 14.727241379310344, 18.599482758620688,
108     23.895862068965517, 30.157413793103448, 38.333275862068966,
109     47.146724137931038, 57.659999999999997, 68.3953448275862,
110     81.231034482758616, 99.765517241379314, 111.80913793103448,
111     127.22586206896551, 147.36137931034483, 160.73448275862069,
112     174.77620689655171, 188.09258620689656, 200.56137931034482,
113     212.24275862068964, 222.05310344827586, 231.96775862068966,
114     240.85534482758621, 250.1894827586207, 255.0, 255.0, 255.0,
115     255.0, 255.0, 255.0],
116     
117     'BW': [0.012068965517241379, 0.03017241379310345,
118     0.15879310344827585, 0.40379310344827585, 1.039655172413793,
119     1.5532758620689655, 2.5448275862068965, 3.6275862068965519,
120     5.1298275862068969, 7.1593103448275865, 9.1227586206896554,
121     12.219482758620689, 15.602413793103448, 19.667068965517242,
122     25.558793103448277, 32.138103448275864, 40.762413793103448,
123     50.295862068965519, 61.059655172413791, 72.762758620689652,
124     86.235172413793109, 105.61879310344827, 118.42362068965517,
125     134.05362068965516, 155.23034482758621, 168.7403448275862,
126     182.65913793103448, 195.72913793103447, 208.67896551724138,
127     220.05086206896553, 229.83293103448275, 238.34137931034482,
128     246.78810344827585, 254.53086206896552, 255.0, 255.0, 255.0,
129     255.0, 255.0, 255.0],
130     
131     'Cinelike-V': [0.0, 0.0, 0.00034482758620689653,
132     0.017586206896551725, 0.16534482758620689, 0.71965517241379307,
133     1.0924137931034483, 1.8994827586206897, 2.7312068965517242,
134     3.7174137931034483, 5.0798275862068962, 7.0439655172413795,
135     9.4998275862068962, 12.756379310344828, 17.174827586206895,
136     22.375, 29.295000000000002, 37.235344827586204,
137     47.138448275862068, 58.181034482758619, 71.008103448275861,
138     89.295000000000002, 101.09810344827586, 117.49913793103448,
139     138.71793103448275, 152.10293103448276, 166.64051724137931,
140     181.39448275862068, 194.29568965517242, 206.1453448275862,
141     218.05224137931035, 229.84310344827585, 243.2646551724138,
142     254.25103448275863, 254.99310344827586, 255.0, 255.0, 255.0,
143     255.0, 255.0],
144     
145     'Portrait': [0.044999999999999998, 0.10706896551724138,
146     0.21482758620689654, 0.61827586206896556, 1.258448275862069,
147     1.9755172413793103, 2.8977586206896553, 4.243620689655172,
148     5.7982758620689658, 7.8572413793103451, 10.573620689655172,
149     13.566896551724138, 17.62396551724138, 22.091724137931035,
150     28.537068965517243, 36.138103448275864, 45.346551724137932,
151     55.862241379310348, 67.451379310344834, 79.880689655172418,
152     94.139827586206891, 113.69862068965517, 126.13396551724138,
153     141.94724137931036, 161.84068965517241, 173.89344827586206,
154     186.02206896551724, 196.99051724137931, 207.93103448275863,
155     217.54758620689654, 227.05655172413793, 235.96586206896552,
156     245.08810344827586, 253.00948275862069, 253.97603448275862,
157     254.0, 254.0, 254.0, 254.0, 254.0],
158     
159     'Custom': [0.039310344827586205, 0.1, 0.31086206896551727,
160     0.73482758620689659, 1.3484482758620691, 2.046551724137931,
161     3.1565517241379308, 4.5710344827586207, 6.1872413793103451,
162     8.3953448275862073, 10.947068965517241, 14.126724137931035,
163     18.054827586206898, 22.778965517241378, 29.185689655172414,
164     36.938448275862072, 45.952931034482759, 56.380000000000003,
165     67.094310344827591, 79.601724137931029, 93.560000000000002,
166     113.16017241379311, 125.54551724137932, 141.44189655172414,
167     162.21155172413793, 174.12517241379311, 186.3755172413793,
168     199.36862068965516, 211.13103448275862, 221.01068965517243,
169     230.70120689655172, 239.67758620689656, 249.01879310344827,
170     254.96413793103449, 255.0, 255.0, 255.0, 255.0, 255.0, 255.0]
171 }
172
173 def prepare_photostyle_to_linear(style, legal_range=False, debug_plot=False):
174
175     # 18% gray card exposure offset
176     corr = 16.5
177
178     exp = np.linspace( (corr-39)/3.0, corr/3.0, 40)
179     l = np.power(2, exp) * 0.18
180     vals = np.array(styles[style])
181     if legal_range:
182         vals = vals/255.0 * (235-16)/255.0 + 16/255.0
183     else:
184         vals = vals/255.0
185
186     # remove visible edges at some exposure values
187     print 'DIR:', dir()
188     for n in [24, 21, 4]:
189         if debug_plot:
190             plt.semilogy(vals[n], l[n], 'bx' )
191         vals = np.delete(vals, n)
192         l = np.delete(l, n)
193
194     # modify equal values to get an invertable monotone function
195     force_monotone = np.linspace(0,0.00001,len(vals))
196     vals = vals+force_monotone
197
198     #interp = interpolate.Akima1DInterpolator(vals, l)
199     interp = interpolate.PchipInterpolator(vals, l, extrapolate=False)
200
201     if debug_plot:
202         plt.semilogy(vals, l, 'b+' )
203         x = np.linspace(0., 1.0, 4096)
204         y = interp(x)
205         plt.semilogy(x, y, 'r.', ms=1 )
206         x = np.linspace(0., 1.0, 256)
207         y = interp(x)
208         plt.semilogy(x, y, 'bo', ms=2 )
209         plt.show()
210
211     return interp
212
213
214 def create_gh4_style(gamut,
215                      transfer_function,
216                      lut_directory,
217                      lut_resolution_1d,
218                      aliases,
219                      legal_range,
220                      style,
221                      variant=''):
222     """
223     Creates colorspace covering the conversion from GH4 picture styles 
224     to ACES based on empirical data
225
226     Parameters
227     ----------
228     gamut : str
229         The name of the encoding gamut to use.
230     transfer_function : str
231         The name of the transfer function to use
232     lut_directory : str or unicode 
233         The directory to use when generating LUTs
234     lut_resolution_1d : int
235         The resolution of generated 1D LUTs
236     aliases : list of str
237         Aliases for this colorspace
238     legal_range: bool
239         Add legal range shaper
240     style : str
241         Associated Photo Style
242     variant : str
243         Variant of processing
244
245     Returns
246     -------
247     ColorSpace
248     A ColorSpace container class referencing the LUTs, matrices 
249     and identifying information for the requested colorspace.
250     """
251     
252     if legal_range:
253         legal_str = 'Legal range '
254     else:
255         legal_str = ''
256         
257     name = ('%sGH4-%s - GH4-%s-Gamut %s' % ( legal_str, transfer_function,
258                                             gamut, variant)).strip()
259     if transfer_function == '':
260         name = ('Linear - GH4-%s-Gamut %s' % (gamut, variant)).strip()
261     if gamut == '':
262         name = 'Curve - %sGH4-%s' % (legal_str, transfer_function)
263
264     cs = ColorSpace(name)
265     cs.description = name
266     cs.aliases = aliases
267     cs.equality_group = ''
268     cs.family = 'Input/Panasonic/GH4 %s' %style
269     cs.is_data = False
270
271     # A linear space needs allocation variables
272     if transfer_function == '':
273         cs.allocation_type = ocio.Constants.ALLOCATION_LG2
274         cs.allocation_vars = [-8, 6, 0.00390625]
275
276     cs.to_reference_transforms = []
277
278     if transfer_function != '':
279
280         if legal_range:
281             lut = 'gh4_%s_legal_to_linear.spi1d' % transfer_function.lower()
282         else:
283             lut = 'gh4_%s_to_linear.spi1d' % transfer_function.lower()
284         f = prepare_photostyle_to_linear(transfer_function, legal_range)
285         
286         dom = np.linspace(0.0, 1.0, lut_resolution_1d)
287         data = f(dom)
288
289         # constant extrapolation
290         data_min = np.nanmin(data)
291         data_max = np.nanmax(data)
292         inds = np.where(np.isnan(data))
293         for i in inds[0]:
294             if i < lut_resolution_1d/2:
295                 data[i] = data_min
296                 #data[i] = 0.0
297             else:
298                 data[i] = data_max
299         
300         genlut.write_SPI_1d(
301             os.path.join(lut_directory, lut),
302             0.0,
303             1.0,
304             data,
305             lut_resolution_1d,
306             1)
307
308         cs.to_reference_transforms.append({
309             'type': 'lutFile',
310             'path': lut,
311             'interpolation': 'linear',
312             'direction': 'forward'})
313
314     #
315     # matrix calculations based on:
316     # http://www.colour-science.org
317     #
318     
319     if gamut != '':
320
321         # Lin -> XYZ 
322         
323         if gamut == 'V-Log':
324             cs.to_reference_transforms.append({
325                 'type': 'matrix',
326                 # colour.V_GAMUT_COLOURSPACE.RGB_to_XYZ_matrix
327                 'matrix': [0.679644,  0.152211,  0.1186 , 0.,
328                            0.260686,  0.774894, -0.03558, 0.,
329                            -0.00931 , -0.004612,  1.10298, 0.,
330                            0, 0, 0,  1.],
331                  'direction': 'forward'})
332         else:
333             cs.to_reference_transforms.append({
334                 'type': 'matrix',
335                 # colour.sRGB_COLOURSPACE.RGB_to_XYZ_matrix
336                 'matrix': [0.41238656,  0.35759149,  0.18045049, 0.0,
337                            0.21263682,  0.71518298,  0.0721802 , 0.0,
338                            0.01933062,  0.11919716,  0.95037259, 0.0,
339                            0, 0, 0, 1.0],
340                 'direction': 'forward'})
341             
342         # D65 -> D50 adaption
343         #
344         # colour.chromatic_adaptation_matrix_VonKries(
345         #    colour.xy_to_XYZ(c.sRGB_COLOURSPACE.whitepoint),
346         #    colour.xy_to_XYZ(colour.ILLUMINANTS[
347         #        'CIE 1931 2 Degree Standard Observer']['D50']), 'CAT02')
348         #
349         
350         cs.to_reference_transforms.append({
351             'type': 'matrix',
352             'matrix': [1.04251352,  0.03083262, -0.05276445, 0.0,
353                        0.0221519 ,  1.00187227, -0.02105432, 0.0,
354                        -0.00116356, -0.00341802,  0.76197255, 0.0,
355                        0, 0, 0, 1.0],
356             'direction': 'forward'})
357
358         if variant == ('XYZ'):
359             cs.from_reference_transforms = []
360             return cs
361
362         if variant == ('Calibrated'):
363             wb = [0.]*16
364             wb[0] = 1/calibration_wb[style][0]
365             wb[5] = 1/calibration_wb[style][1]
366             wb[10] = 1/calibration_wb[style][2]
367             wb[15] = 1.0
368             cs.to_reference_transforms.append({
369                 'type': 'matrix',
370                 'matrix': wb,
371                 'direction': 'forward'})
372
373             # calibrat_matrices = clibration_matrices_argyll
374             
375             cs.to_reference_transforms.append({
376                 'type': 'matrix',
377                 'matrix': (calibration_matrices[style][0] + [0.0] +
378                            calibration_matrices[style][1] + [0.0] +
379                            calibration_matrices[style][2] + [0.0] +
380                            [0, 0, 0, 1.0]),
381                 'direction': 'forward'})
382
383         # D50 -> ACES WP (~D60)
384
385         # colour.chromatic_adaptation_matrix_VonKries(
386         #   colour.xy_to_XYZ(colour.ILLUMINANTS[
387         #     'CIE 1931 2 Degree Standard Observer']['D50']),
388         #   colour.xy_to_XYZ(colour.ACES_2065_1_COLOURSPACE.whitepoint),
389         #   'CAT02')
390         
391         cs.to_reference_transforms.append({
392             'type': 'matrix',
393             'matrix': [ 9.71197151e-01,  -2.17328334e-02,  4.59761326e-02, 0.0,
394                         -1.56890302e-02,  1.00002082e+00,  1.83070138e-02, 0.0,
395                         9.70076767e-04,   3.08175255e-03,  1.21767128e+00, 0.0,
396                         0, 0, 0, 1.0],
397             'direction': 'forward'})
398
399         
400         # XYZ -> ACES
401
402         # colour.ACES_2065_1_COLOURSPACE.XYZ_to_RGB_matrix
403         cs.to_reference_transforms.append({
404             'type': 'matrix',
405             'matrix': [1.04981102e+00,   0.00000000e+00,  -9.74845410e-05, 0.,
406                        -4.95903023e-01,  1.37331305e+00,   9.82400365e-02, 0.,
407                        0.00000000e+00,   0.00000000e+00,   9.91252022e-01, 0.,
408                        0, 0, 0,  1.],
409             'direction': 'forward'})
410
411         #
412         # matrix given py panasonic / calculated by nuke
413         # for one step transformation:
414         #
415         
416         # if gamut == 'V-Log':
417         #         cs.to_reference_transforms.append({
418         #             'type': 'matrix',
419         #             'matrix': [0.724383  ,  0.166748 ,  0.108497, 0.,
420         #                        0.021354  ,  0.985138 , -0.006319, 0.,
421         #                        -0.009234 , -0.001043 ,  1.010273, 0.,
422         #                        0, 0, 0,  1.],
423         #              'direction': 'forward'})
424         # elif gamut != '':
425         #         cs.to_reference_transforms.append({
426         #             'type': 'matrix',
427         #             'matrix': [0.43964687, 0.38298151, 0.17737158, 0.0,
428         #                        0.08978026, 0.81343973, 0.09677973, 0.0,
429         #                        0.01754464, 0.11155707, 0.87089837, 0.0,
430         #                        0, 0, 0, 1.0],
431         #             'direction': 'forward'})
432
433     cs.from_reference_transforms = []
434     return cs
435
436
437 def create_colorspaces(lut_directory, lut_resolution_1d):
438     """
439     Generates the colorspace conversions.
440
441     Parameters
442     ----------
443     lut_directory : str or unicode 
444         The directory to use when generating LUTs
445     lut_resolution_1d : int
446         The resolution of generated 1D LUTs
447
448     Returns
449     -------
450     list
451          A list of colorspaces for Panasonic cameras and encodings 
452     """
453     
454     colorspaces = []
455     
456     styles_sort = styles.keys()
457     styles_sort.sort()
458
459     for legal_range in [True, False]:
460         
461         for style in styles_sort: 
462
463             if legal_range:
464                 lower_style='%s_legal' % style.lower().replace('-','_')
465             else:
466                 lower_style='%s' % style.lower().replace('-','_')
467
468             # Full conversion
469             style_1 = create_gh4_style(
470                 style,
471                 style, 
472                 lut_directory,
473                 lut_resolution_1d,
474                 ['gh4_%s_gh4_gamut' % lower_style],
475                 legal_range=legal_range,
476                 style=style)
477             colorspaces.append(style_1)
478
479             # Linearization Only
480             style_2 = create_gh4_style(
481                 '',
482                 style,
483                 lut_directory,
484                 lut_resolution_1d,
485                 ['crv_gh4_%s' % lower_style],
486                 legal_range=legal_range,
487                 style=style)
488             colorspaces.append(style_2)
489
490             # # Linearization and XYZ conversion
491             # # only necessary for matrix calculation
492             #
493             # style_3 = create_gh4_style(
494             #     style,
495             #     style,
496             #     lut_directory,
497             #     lut_resolution_1d,
498             #     ['gh4_%s_xyz' % lower_style],
499             #     legal_range=legal_range,
500             #     style=style,
501             #     variant='XYZ')
502             # colorspaces.append(style_3)
503
504             # Primaries Only
505             style_4 = create_gh4_style(
506                 style,
507                 '',
508                 lut_directory,
509                 lut_resolution_1d,
510                 ['lin_gh4_%s_gamut' % lower_style],
511                 legal_range=legal_range,
512                 style=style
513             )
514             colorspaces.append(style_4)
515
516             if style != 'BW':
517
518                 # Full conversion calibrated
519                 style_5 = create_gh4_style(
520                     style,
521                     style, 
522                     lut_directory,
523                     lut_resolution_1d,
524                     ['gh4_%s_gh4_calibrated_gamut'% lower_style],
525                     legal_range=legal_range,
526                     style=style,
527                     variant='Calibrated')
528                 colorspaces.append(style_5)
529
530                 # Primaries Only calibrated
531                 style_6 = create_gh4_style(
532                     style,
533                     '',
534                     lut_directory,
535                     lut_resolution_1d,
536                     ['lin_gh4_%s_calibrated_gamut'%lower_style],
537                     legal_range=legal_range,
538                     style=style,
539                     variant='Calibrated')
540                 colorspaces.append(style_6)
541
542             
543     return colorspaces