def test_as_float(self): """ Tests :func:`colour.utilities.array.as_float` definition. """ self.assertEqual(as_float(1), 1.0) self.assertEqual(as_float(np.array([1])), 1.0) np.testing.assert_almost_equal( as_float(np.array([1, 2, 3])), np.array([1.0, 2.0, 3.0])) self.assertEqual( as_float(np.array([1, 2, 3])).dtype, DEFAULT_FLOAT_DTYPE) self.assertIsInstance(as_float(1), DEFAULT_FLOAT_DTYPE)
def intermediate_lightness_function_CIE1976(Y, Y_n=100): """ Returns the intermediate value :math:`f(Y/Yn)` in the *Lightness* :math:`L^*` computation for given *luminance* :math:`Y` using given reference white *luminance* :math:`Y_n` as per *CIE 1976* recommendation. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. Y_n : numeric or array_like, optional White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like Intermediate value :math:`f(Y/Yn)`. Notes ----- +-------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``Y`` | [0, 100] | [0, 100] | +-------------+-----------------------+---------------+ +-------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``f_Y_Y_n`` | [0, 1] | [0, 1] | +-------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> intermediate_lightness_function_CIE1976(12.19722535) ... # doctest: +ELLIPSIS 0.4959299... >>> intermediate_lightness_function_CIE1976(12.19722535, 95) ... # doctest: +ELLIPSIS 0.5044821... """ Y = as_float_array(Y) Y_n = as_float_array(Y_n) Y_Y_n = Y / Y_n f_Y_Y_n = as_float( np.where( Y_Y_n > (24 / 116) ** 3, spow(Y_Y_n, 1 / 3), (841 / 108) * Y_Y_n + 16 / 116, )) return f_Y_Y_n
def maximise_spatial_frequency(L): """ Maximises the spatial frequency :math:`u` for given luminance value. Parameters ---------- L : numeric or array_like Luminance value at which to maximize the spatial frequency :math:`u`. Returns ------- numeric or ndarray Maximised spatial frequency :math:`u`. """ maximised_spatial_frequency = [] for L_v in L: X_0 = 60 d = colour.contrast.pupil_diameter_Barten1999(L_v, X_0) sigma = colour.contrast.sigma_Barten1999(0.5 / 60, 0.08 / 60, d) E = colour.contrast.retinal_illuminance_Barten1999(L_v, d, True) maximised_spatial_frequency.append( fmin(lambda x: ( -colour.contrast.contrast_sensitivity_function_Barten1999( u=x, sigma=sigma, X_0=X_0, E=E, **settings_BT2246) ), 0, disp=False)[0]) return as_float(np.array(maximised_spatial_frequency))
def oetf_reverse_ARIBSTDB67(E_p, r=0.5, constants=ARIBSTDB67_CONSTANTS): """ Defines *ARIB STD-B67 (Hybrid Log-Gamma)* reverse opto-electrical transfer function (OETF / OECF). Parameters ---------- E_p : numeric or array_like Non-linear signal :math:`E'`. r : numeric, optional Video level corresponding to reference white level. constants : Structure, optional *ARIB STD-B67 (Hybrid Log-Gamma)* constants. Returns ------- numeric or ndarray Voltage :math:`E` normalised by the reference white level and proportional to the implicit light intensity that would be detected with a reference camera color channel R, G, B. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E_p`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`AssociationofRadioIndustriesandBusinesses2015a` Examples -------- >>> oetf_reverse_ARIBSTDB67(0.212132034355964) # doctest: +ELLIPSIS 0.1799999... """ E_p = to_domain_1(E_p) a = constants.a b = constants.b c = constants.c with domain_range_scale('ignore'): E = np.where( E_p <= oetf_ARIBSTDB67(1), (E_p / r) ** 2, np.exp((E_p - c) / a) + b, ) return as_float(from_range_1(E))
def pupil_diameter_Barten1999(L, X_0=60, Y_0=None): """ Returns the pupil diameter for given luminance and object or stimulus angular size using *Barten (1999)* method. Parameters ---------- L : numeric or array_like Average luminance :math:`L` in :math:`cd/m^2`. X_0 : numeric or array_like, optional Angular size of the object :math:`X_0` in degrees in the x direction. Y_0 : numeric or array_like, optional Angular size of the object :math:`X_0` in degrees in the y direction. References ---------- :cite:`Barten1999`, :cite:`Barten2003`, :cite:`Cowan2004`, :cite:`InternationalTelecommunicationUnion2015`, Examples -------- >>> pupil_diameter_Barten1999(100, 60, 60) # doctest: +ELLIPSIS 2.0777571... """ L = as_float_array(L) X_0 = as_float_array(X_0) Y_0 = X_0 if Y_0 is None else as_float_array(Y_0) return as_float(5 - 3 * np.tanh(0.4 * np.log(L * X_0 * Y_0 / 40 ** 2)))
def optical_MTF_Barten1999(u, sigma=0.01): """ Returns the optical modulation transfer function (MTF) :math:`M_{opt}` of the eye using *Barten (1999)* method. Parameters ---------- u : numeric or array_like Spatial frequency :math:`u`, the cycles per degree. sigma : numeric or array_like, optional Standard deviation :math:`\\sigma` of the line-spread function resulting from the convolution of the different elements of the convolution process. Returns ------- numeric or array_like Optical modulation transfer function (MTF) :math:`M_{opt}` of the eye. References ---------- :cite:`Barten1999`, :cite:`Barten2003`, :cite:`Cowan2004`, :cite:`InternationalTelecommunicationUnion2015`, Examples -------- >>> optical_MTF_Barten1999(4, 0.01) # doctest: +ELLIPSIS 0.9689107... """ u = as_float_array(u) sigma = as_float_array(sigma) return as_float(np.exp(-2 * np.pi ** 2 * sigma ** 2 * u ** 2))
def log_encoding_ALEXALogC(x, firmware='SUP 3.x', method='Linear Scene Exposure Factor', EI=800): """ Defines the *ALEXA Log C* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Linear data :math:`x`. firmware : unicode, optional **{'SUP 3.x', 'SUP 2.x'}**, Alexa firmware version. method : unicode, optional **{'Linear Scene Exposure Factor', 'Normalised Sensor Signal'}**, Conversion method. EI : int, optional Ei. Returns ------- numeric or ndarray *ALEXA Log C* encoded data :math:`t`. References ---------- :cite:`ARRI2012a` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> log_encoding_ALEXALogC(0.18) # doctest: +ELLIPSIS 0.3910068... """ x = to_domain_1(x) cut, a, b, c, d, e, f, _e_cut_f = ( ALEXA_LOG_C_CURVE_CONVERSION_DATA[firmware][method][EI]) t = np.where(x > cut, c * np.log10(a * x + b) + d, e * x + f) return as_float(from_range_1(t))
def log_decoding_ALEXALogC(t, firmware='SUP 3.x', method='Linear Scene Exposure Factor', EI=800): """ Defines the *ALEXA Log C* log decoding curve / electro-optical transfer function. Parameters ---------- t : numeric or array_like *ALEXA Log C* encoded data :math:`t`. firmware : unicode, optional **{'SUP 3.x', 'SUP 2.x'}**, Alexa firmware version. method : unicode, optional **{'Linear Scene Exposure Factor', 'Normalised Sensor Signal'}**, Conversion method. EI : int, optional Ei. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`ARRI2012a` Examples -------- >>> log_decoding_ALEXALogC(0.391006832034084) # doctest: +ELLIPSIS 0.18... """ t = to_domain_1(t) cut, a, b, c, d, e, f, _e_cut_f = ( ALEXA_LOG_C_CURVE_CONVERSION_DATA[firmware][method][EI]) x = np.where(t > e * cut + f, (10 ** ((t - d) / c) - b) / a, (t - f) / e) return as_float(from_range_1(x))
def intermediate_luminance_function_CIE1976(f_Y_Y_n, Y_n=100): """ Returns the *luminance* :math:`Y` in the *luminance* :math:`Y` computation for given intermediate value :math:`f(Y/Yn)` using given reference white *luminance* :math:`Y_n` as per *CIE 1976* recommendation. Parameters ---------- f_Y_Y_n : numeric or array_like Intermediate value :math:`f(Y/Yn)`. Y_n : numeric or array_like White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like *luminance* :math:`Y`. Notes ----- +-------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``f_Y_Y_n`` | [0, 1] | [0, 1] | +-------------+-----------------------+---------------+ +-------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``Y`` | [0, 100] | [0, 100] | +-------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> intermediate_luminance_function_CIE1976(0.495929964178047) ... # doctest: +ELLIPSIS 12.1972253... >>> intermediate_luminance_function_CIE1976(0.504482161449319, 95) ... # doctest: +ELLIPSIS 12.1972253... """ f_Y_Y_n = as_float_array(f_Y_Y_n) Y_n = as_float_array(Y_n) Y = as_float( np.where( f_Y_Y_n > 24 / 116, Y_n * f_Y_Y_n ** 3, Y_n * (f_Y_Y_n - 16 / 116) * (108 / 841), )) return Y
def oetf_ARIBSTDB67(E, r=0.5, constants=ARIBSTDB67_CONSTANTS): """ Defines *ARIB STD-B67 (Hybrid Log-Gamma)* opto-electrical transfer function (OETF / OECF). Parameters ---------- E : numeric or array_like Voltage normalised by the reference white level and proportional to the implicit light intensity that would be detected with a reference camera color channel R, G, B. r : numeric, optional Video level corresponding to reference white level. constants : Structure, optional *ARIB STD-B67 (Hybrid Log-Gamma)* constants. Returns ------- numeric or ndarray Resulting non-linear signal :math:`E'`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E_p`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`AssociationofRadioIndustriesandBusinesses2015a` Examples -------- >>> oetf_ARIBSTDB67(0.18) # doctest: +ELLIPSIS 0.2121320... """ E = to_domain_1(E) a = constants.a b = constants.b c = constants.c E_p = np.where(E <= 1, r * np.sqrt(E), a * np.log(E - b) + c) return as_float(from_range_1(E_p))
def sigma_Barten1999(sigma_0=0.5 / 60, C_ab=0.08 / 60, d=2.1): """ Returns the standard deviation :math:`\\sigma` of the line-spread function resulting from the convolution of the different elements of the convolution process using *Barten (1999)* method. The :math:`\\sigma` quantity depends on the pupil diameter :math:`d` of the eye lens. For very small pupil diameters, :math:`\\sigma` increases inversely proportionally with pupil size because of diffraction, and for large pupil diameters, :math:`\\sigma` increases about linearly with pupil size because of chromatic aberration and others aberrations. Parameters ---------- sigma_0 : numeric or array_like, optional Constant :math:`\\sigma_{0}` in degrees. C_ab : numeric or array_like, optional Spherical aberration of the eye :math:`C_{ab}` in :math:`degrees\\div mm`. d : numeric or array_like, optional Pupil diameter :math:`d` in millimeters. Returns ------- ndarray Standard deviation :math:`\\sigma` of the line-spread function resulting from the convolution of the different elements of the convolution process. Warnings -------- This definition expects :math:`\\sigma_{0}` and :math:`C_{ab}` to be given in degrees and :math:`degrees\\div mm` respectively. However, in the literature, the values for :math:`\\sigma_{0}` and :math:`C_{ab}` are usually given in :math:`arc min` and :math:`arc min\\div mm` respectively, thus they need to be divided by 60. References ---------- :cite:`Barten1999`, :cite:`Barten2003`, :cite:`Cowan2004`, :cite:`InternationalTelecommunicationUnion2015`, Examples -------- >>> sigma_Barten1999(0.5 / 60, 0.08 / 60, 2.1) # doctest: +ELLIPSIS 0.0087911... """ sigma_0 = as_float_array(sigma_0) C_ab = as_float_array(C_ab) d = as_float_array(d) return as_float(np.sqrt(sigma_0 ** 2 + (C_ab * d) ** 2))
def oetf_reverse_sRGB(V): """ Defines the *sRGB* colourspace reverse opto-electronic transfer function (OETF / OECF). Parameters ---------- V : numeric or array_like Electrical signal :math:`V`. Returns ------- numeric or ndarray Corresponding *luminance* :math:`L` of the image. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`InternationalElectrotechnicalCommission1999a`, :cite:`InternationalTelecommunicationUnion2015i` Examples -------- >>> oetf_reverse_sRGB(0.461356129500442) # doctest: +ELLIPSIS 0.1... """ V = to_domain_1(V) with domain_range_scale('ignore'): L = np.where( V <= oetf_sRGB(0.0031308), V / 12.92, spow((V + 0.055) / 1.055, 2.4), ) return as_float(from_range_1(L))
def eotf_SMPTE240M(V_r): """ Defines *SMPTE 240M* electro-optical transfer function (EOTF / EOCF). Parameters ---------- V_r : numeric or array_like Video signal level :math:`V_r` driving the reference reproducer normalised to the system reference white. Returns ------- numeric or ndarray Light output :math:`L_r` from the reference reproducer normalised to the system reference white. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V_c`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_c`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SocietyofMotionPictureandTelevisionEngineers1999b` Examples -------- >>> eotf_SMPTE240M(0.402285796753870) # doctest: +ELLIPSIS 0.1... """ V_r = to_domain_1(V_r) with domain_range_scale('ignore'): L_r = np.where( V_r < oetf_SMPTE240M(0.0228), V_r / 4, spow((V_r + 0.1115) / 1.1115, 1 / 0.45), ) return as_float(from_range_1(L_r))
def hue_quadrature(h): """ Returns the hue quadrature from given hue :math:`h` angle in degrees. Parameters ---------- h : numeric or array_like Hue :math:`h` angle in degrees. Returns ------- numeric or ndarray Hue quadrature. Examples -------- >>> hue_quadrature(219.0484326582719) # doctest: +ELLIPSIS 278.0607358... """ h = as_float_array(h) h_i = HUE_DATA_FOR_HUE_QUADRATURE['h_i'] e_i = HUE_DATA_FOR_HUE_QUADRATURE['e_i'] H_i = HUE_DATA_FOR_HUE_QUADRATURE['H_i'] # *np.searchsorted* returns an erroneous index if a *nan* is used as input. h[np.asarray(np.isnan(h))] = 0 i = as_int_array(np.searchsorted(h_i, h, side='left') - 1) h_ii = h_i[i] e_ii = e_i[i] H_ii = H_i[i] h_ii1 = h_i[i + 1] e_ii1 = e_i[i + 1] H = H_ii + ((100 * (h - h_ii) / e_ii) / ( (h - h_ii) / e_ii + (h_ii1 - h) / e_ii1)) H = np.where( h < 20.14, 385.9 + (14.1 * h / 0.856) / (h / 0.856 + (20.14 - h) / 0.8), H, ) H = np.where( h >= 237.53, H_ii + ((85.9 * (h - h_ii) / e_ii) / ( (h - h_ii) / e_ii + (360 - h) / 0.856)), H, ) return as_float(H)
def oetf_sRGB(L): """ Defines the *sRGB* colourspace opto-electronic transfer function (OETF / OECF). Parameters ---------- L : numeric or array_like *Luminance* :math:`L` of the image. Returns ------- numeric or ndarray Corresponding electrical signal :math:`V`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`InternationalElectrotechnicalCommission1999a`, :cite:`InternationalTelecommunicationUnion2015i` Examples -------- >>> oetf_sRGB(0.18) # doctest: +ELLIPSIS 0.4613561... """ L = to_domain_1(L) V = np.where(L <= 0.0031308, L * 12.92, 1.055 * spow(L, 1 / 2.4) - 0.055) return as_float(from_range_1(V))
def oetf_SMPTE240M(L_c): """ Defines *SMPTE 240M* opto-electrical transfer function (OETF / OECF). Parameters ---------- L_c : numeric or array_like Light input :math:`L_c` to the reference camera normalised to the system reference white. Returns ------- numeric or ndarray Video signal output :math:`V_c` of the reference camera normalised to the system reference white. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_c`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V_c`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SocietyofMotionPictureandTelevisionEngineers1999b` Examples -------- >>> oetf_SMPTE240M(0.18) # doctest: +ELLIPSIS 0.4022857... """ L_c = to_domain_1(L_c) V_c = np.where(L_c < 0.0228, 4 * L_c, 1.1115 * spow(L_c, 0.45) - 0.1115) return as_float(from_range_1(V_c))
def log_decoding_DJIDLog(y): """ Defines the *DJI D-Log* log decoding curve. Parameters ---------- y : numeric or array_like Non-linear data :math:`t`. Returns ------- numeric or ndarray Linear reflection data :math`x`. References ---------- :cite:`DJI2017` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> log_decoding_DJIDLog(0.3987645561893306) # doctest: +ELLIPSIS 0.1799998... """ y = to_domain_1(y) x = np.where(y <= 0.14, (y - 0.0929) / 6.025, (10 ** (3.89616 * y - 2.27752) - 0.0108) / 0.9892) return as_float(from_range_1(x))
def log_encoding_DJIDLog(x): """ Defines the *DJI D-Log* log encoding curve. Parameters ---------- x : numeric or array_like Linear reflection data :math`x`. Returns ------- numeric or ndarray *DJI D-Log* encoded data :math:`y`. References ---------- :cite:`DJI2017` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> log_encoding_DJIDLog(0.18) # doctest: +ELLIPSIS 0.3987645... """ x = to_domain_1(x) y = np.where(x <= 0.0078, 6.025 * x + 0.0929, (np.log10(x * 0.9892 + 0.0108)) * 0.256663 + 0.584555) return as_float(from_range_1(y))
def xy_to_CCT_Hernandez1999(xy): """ Returns the correlated colour temperature :math:`T_{cp}` from given *CIE XYZ* tristimulus values *xy* chromaticity coordinates using *Hernandez-Andres et al. (1999)* method. Parameters ---------- xy : array_like *xy* chromaticity coordinates. Returns ------- numeric Correlated colour temperature :math:`T_{cp}`. References ---------- :cite:`Hernandez-Andres1999a` Examples -------- >>> xy = np.array([0.31270, 0.32900]) >>> xy_to_CCT_Hernandez1999(xy) # doctest: +ELLIPSIS 6500.7420431... """ x, y = tsplit(xy) n = (x - 0.3366) / (y - 0.1735) CCT = (-949.86315 + 6253.80338 * np.exp(-n / 0.92159) + 28.70599 * np.exp(-n / 0.20039) + 0.00004 * np.exp(-n / 0.07125)) n = np.where(CCT > 50000, (x - 0.3356) / (y - 0.1691), n) CCT = np.where( CCT > 50000, 36284.48953 + 0.00228 * np.exp(-n / 0.07861) + 5.4535e-36 * np.exp(-n / 0.01543), CCT, ) return as_float(CCT)
def __call__(self, x): """ Evaluates the Extrapolator at given point(s). Parameters ---------- x : numeric or array_like Point(s) to evaluate the Extrapolator at. Returns ------- float or ndarray Extrapolated points value(s). """ x = np.atleast_1d(x).astype(self._dtype) xe = as_float(self._evaluate(x)) return xe
def spow(a, p): """ Raises given array :math:`a` to the power :math:`p` as follows: :math:`sign(a) * |a|^p`. This avoids NaNs generation when array :math:`a` is negative and the power :math:`p` is fractional. Parameters ---------------- a : numeric or array_like Array :math:`a`. p : numeric or array_like Power :math:`p`. Returns ------- numeric or ndarray Array :math:`a` safely raised to the power :math:`p`. Examples -------- >>> np.power(-2, 0.15) nan >>> spow(-2, 0.15) # doctest: +ELLIPSIS -1.1095694... >>> spow(0, 0) 0.0 """ if not _SPOW_ENABLED: return np.power(a, p) a = np.atleast_1d(a) p = as_float_array(p) a_p = np.sign(a) * np.abs(a) ** p a_p[np.isnan(a_p)] = 0 return as_float(a_p)
def domain_distance(self, a): """ Returns the euclidean distance between given array and independent domain :math:`x` closest element. Parameters ---------- a : numeric or array_like :math:`a` variable to compute the euclidean distance with independent domain :math:`x` variable. Returns ------- numeric or array_like Euclidean distance between independent domain :math:`x` variable and given :math:`a` variable. """ n = closest(self.domain, a) return as_float(np.abs(a - n))
def log_decoding_SLog( y: FloatingOrArrayLike, bit_depth: Integer = 10, in_normalised_code_value: Boolean = True, out_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Sony S-Log* log decoding curve / electro-optical transfer function. Parameters ---------- y Non-linear *Sony S-Log* data :math:`y`. bit_depth Bit depth used for conversion. in_normalised_code_value Whether the non-linear *Sony S-Log* data :math:`y` is encoded as normalised code values. out_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporation2012a` Examples -------- >>> log_decoding_SLog(0.384970815928670) # doctest: +ELLIPSIS 0.1... """ y = to_domain_1(y) x = legal_to_full(y, bit_depth) if in_normalised_code_value else y with domain_range_scale("ignore"): x = np.where( y >= log_encoding_SLog(0.0, bit_depth, in_normalised_code_value), 10**((x - 0.616596 - 0.03) / 0.432699) - 0.037584, (x - 0.030001222851889303) / 5.0, ) if out_reflection: x = x * 0.9 return as_float(from_range_1(x))
def oetf_RIMMRGB(X, bit_depth=8, out_int=False, E_clip=2.0): """ Defines the *RIMM RGB* encoding opto-electronic transfer function (OETF / OECF). *RIMM RGB* encoding non-linearity is based on that specified by *Recommendation ITU-R BT.709-6*. Parameters ---------- X : numeric or array_like Linear data :math:`X_{RIMM}`. bit_depth : int, optional Bit depth used for conversion. out_int : bool, optional Whether to return value as integer code value or float equivalent of a code value at a given bit depth. E_clip : numeric, optional Maximum exposure level. Returns ------- numeric or ndarray Non-linear data :math:`X'_{RIMM}`. Notes ----- +---------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ +---------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X_p`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ - \\* This definition has an output integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`Spaulding2000b` Examples -------- >>> oetf_RIMMRGB(0.18) # doctest: +ELLIPSIS 0.2916737... >>> oetf_RIMMRGB(0.18, out_int=True) 74 """ X = to_domain_1(X) I_max = 2**bit_depth - 1 V_clip = 1.099 * spow(E_clip, 0.45) - 0.099 q = I_max / V_clip X_p = q * np.select([X < 0.0, X < 0.018, X >= 0.018, X > E_clip], [0, 4.5 * X, 1.099 * spow(X, 0.45) - 0.099, I_max]) if out_int: return as_int(np.round(X_p)) else: return as_float(from_range_1(X_p / I_max))
def log_encoding_VLog(L_in, bit_depth=10, out_legal=True, in_reflection=True, constants=VLOG_CONSTANTS): """ Defines the *Panasonic V-Log* log encoding curve / opto-electronic transfer function. Parameters ---------- L_in : numeric or array_like Linear reflection data :math`L_{in}`. bit_depth : int, optional Bit depth used for conversion. out_legal : bool, optional Whether the non-linear *Panasonic V-Log* data :math:`V_{out}` is encoded in legal range. in_reflection : bool, optional Whether the light level :math`L_{in}` to a camera is reflection. constants : Structure, optional *Panasonic V-Log* constants. Returns ------- numeric or ndarray Non-linear data :math:`V_{out}`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_in`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V_out`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Panasonic2014a` Examples -------- >>> log_encoding_VLog(0.18) # doctest: +ELLIPSIS 0.4233114... """ L_in = to_domain_1(L_in) if not in_reflection: L_in = L_in * 0.9 cut1 = constants.cut1 b = constants.b c = constants.c d = constants.d V_out = np.where( L_in < cut1, 5.6 * L_in + 0.125, c * np.log10(L_in + b) + d, ) V_out = V_out if out_legal else legal_to_full(V_out, bit_depth) return as_float(from_range_1(V_out))
def log_decoding_SLog(y, bit_depth=10, in_legal=True, out_reflection=True): """ Defines the *Sony S-Log* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear *Sony S-Log* data :math:`y`. bit_depth : int, optional Bit depth used for conversion. in_legal : bool, optional Whether the non-linear *Sony S-Log* data :math:`y` is encoded in legal range. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporation2012a` Examples -------- >>> log_decoding_SLog(0.384970815928670) # doctest: +ELLIPSIS 0.1... >>> log_decoding_SLog(0.376512722254600, in_legal=False) ... # doctest: +ELLIPSIS 0.1... >>> log_decoding_SLog(0.370820482371268, out_reflection=False) ... # doctest: +ELLIPSIS 0.1... """ y = to_domain_1(y) x = legal_to_full(y, bit_depth) if in_legal else y with domain_range_scale('ignore'): x = np.where( y >= log_encoding_SLog(0.0, bit_depth, in_legal), 10 ** ((x - 0.616596 - 0.03) / 0.432699) - 0.037584, (x - 0.030001222851889303) / 5.0, ) if out_reflection: x = x * 0.9 return as_float(from_range_1(x))
def log_decoding_SLog3(y, bit_depth=10, in_legal=True, out_reflection=True): """ Defines the *Sony S-Log3* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear *Sony S-Log3* data :math:`y`. bit_depth : int, optional Bit depth used for conversion. in_legal : bool, optional Whether the non-linear *Sony S-Log3* data :math:`y` is encoded in legal range. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporationd` Examples -------- >>> log_decoding_SLog3(0.410557184750733) # doctest: +ELLIPSIS 0.1... >>> log_decoding_SLog3(0.406392694063927, in_legal=False) ... # doctest: +ELLIPSIS 0.1... >>> log_decoding_SLog3(0.399507939606216, out_reflection=False) ... # doctest: +ELLIPSIS 0.1... """ y = to_domain_1(y) y = y if in_legal else full_to_legal(y, bit_depth) x = np.where( y >= 171.2102946929 / 1023, ((10 ** ((y * 1023 - 420) / 261.5)) * (0.18 + 0.01) - 0.01), (y * 1023 - 95) * 0.01125000 / (171.2102946929 - 95), ) if not out_reflection: x = x / 0.9 return as_float(from_range_1(x))
def eotf_inverse_ST2084( C: FloatingOrArrayLike, L_p: Floating = 10000, constants: Structure = CONSTANTS_ST2084, ) -> FloatingOrNDArray: """ Define *SMPTE ST 2084:2014* optimised perceptual inverse electro-optical transfer function (EOTF). Parameters ---------- C Target optical output :math:`C` in :math:`cd/m^2` of the ideal reference display. L_p System peak luminance :math:`cd/m^2`, this parameter should stay at its default :math:`10000 cd/m^2` value for practical applications. It is exposed so that the definition can be used as a fitting function. constants *SMPTE ST 2084:2014* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Color value abbreviated as :math:`N`, that is directly proportional to the encoded signal representation, and which is not directly proportional to the optical output of a display device. Warnings -------- *SMPTE ST 2084:2014* is an absolute transfer function. Notes ----- - *SMPTE ST 2084:2014* is an absolute transfer function, thus the domain and range values for the *Reference* and *1* scales are only indicative that the data is not affected by scale transformations. The effective domain of *SMPTE ST 2084:2014* inverse electro-optical transfer function (EOTF) is [0.0001, 10000]. +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``C`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``N`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ References ---------- :cite:`Miller2014a`, :cite:`SocietyofMotionPictureandTelevisionEngineers2014a` Examples -------- >>> eotf_inverse_ST2084(100) # doctest: +ELLIPSIS 0.5080784... """ C = as_float_array(C) Y_p = spow(C / L_p, constants.m_1) N = spow( (constants.c_1 + constants.c_2 * Y_p) / (constants.c_3 * Y_p + 1), constants.m_2, ) return as_float(N)
def eotf_ROMMRGB(X_p, bit_depth=8, in_int=False): """ Defines the *ROMM RGB* encoding electro-optical transfer function (EOTF / EOCF). Parameters ---------- X_p : numeric or array_like Non-linear data :math:`X'_{ROMM}`. bit_depth : int, optional Bit depth used for conversion. in_int : bool, optional Whether to treat the input value as integer code value or float equivalent of a code value at a given bit depth. Returns ------- numeric or ndarray Linear data :math:`X_{ROMM}`. Notes ----- +---------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X_p`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ +---------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ - \\* This definition has an input integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`ANSI2003a`, :cite:`Spaulding2000b` Examples -------- >>> eotf_ROMMRGB(0.385711424751138) # doctest: +ELLIPSIS 0.1... >>> eotf_ROMMRGB(98, in_int=True) # doctest: +ELLIPSIS 0.1... """ X_p = to_domain_1(X_p) I_max = 2**bit_depth - 1 if not in_int: X_p = X_p * I_max E_t = 16**(1.8 / (1 - 1.8)) X = np.where( X_p < 16 * E_t * I_max, X_p / (16 * I_max), spow(X_p / I_max, 1.8), ) return as_float(from_range_1(X))
def log_decoding_NLog( out_r: FloatingOrArrayLike, bit_depth: Integer = 10, in_normalised_code_value: Boolean = True, out_reflection: Boolean = True, constants: Structure = NLOG_CONSTANTS, ) -> FloatingOrNDArray: """ Define the *Nikon N-Log* log decoding curve / electro-optical transfer function. Parameters ---------- out_r Non-linear data :math:`out`. bit_depth Bit depth used for conversion. in_normalised_code_value Whether the non-linear *Nikon N-Log* data :math:`out` is encoded as normalised code values. out_reflection Whether the light level :math`in` to a camera is reflection. constants *Nikon N-Log* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Linear reflection data :math`in`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``out_r`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``in_r`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Nikon2018` Examples -------- >>> log_decoding_NLog(0.36366777011713869) # doctest: +ELLIPSIS 0.1799999... """ out_r = to_domain_1(out_r) out_r = (out_r if in_normalised_code_value else full_to_legal( out_r, bit_depth)) cut2 = constants.cut2 a = constants.a b = constants.b c = constants.c d = constants.d in_r = np.where( out_r < cut2, spow(out_r / a, 3) - b, np.exp((out_r - d) / c), ) if not out_reflection: in_r = in_r / 0.9 return as_float(from_range_1(in_r))
def luminance_Abebe2017( L: FloatingOrArrayLike, Y_n: FloatingOrArrayLike = 100, method: Union[Literal["Michaelis-Menten", "Stevens"], str] = "Michaelis-Menten", ) -> FloatingOrNDArray: """ Compute *luminance* :math:`Y` of *Lightness* :math:`L` using *Abebe, Pouli, Larabi and Reinhard (2017)* method according to *Michaelis-Menten* kinetics or *Stevens's Power Law*. Parameters ---------- L *Lightness* :math:`L`. Y_n Adapting luminance :math:`Y_n` in :math:`cd/m^2`. method *Luminance* :math:`Y` computation method. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Luminance* :math:`Y` in :math:`cd/m^2`. Notes ----- - *Abebe, Pouli, Larabi and Reinhard (2017)* method uses absolute luminance levels, thus the domain and range values for the *Reference* and *1* scales are only indicative that the data is not affected by scale transformations. +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ | ``Y_n`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ References ---------- :cite:`Abebe2017a` Examples -------- >>> luminance_Abebe2017(0.486955571109229) # doctest: +ELLIPSIS 12.1972253... >>> luminance_Abebe2017(0.474544792145434, method='Stevens') ... # doctest: +ELLIPSIS 12.1972253... """ L = as_float_array(L) Y_n = as_float_array(Y_n) method = validate_method(method, ["Michaelis-Menten", "Stevens"]) if method == "stevens": Y = np.where( Y_n <= 100, spow((L + 0.226) / 1.226, 1 / 0.266), spow((L + 0.127) / 1.127, 1 / 0.230), ) else: Y = np.where( Y_n <= 100, spow( substrate_concentration_MichaelisMenten_Abebe2017( L, 1.448, 0.635, 0.813), 1 / 0.582, ), spow( substrate_concentration_MichaelisMenten_Abebe2017( L, 1.680, 1.584, 0.096), 1 / 0.293, ), ) Y = Y * Y_n return as_float(Y)
def log_encoding_FilmLightTLog(x, w=128.0, g=16.0, o=0.075): """ Defines the *FilmLight T-Log* log encoding curve. Parameters ---------- x : numeric or array_like Linear reflection data :math`x`. w : numeric, optional Value of :math:`x` for :math:`t = 1.0`. g : numeric, optional Gradient at :math:`x = 0.0`. o : numeric, optional Value of :math:`t` for :math:`x = 0.0`. Returns ------- numeric or ndarray *FilmLight T-Log* encoded data :math:`t`. References ---------- :cite:`Siragusano2018a` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - The following is an excerpt from the FilmLight colour space file `./etc/colourspaces/FilmLight_TLog_EGamut.flspace` which can be obtained by installing the free Baselight for Nuke plugin:: T-Log, a cineon driven log tone-curve developed by FilmLight. The colour space is designed to be used as *Working Colour Space*. Version 10.0 This is similar to Cineon LogC function. The formula is... y = A + B*log(x + C) ...where x,y are the log and linear values. A,B,C are constants calculated from... w = x value for y = 1.0 g = the gradient at x=0 o = y value for x = 0.0 We do not have an exact solution but the formula for b gives an approximation. The gradient is not g, but should be within a few percent for most sensible values of (w*g). Examples -------- >>> log_encoding_FilmLightTLog(0.18) # doctest: +ELLIPSIS 0.3965678... """ x = to_domain_1(x) b = 1.0 / (0.7107 + 1.2359 * np.log(w * g)) gs = g / (1.0 - o) C = b / gs a = 1.0 - b * np.log(w + C) y0 = a + b * np.log(C) s = (1.0 - o) / (1.0 - y0) A = 1.0 + (a - 1.0) * s B = b * s G = gs * s t = np.where( x < 0.0, G * x + o, np.log(x + C) * B + A, ) return as_float(from_range_1(t))
def oetf_ROMMRGB(X, bit_depth=8, out_int=False): """ Defines the *ROMM RGB* encoding opto-electronic transfer function (OETF / OECF). Parameters ---------- X : numeric or array_like Linear data :math:`X_{ROMM}`. bit_depth : int, optional Bit depth used for conversion. out_int : bool, optional Whether to return value as integer code value or float equivalent of a code value at a given bit depth. Returns ------- numeric or ndarray Non-linear data :math:`X'_{ROMM}`. Notes ----- +---------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ +---------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X_p`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ - \\* This definition has an output integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`ANSI2003a`, :cite:`Spaulding2000b` Examples -------- >>> oetf_ROMMRGB(0.18) # doctest: +ELLIPSIS 0.3857114... >>> oetf_ROMMRGB(0.18, out_int=True) 98 """ X = to_domain_1(X) I_max = 2**bit_depth - 1 E_t = 16**(1.8 / (1 - 1.8)) X_p = np.where(X < E_t, X * 16 * I_max, spow(X, 1 / 1.8) * I_max) if out_int: return as_int(np.round(X_p)) else: return as_float(from_range_1(X_p / I_max))
def log_decoding_ERIMMRGB(X_p, bit_depth=8, in_int=False, E_min=0.001, E_clip=316.2): """ Defines the *ERIMM RGB* log decoding curve / electro-optical transfer function (EOTF / EOCF). Parameters ---------- X_p : numeric or array_like Non-linear data :math:`X'_{ERIMM}`. bit_depth : int, optional Bit depth used for conversion. in_int : bool, optional Whether to treat the input value as integer code value or float equivalent of a code value at a given bit depth. E_min : numeric, optional Minimum exposure limit. E_clip : numeric, optional Maximum exposure limit. Returns ------- numeric or ndarray Linear data :math:`X_{ERIMM}`. Notes ----- +---------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X_p`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ +---------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ - \\* This definition has an input integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`Spaulding2000b` Examples -------- >>> log_decoding_ERIMMRGB(0.410052389492129) # doctest: +ELLIPSIS 0.1... >>> log_decoding_ERIMMRGB(105, in_int=True) # doctest: +ELLIPSIS 0.1... """ X_p = to_domain_1(X_p) I_max = 2**bit_depth - 1 if not in_int: X_p = X_p * I_max E_t = np.exp(1) * E_min X = np.where( X_p <= I_max * ((np.log(E_t) - np.log(E_min)) / (np.log(E_clip) - np.log(E_min))), ((np.log(E_clip) - np.log(E_min)) / (np.log(E_t) - np.log(E_min))) * ((X_p * E_t) / I_max), np.exp((X_p / I_max) * (np.log(E_clip) - np.log(E_min)) + np.log(E_min)), ) return as_float(from_range_1(X))
def log_encoding_ERIMMRGB(X, bit_depth=8, out_int=False, E_min=0.001, E_clip=316.2): """ Defines the *ERIMM RGB* log encoding curve / opto-electronic transfer function (OETF / OECF). Parameters ---------- X : numeric or array_like Linear data :math:`X_{ERIMM}`. bit_depth : int, optional Bit depth used for conversion. out_int : bool, optional Whether to return value as integer code value or float equivalent of a code value at a given bit depth. E_min : numeric, optional Minimum exposure limit. E_clip : numeric, optional Maximum exposure limit. Returns ------- numeric or ndarray Non-linear data :math:`X'_{ERIMM}`. Notes ----- +---------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ +---------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X_p`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ - \\* This definition has an output integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`Spaulding2000b` Examples -------- >>> log_encoding_ERIMMRGB(0.18) # doctest: +ELLIPSIS 0.4100523... >>> log_encoding_ERIMMRGB(0.18, out_int=True) 105 """ X = to_domain_1(X) I_max = 2**bit_depth - 1 E_t = np.exp(1) * E_min X_p = np.select([ X < 0.0, X <= E_t, X > E_t, X > E_clip, ], [ 0, I_max * ((np.log(E_t) - np.log(E_min)) / (np.log(E_clip) - np.log(E_min))) * (X / E_t), I_max * ((np.log(X) - np.log(E_min)) / (np.log(E_clip) - np.log(E_min))), I_max, ]) if out_int: return as_int(np.round(X_p)) else: return as_float(from_range_1(X_p / I_max))
def eotf_RIMMRGB(X_p, bit_depth=8, in_int=False, E_clip=2.0): """ Defines the *RIMM RGB* encoding electro-optical transfer function (EOTF / EOCF). Parameters ---------- X_p : numeric or array_like Non-linear data :math:`X'_{RIMM}`. bit_depth : int, optional Bit depth used for conversion. in_int : bool, optional Whether to treat the input value as integer code value or float equivalent of a code value at a given bit depth. E_clip : numeric, optional Maximum exposure level. Returns ------- numeric or ndarray Linear data :math:`X_{RIMM}`. Notes ----- +---------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X_p`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ +---------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``X`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ - \\* This definition has an input integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`Spaulding2000b` Examples -------- >>> eotf_RIMMRGB(0.291673732475746) # doctest: +ELLIPSIS 0.1... >>> eotf_RIMMRGB(74, in_int=True) # doctest: +ELLIPSIS 0.1... """ X_p = to_domain_1(X_p) I_max = 2**bit_depth - 1 if not in_int: X_p = X_p * I_max V_clip = 1.099 * spow(E_clip, 0.45) - 0.099 m = V_clip * X_p / I_max with domain_range_scale('ignore'): X = np.where( X_p / I_max < oetf_RIMMRGB(0.018, bit_depth, E_clip=E_clip), m / 4.5, spow((m + 0.099) / 1.099, 1 / 0.45), ) return as_float(from_range_1(X))
def luminance_Fairchild2011( L_hdr: FloatingOrArrayLike, epsilon: FloatingOrArrayLike = 0.474, method: Union[Literal["hdr-CIELAB", "hdr-IPT"], str] = "hdr-CIELAB", ) -> FloatingOrNDArray: """ Compute *luminance* :math:`Y` of given *Lightness* :math:`L_{hdr}` using *Fairchild and Chen (2011)* method according to *Michaelis-Menten* kinetics. Parameters ---------- L_hdr *Lightness* :math:`L_{hdr}`. epsilon :math:`\\epsilon` exponent. method *Lightness* :math:`L_{hdr}` computation method. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Luminance* :math:`Y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_hdr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2011` Examples -------- >>> luminance_Fairchild2011(51.852958445912506) # doctest: +ELLIPSIS 0.1219722... >>> luminance_Fairchild2011(51.643108411718522, method='hdr-IPT') ... # doctest: +ELLIPSIS 0.1219722... """ L_hdr = to_domain_100(L_hdr) method = validate_method(method, ["hdr-CIELAB", "hdr-IPT"]) if method == "hdr-cielab": maximum_perception = 247 else: maximum_perception = 246 Y = np.exp( np.log( substrate_concentration_MichaelisMenten_Michaelis1913( L_hdr - 0.02, maximum_perception, spow(2, epsilon))) / epsilon) return as_float(from_range_1(Y))
def lightness_Fairchild2011( Y: FloatingOrArrayLike, epsilon: FloatingOrArrayLike = 0.474, method: Union[Literal["hdr-CIELAB", "hdr-IPT"], str] = "hdr-CIELAB", ) -> FloatingOrNDArray: """ Compute *Lightness* :math:`L_{hdr}` of given *luminance* :math:`Y` using *Fairchild and Chen (2011)* method according to *Michaelis-Menten* kinetics. Parameters ---------- Y *Luminance* :math:`Y`. epsilon :math:`\\epsilon` exponent. method *Lightness* :math:`L_{hdr}` computation method. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Lightness* :math:`L_{hdr}`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_hdr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2011` Examples -------- >>> lightness_Fairchild2011(12.19722535 / 100) # doctest: +ELLIPSIS 51.8529584... >>> lightness_Fairchild2011(12.19722535 / 100, method='hdr-IPT') ... # doctest: +ELLIPSIS 51.6431084... """ Y = to_domain_1(Y) method = validate_method(method, ["hdr-CIELAB", "hdr-IPT"]) if method == "hdr-cielab": maximum_perception = 247 else: maximum_perception = 246 L_hdr = ( reaction_rate_MichaelisMenten_Michaelis1913( spow(Y, epsilon), maximum_perception, spow(2, epsilon) ) + 0.02 ) return as_float(from_range_100(L_hdr))
def eotf_ST2084( N: FloatingOrArrayLike, L_p: Floating = 10000, constants: Structure = CONSTANTS_ST2084, ) -> FloatingOrNDArray: """ Define *SMPTE ST 2084:2014* optimised perceptual electro-optical transfer function (EOTF). This perceptual quantizer (PQ) has been modeled by Dolby Laboratories using *Barten (1999)* contrast sensitivity function. Parameters ---------- N Color value abbreviated as :math:`N`, that is directly proportional to the encoded signal representation, and which is not directly proportional to the optical output of a display device. L_p System peak luminance :math:`cd/m^2`, this parameter should stay at its default :math:`10000 cd/m^2` value for practical applications. It is exposed so that the definition can be used as a fitting function. constants *SMPTE ST 2084:2014* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Target optical output :math:`C` in :math:`cd/m^2` of the ideal reference display. Warnings -------- *SMPTE ST 2084:2014* is an absolute transfer function. Notes ----- - *SMPTE ST 2084:2014* is an absolute transfer function, thus the domain and range values for the *Reference* and *1* scales are only indicative that the data is not affected by scale transformations. +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``N`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``C`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ References ---------- :cite:`Miller2014a`, :cite:`SocietyofMotionPictureandTelevisionEngineers2014a` Examples -------- >>> eotf_ST2084(0.508078421517399) # doctest: +ELLIPSIS 100.0000000... """ N = as_float_array(N) m_1_d = 1 / constants.m_1 m_2_d = 1 / constants.m_2 V_p = spow(N, m_2_d) n = V_p - constants.c_1 # Limiting negative values. n = np.where(n < 0, 0, n) L = spow((n / (constants.c_2 - constants.c_3 * V_p)), m_1_d) C = L_p * L return as_float(C)
def log_decoding_FilmLightTLog(t, w=128.0, g=16.0, o=0.075): """ Defines the *FilmLight T-Log* log decoding curve. Parameters ---------- t : numeric or array_like Non-linear data :math:`t`. w : numeric, optional Value of :math:`x` for :math:`t = 1.0`. g : numeric, optional Gradient at :math:`x = 0.0`. o : numeric, optional Value of :math:`t` for :math:`x = 0.0`. Returns ------- numeric or ndarray Linear reflection data :math`x`. References ---------- :cite:`Siragusano2018a` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - The following is an excerpt from the FilmLight colour space file `./etc/colourspaces/FilmLight_TLog_EGamut.flspace` which can be obtained by installing the free Baselight for Nuke plugin:: T-Log, a cineon driven log tone-curve developed by FilmLight. The colour space is designed to be used as *Working Colour Space*. Version 10.0 This is similar to Cineon LogC function. The formula is... y = A + B*log(x + C) ...where x,y are the log and linear values. A,B,C are constants calculated from... w = x value for y = 1.0 g = the gradient at x=0 o = y value for x = 0.0 We do not have an exact solution but the formula for b gives an approximation. The gradient is not g, but should be within a few percent for most sensible values of (w*g). Examples -------- >>> log_decoding_FilmLightTLog(0.396567801298332) # doctest: +ELLIPSIS 0.1800000... """ t = to_domain_1(t) b = 1.0 / (0.7107 + 1.2359 * np.log(w * g)) gs = g / (1.0 - o) C = b / gs a = 1.0 - b * np.log(w + C) y0 = a + b * np.log(C) s = (1.0 - o) / (1.0 - y0) A = 1.0 + (a - 1.0) * s B = b * s G = gs * s x = np.where( t < o, (t - o) / G, np.exp((t - A) / B) - C, ) return as_float(from_range_1(x))
def log_encoding_SLog3( x: FloatingOrArrayLike, bit_depth: Integer = 10, out_normalised_code_value: Boolean = True, in_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Sony S-Log3* log encoding curve / opto-electronic transfer function. Parameters ---------- x Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. bit_depth Bit depth used for conversion. out_normalised_code_value Whether the non-linear *Sony S-Log3* data :math:`y` is encoded as normalised code values. in_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Non-linear *Sony S-Log3* data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporationd` Examples -------- >>> log_encoding_SLog3(0.18) # doctest: +ELLIPSIS 0.4105571... The values of *S-Log3 10bit code values (18%, 90%)* table in :cite:`SonyCorporationd` are obtained as follows: >>> x = np.array([0, 18, 90]) / 100 >>> np.around(log_encoding_SLog3(x, 10, False) * 100).astype(np.int) array([ 4, 41, 61]) >>> np.around(log_encoding_SLog3(x) * (2 ** 10 - 1)).astype(np.int) array([ 95, 420, 598]) """ x = to_domain_1(x) if not in_reflection: x = x * 0.9 y = np.where( x >= 0.01125000, (420 + np.log10((x + 0.01) / (0.18 + 0.01)) * 261.5) / 1023, (x * (171.2102946929 - 95) / 0.01125000 + 95) / 1023, ) y_cv = y if out_normalised_code_value else legal_to_full(y, bit_depth) return as_float(from_range_1(y_cv))
def log_encoding_NLog( in_r: FloatingOrArrayLike, bit_depth: Integer = 10, out_normalised_code_value: Boolean = True, in_reflection: Boolean = True, constants: Structure = NLOG_CONSTANTS, ) -> FloatingOrNDArray: """ Define the *Nikon N-Log* log encoding curve / opto-electronic transfer function. Parameters ---------- in_r Linear reflection data :math`in`. bit_depth Bit depth used for conversion. out_normalised_code_value Whether the non-linear *Nikon N-Log* data :math:`out` is encoded as normalised code values. in_reflection Whether the light level :math`in` to a camera is reflection. constants *Nikon N-Log* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Non-linear data :math:`out`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``in_r`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``out_r`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Nikon2018` Examples -------- >>> log_encoding_NLog(0.18) # doctest: +ELLIPSIS 0.3636677... """ in_r = to_domain_1(in_r) if not in_reflection: in_r = in_r * 0.9 cut1 = constants.cut1 a = constants.a b = constants.b c = constants.c d = constants.d out_r = np.where( in_r < cut1, a * spow(in_r + b, 1 / 3), c * np.log(in_r) + d, ) out_r_cv = (out_r if out_normalised_code_value else legal_to_full( out_r, bit_depth)) return as_float(from_range_1(out_r_cv))
def lightness_Abebe2017( Y: FloatingOrArrayLike, Y_n: FloatingOrArrayLike = 100, method: Union[ Literal["Michaelis-Menten", "Stevens"], str ] = "Michaelis-Menten", ) -> FloatingOrNDArray: """ Compute *Lightness* :math:`L` of given *luminance* :math:`Y` using *Abebe, Pouli, Larabi and Reinhard (2017)* method according to *Michaelis-Menten* kinetics or *Stevens's Power Law*. Parameters ---------- Y *Luminance* :math:`Y` in :math:`cd/m^2`. Y_n Adapting luminance :math:`Y_n` in :math:`cd/m^2`. method *Lightness* :math:`L` computation method. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Lightness* :math:`L`. Notes ----- - *Abebe, Pouli, Larabi and Reinhard (2017)* method uses absolute luminance levels, thus the domain and range values for the *Reference* and *1* scales are only indicative that the data is not affected by scale transformations. +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ | ``Y_n`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | ``UN`` | ``UN`` | +------------+-----------------------+---------------+ References ---------- :cite:`Abebe2017a` Examples -------- >>> lightness_Abebe2017(12.19722535) # doctest: +ELLIPSIS 0.4869555... >>> lightness_Abebe2017(12.19722535, method='Stevens') ... # doctest: +ELLIPSIS 0.4745447... """ Y = as_float_array(Y) Y_n = as_float_array(Y_n) method = validate_method(method, ["Michaelis-Menten", "Stevens"]) Y_Y_n = Y / Y_n if method == "stevens": L = np.where( Y_n <= 100, 1.226 * spow(Y_Y_n, 0.266) - 0.226, 1.127 * spow(Y_Y_n, 0.230) - 0.127, ) else: L = np.where( Y_n <= 100, reaction_rate_MichaelisMenten_Abebe2017( spow(Y_Y_n, 0.582), 1.448, 0.635, 0.813 ), reaction_rate_MichaelisMenten_Abebe2017( spow(Y_Y_n, 0.293), 1.680, 1.584, 0.096 ), ) return as_float(L)
def log_encoding_CanonLog(x, bit_depth=10, out_legal=True, in_reflection=True): """ Defines the *Canon Log* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Linear data :math:`x`. bit_depth : int, optional Bit depth used for conversion. out_legal : bool, optional Whether the *Canon Log* non-linear data is encoded in legal range. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray *Canon Log* non-linear data. References ---------- :cite:`Thorpe2012a` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``clog`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> log_encoding_CanonLog(0.18) * 100 # doctest: +ELLIPSIS 34.3389651... """ x = to_domain_1(x) if in_reflection: x = x / 0.9 with domain_range_scale('ignore'): clog = np.where( x < log_decoding_CanonLog(0.0730597, bit_depth, False), -(0.529136 * (np.log10(-x * 10.1596 + 1)) - 0.0730597), 0.529136 * np.log10(10.1596 * x + 1) + 0.0730597, ) clog = full_to_legal(clog, bit_depth) if out_legal else clog return as_float(from_range_1(clog))
def XYZ_to_LLAB( XYZ: ArrayLike, XYZ_0: ArrayLike, Y_b: FloatingOrArrayLike, L: FloatingOrArrayLike, surround: InductionFactors_LLAB = VIEWING_CONDITIONS_LLAB[ "Reference Samples & Images, Average Surround, Subtending < 4"], ) -> CAM_Specification_LLAB: """ Compute the *:math:`LLAB(l:c)`* colour appearance model correlates. Parameters ---------- XYZ *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_0 *CIE XYZ* tristimulus values of reference white. Y_b Luminance factor of the background in :math:`cd/m^2`. L Absolute luminance :math:`L` of reference white in :math:`cd/m^2`. surround Surround viewing conditions induction factors. Returns ------- :class:`colour.CAM_Specification_LLAB` *:math:`LLAB(l:c)`* colour appearance model specification. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_0`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +==============================+=======================+===============+ | ``CAM_Specification_LLAB.h`` | [0, 360] | [0, 1] | +------------------------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2013x`, :cite:`Luo1996b`, :cite:`Luo1996c` Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_0 = np.array([95.05, 100.00, 108.88]) >>> Y_b = 20.0 >>> L = 318.31 >>> surround = VIEWING_CONDITIONS_LLAB['ref_average_4_minus'] >>> XYZ_to_LLAB(XYZ, XYZ_0, Y_b, L, surround) # doctest: +ELLIPSIS CAM_Specification_LLAB(J=37.3668650..., C=0.0089496..., h=270..., \ s=0.0002395..., M=0.0190185..., HC=None, a=..., b=-0.0190185...) """ _X, Y, _Z = tsplit(to_domain_100(XYZ)) RGB = XYZ_to_RGB_LLAB(to_domain_100(XYZ)) RGB_0 = XYZ_to_RGB_LLAB(to_domain_100(XYZ_0)) # Reference illuminant *CIE Standard Illuminant D Series* *D65*. XYZ_0r = np.array([95.05, 100.00, 108.88]) RGB_0r = XYZ_to_RGB_LLAB(XYZ_0r) # Computing chromatic adaptation. XYZ_r = chromatic_adaptation(RGB, RGB_0, RGB_0r, Y, surround.D) # ------------------------------------------------------------------------- # Computing the correlate of *Lightness* :math:`L_L`. # ------------------------------------------------------------------------- # Computing opponent colour dimensions. L_L, a, b = tsplit( opponent_colour_dimensions(XYZ_r, Y_b, surround.F_S, surround.F_L)) # Computing perceptual correlates. # ------------------------------------------------------------------------- # Computing the correlate of *chroma* :math:`Ch_L`. # ------------------------------------------------------------------------- Ch_L = chroma_correlate(a, b) # ------------------------------------------------------------------------- # Computing the correlate of *colourfulness* :math:`C_L`. # ------------------------------------------------------------------------- C_L = colourfulness_correlate(L, L_L, Ch_L, surround.F_C) # ------------------------------------------------------------------------- # Computing the correlate of *saturation* :math:`s_L`. # ------------------------------------------------------------------------- s_L = saturation_correlate(Ch_L, L_L) # ------------------------------------------------------------------------- # Computing the *hue* angle :math:`h_L`. # ------------------------------------------------------------------------- h_L = hue_angle(a, b) # TODO: Implement hue composition computation. # ------------------------------------------------------------------------- # Computing final opponent signals. # ------------------------------------------------------------------------- A_L, B_L = tsplit(final_opponent_signals(C_L, h_L)) return CAM_Specification_LLAB( L_L, Ch_L, as_float(from_range_degrees(h_L)), s_L, C_L, None, A_L, B_L, )
def log_encoding_SLog( x: FloatingOrArrayLike, bit_depth: Integer = 10, out_normalised_code_value: Boolean = True, in_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Sony S-Log* log encoding curve / opto-electronic transfer function. Parameters ---------- x Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. bit_depth Bit depth used for conversion. out_normalised_code_value Whether the non-linear *Sony S-Log* data :math:`y` is encoded as normalised code values. in_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Non-linear *Sony S-Log* data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporation2012a` Examples -------- >>> log_encoding_SLog(0.18) # doctest: +ELLIPSIS 0.3849708... The values of *IRE and CV of S-Log2 @ISO800* table in :cite:`SonyCorporation2012a` are obtained as follows: >>> x = np.array([0, 18, 90]) / 100 >>> np.around(log_encoding_SLog(x, 10, False) * 100).astype(np.int) array([ 3, 38, 65]) >>> np.around(log_encoding_SLog(x) * (2 ** 10 - 1)).astype(np.int) array([ 90, 394, 636]) """ x = to_domain_1(x) if in_reflection: x = x / 0.9 y = np.where( x >= 0, ((0.432699 * np.log10(x + 0.037584) + 0.616596) + 0.03), x * 5 + 0.030001222851889303, ) y_cv = full_to_legal(y, bit_depth) if out_normalised_code_value else y return as_float(from_range_1(y_cv))
def log_encoding_SLog3(x, bit_depth=10, out_legal=True, in_reflection=True): """ Defines the *Sony S-Log3* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. bit_depth : int, optional Bit depth used for conversion. out_legal : bool, optional Whether the non-linear *Sony S-Log3* data :math:`y` is encoded in legal range. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray Non-linear *Sony S-Log3* data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporationd` Examples -------- >>> log_encoding_SLog3(0.18) # doctest: +ELLIPSIS 0.4105571... >>> log_encoding_SLog3(0.18, out_legal=False) # doctest: +ELLIPSIS 0.4063926... >>> log_encoding_SLog3(0.18, in_reflection=False) # doctest: +ELLIPSIS 0.3995079... """ x = to_domain_1(x) if not in_reflection: x = x * 0.9 y = np.where( x >= 0.01125000, (420 + np.log10((x + 0.01) / (0.18 + 0.01)) * 261.5) / 1023, (x * (171.2102946929 - 95) / 0.01125000 + 95) / 1023, ) y = y if out_legal else legal_to_full(y, bit_depth) return as_float(from_range_1(y))
def log_encoding_ACESproxy(lin_AP1, bit_depth=10, out_int=False, constants=ACES_PROXY_CONSTANTS): """ Defines the *ACESproxy* colourspace log encoding curve / opto-electronic transfer function. Parameters ---------- lin_AP1 : numeric or array_like *lin_AP1* value. bit_depth : int, optional **{10, 12}**, *ACESproxy* bit depth. out_int : bool, optional Whether to return value as integer code value or float equivalent of a code value at a given bit depth. constants : Structure, optional *ACESproxy* constants. Returns ------- numeric or ndarray *ACESproxy* non-linear value. Notes ----- +---------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``lin_AP1`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ +---------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +===============+=======================+===============+ | ``ACESproxy`` | [0, 1] | [0, 1] | +---------------+-----------------------+---------------+ - \\* This definition has an output integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, :cite:`TheAcademyofMotionPictureArtsandSciences2014s`, :cite:`TheAcademyofMotionPictureArtsandSciencese` Examples -------- >>> log_encoding_ACESproxy(0.18) # doctest: +ELLIPSIS 0.4164222... >>> log_encoding_ACESproxy(0.18, out_int=True) 426 """ lin_AP1 = to_domain_1(lin_AP1) constants = constants[bit_depth] CV_min = np.resize(constants.CV_min, lin_AP1.shape) CV_max = np.resize(constants.CV_max, lin_AP1.shape) def float_2_cv(x): """ Converts given numeric to code value. """ return np.maximum(CV_min, np.minimum(CV_max, np.round(x))) ACESproxy = np.where( lin_AP1 > 2**-9.72, float_2_cv((np.log2(lin_AP1) + constants.mid_log_offset) * constants.steps_per_stop + constants.mid_CV_offset), np.resize(CV_min, lin_AP1.shape), ) if out_int: return as_int(np.round(ACESproxy)) else: return as_float(from_range_1(ACESproxy / (2**bit_depth - 1)))
def log_encoding_SLog(x, bit_depth=10, out_legal=True, in_reflection=True): """ Defines the *Sony S-Log* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. bit_depth : int, optional Bit depth used for conversion. out_legal : bool, optional Whether the non-linear *Sony S-Log* data :math:`y` is encoded in legal range. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray Non-linear *Sony S-Log* data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporation2012a` Examples -------- >>> log_encoding_SLog(0.18) # doctest: +ELLIPSIS 0.3849708... >>> log_encoding_SLog(0.18, out_legal=False) # doctest: +ELLIPSIS 0.3765127... >>> log_encoding_SLog(0.18, in_reflection=False) # doctest: +ELLIPSIS 0.3708204... """ x = to_domain_1(x) if in_reflection: x = x / 0.9 y = np.where( x >= 0, ((0.432699 * np.log10(x + 0.037584) + 0.616596) + 0.03), x * 5 + 0.030001222851889303, ) y = full_to_legal(y, bit_depth) if out_legal else y return as_float(from_range_1(y))
def log_decoding_ALEXALogC( t: FloatingOrArrayLike, firmware: Union[Literal["SUP 2.x", "SUP 3.x"], str] = "SUP 3.x", method: Union[Literal["Linear Scene Exposure Factor", "Normalised Sensor Signal"], str, ] = "Linear Scene Exposure Factor", EI: Literal[160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600] = 800, ) -> FloatingOrNDArray: """ Define the *ARRI ALEXA Log C* log decoding curve / electro-optical transfer function. Parameters ---------- t *ARRI ALEXA Log C* encoded data :math:`t`. firmware Alexa firmware version. method Conversion method. EI Exposure Index :math:`EI`. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`ARRI2012a` Examples -------- >>> log_decoding_ALEXALogC(0.391006832034084) # doctest: +ELLIPSIS 0.18... """ t = to_domain_1(t) method = validate_method( method, ["Linear Scene Exposure Factor", "Normalised Sensor Signal"]) cut, a, b, c, d, e, f, _e_cut_f = DATA_ALEXA_LOG_C_CURVE_CONVERSION[ firmware][method][EI] x = np.where(t > e * cut + f, (10**((t - d) / c) - b) / a, (t - f) / e) return as_float(from_range_1(x))
def log_decoding_VLog(V_out, bit_depth=10, in_legal=True, out_reflection=True, constants=VLOG_CONSTANTS): """ Defines the *Panasonic V-Log* log decoding curve / electro-optical transfer function. Parameters ---------- V_out : numeric or array_like Non-linear data :math:`V_{out}`. bit_depth : int, optional Bit depth used for conversion. in_legal : bool, optional Whether the non-linear *Panasonic V-Log* data :math:`V_{out}` is encoded in legal range. out_reflection : bool, optional Whether the light level :math`L_{in}` to a camera is reflection. constants : Structure, optional *Panasonic V-Log* constants. Returns ------- numeric or ndarray Linear reflection data :math`L_{in}`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V_out`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_in`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Panasonic2014a` Examples -------- >>> log_decoding_VLog(0.423311448760136) # doctest: +ELLIPSIS 0.1799999... """ V_out = to_domain_1(V_out) V_out = V_out if in_legal else full_to_legal(V_out, bit_depth) cut2 = constants.cut2 b = constants.b c = constants.c d = constants.d L_in = np.where( V_out < cut2, (V_out - 0.125) / 5.6, 10 ** ((V_out - d) / c) - b, ) if not out_reflection: L_in = L_in / 0.9 return as_float(from_range_1(L_in))
def legal_to_full( CV: Union[FloatingOrArrayLike, IntegerOrArrayLike], bit_depth: Integer = 10, in_int: Boolean = False, out_int: Boolean = False, ) -> Union[FloatingOrNDArray, IntegerOrNDArray]: """ Convert given code value :math:`CV` or float equivalent of a code value at a given bit depth from legal range (studio swing) to full range (full swing). Parameters ---------- CV Legal range code value :math:`CV` or float equivalent of a code value at a given bit depth. bit_depth Bit depth used for conversion. in_int Whether to treat the input value as integer code value or float equivalent of a code value at a given bit depth. out_int Whether to return value as integer code value or float equivalent of a code value at a given bit depth. Returns ------- :class:`numpy.floating` or :class:`numpy.integer` or :class:`numpy.ndarray` Full range code value :math:`CV` or float equivalent of a code value at a given bit depth. Examples -------- >>> legal_to_full(64 / 1023) 0.0 >>> legal_to_full(940 / 1023) 1.0 >>> legal_to_full(64 / 1023, out_int=True) 0 >>> legal_to_full(940 / 1023, out_int=True) 1023 >>> legal_to_full(64, in_int=True) 0.0 >>> legal_to_full(940, in_int=True) 1.0 >>> legal_to_full(64, in_int=True, out_int=True) 0 >>> legal_to_full(940, in_int=True, out_int=True) 1023 """ CV = as_float_array(CV) MV = 2**bit_depth - 1 CV_full = as_int_array(np.round(CV)) if in_int else CV * MV B, W = CV_range(bit_depth, True, True) CV_full = (CV_full - B) / (W - B) if out_int: return as_int(np.round(CV_full * MV)) else: return as_float(CV_full)
def ootf_reverse_BT2100_HLG(F_D, L_B=0, L_W=1000, gamma=None): """ Defines *Recommendation ITU-R BT.2100* *Reference HLG* reverse opto-optical transfer function (OOTF / OOCF). Parameters ---------- F_D : numeric or array_like :math:`F_D` is the luminance of a displayed linear component :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`. L_B : numeric, optional :math:`L_B` is the display luminance for black in :math:`cd/m^2`. L_W : numeric, optional :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2` for achromatic pixels. gamma : numeric, optional System gamma value, 1.2 at the nominal display peak luminance of :math:`1000 cd/m^2`. Returns ------- numeric or ndarray :math:`E` is the signal for each colour component :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled by camera exposure. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``F_D`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Borer2017a`, :cite:`InternationalTelecommunicationUnion2016a` Examples -------- >>> ootf_reverse_BT2100_HLG(63.095734448019336) # doctest: +ELLIPSIS 0.1000000... """ F_D = np.atleast_1d(to_domain_1(F_D)) if F_D.shape[-1] != 3: usage_warning( '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses ' 'RGB Luminance in computations and expects a vector input, thus ' 'the given input array will be stacked to compose a vector for ' 'internal computations but a single component will be output.') R_D = G_D = B_D = F_D else: R_D, G_D, B_D = tsplit(F_D) Y_D = np.sum(BT2100_HLG_WEIGHTS * tstack([R_D, G_D, B_D]), axis=-1) alpha = L_W - L_B beta = L_B if gamma is None: gamma = gamma_function_BT2100_HLG(L_W) R_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha) ** ((1 - gamma) / gamma)) * (R_D - beta) / alpha, ) G_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha) ** ((1 - gamma) / gamma)) * (G_D - beta) / alpha, ) B_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha) ** ((1 - gamma) / gamma)) * (B_D - beta) / alpha, ) if F_D.shape[-1] != 3: return as_float(from_range_1(R_S)) else: RGB_S = tstack([R_S, G_S, B_S]) return from_range_1(RGB_S)
def XYZ_to_CAM16( XYZ: ArrayLike, XYZ_w: ArrayLike, L_A: FloatingOrArrayLike, Y_b: FloatingOrArrayLike, surround: Union[ InductionFactors_CIECAM02, InductionFactors_CAM16] = VIEWING_CONDITIONS_CAM16["Average"], discount_illuminant: Boolean = False, ) -> CAM_Specification_CAM16: """ Compute the *CAM16* colour appearance model correlates from given *CIE XYZ* tristimulus values. Parameters ---------- XYZ *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_w *CIE XYZ* tristimulus values of reference white. L_A Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken to be 20% of the luminance of a white object in the scene). Y_b Luminous factor of background :math:`Y_b` such as :math:`Y_b = 100 x L_b / L_w` where :math:`L_w` is the luminance of the light source and :math:`L_b` is the luminance of the background. For viewing images, :math:`Y_b` can be the average :math:`Y` value for the pixels in the entire image, or frequently, a :math:`Y` value of 20, approximate an :math:`L^*` of 50 is used. surround Surround viewing conditions induction factors. discount_illuminant Truth value indicating if the illuminant should be discounted. Returns ------- :class:`colour.CAM_Specification_CAM16` *CAM16* colour appearance model specification. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +-------------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +===============================+=======================+===============+ | ``CAM_Specification_CAM16.J`` | [0, 100] | [0, 1] | +-------------------------------+-----------------------+---------------+ | ``CAM_Specification_CAM16.C`` | [0, 100] | [0, 1] | +-------------------------------+-----------------------+---------------+ | ``CAM_Specification_CAM16.h`` | [0, 360] | [0, 1] | +-------------------------------+-----------------------+---------------+ | ``CAM_Specification_CAM16.s`` | [0, 100] | [0, 1] | +-------------------------------+-----------------------+---------------+ | ``CAM_Specification_CAM16.Q`` | [0, 100] | [0, 1] | +-------------------------------+-----------------------+---------------+ | ``CAM_Specification_CAM16.M`` | [0, 100] | [0, 1] | +-------------------------------+-----------------------+---------------+ | ``CAM_Specification_CAM16.H`` | [0, 400] | [0, 1] | +-------------------------------+-----------------------+---------------+ References ---------- :cite:`Li2017` Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_w = np.array([95.05, 100.00, 108.88]) >>> L_A = 318.31 >>> Y_b = 20.0 >>> surround = VIEWING_CONDITIONS_CAM16['Average'] >>> XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround) # doctest: +ELLIPSIS CAM_Specification_CAM16(J=41.7312079..., C=0.1033557..., \ h=217.0679597..., s=2.3450150..., Q=195.3717089..., M=0.1074367..., \ H=275.5949861..., HC=None) """ XYZ = to_domain_100(XYZ) XYZ_w = to_domain_100(XYZ_w) _X_w, Y_w, _Z_w = tsplit(XYZ_w) L_A = as_float_array(L_A) Y_b = as_float_array(Y_b) # Step 0 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values. RGB_w = vector_dot(MATRIX_16, XYZ_w) # Computing degree of adaptation :math:`D`. D = (np.clip(degree_of_adaptation(surround.F, L_A), 0, 1) if not discount_illuminant else ones(L_A.shape)) n, F_L, N_bb, N_cb, z = viewing_condition_dependent_parameters( Y_b, Y_w, L_A) D_RGB = (D[..., np.newaxis] * Y_w[..., np.newaxis] / RGB_w + 1 - D[..., np.newaxis]) RGB_wc = D_RGB * RGB_w # Applying forward post-adaptation non-linear response compression. RGB_aw = post_adaptation_non_linear_response_compression_forward( RGB_wc, F_L) # Computing achromatic responses for the whitepoint. A_w = achromatic_response_forward(RGB_aw, N_bb) # Step 1 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values. RGB = vector_dot(MATRIX_16, XYZ) # Step 2 RGB_c = D_RGB * RGB # Step 3 # Applying forward post-adaptation non-linear response compression. RGB_a = post_adaptation_non_linear_response_compression_forward(RGB_c, F_L) # Step 4 # Converting to preliminary cartesian coordinates. a, b = tsplit(opponent_colour_dimensions_forward(RGB_a)) # Computing the *hue* angle :math:`h`. h = hue_angle(a, b) # Step 5 # Computing eccentricity factor *e_t*. e_t = eccentricity_factor(h) # Computing hue :math:`h` quadrature :math:`H`. H = hue_quadrature(h) # TODO: Compute hue composition. # Step 6 # Computing achromatic responses for the stimulus. A = achromatic_response_forward(RGB_a, N_bb) # Step 7 # Computing the correlate of *Lightness* :math:`J`. J = lightness_correlate(A, A_w, surround.c, z) # Step 8 # Computing the correlate of *brightness* :math:`Q`. Q = brightness_correlate(surround.c, J, A_w, F_L) # Step 9 # Computing the correlate of *chroma* :math:`C`. C = chroma_correlate(J, n, surround.N_c, N_cb, e_t, a, b, RGB_a) # Computing the correlate of *colourfulness* :math:`M`. M = colourfulness_correlate(C, F_L) # Computing the correlate of *saturation* :math:`s`. s = saturation_correlate(M, Q) return CAM_Specification_CAM16( as_float(from_range_100(J)), as_float(from_range_100(C)), as_float(from_range_degrees(h)), as_float(from_range_100(s)), as_float(from_range_100(Q)), as_float(from_range_100(M)), as_float(from_range_degrees(H, 400)), None, )
def log_encoding_ALEXALogC( x: FloatingOrArrayLike, firmware: Union[Literal["SUP 2.x", "SUP 3.x"], str] = "SUP 3.x", method: Union[Literal["Linear Scene Exposure Factor", "Normalised Sensor Signal"], str, ] = "Linear Scene Exposure Factor", EI: Literal[160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600] = 800, ) -> FloatingOrNDArray: """ Define the *ARRI ALEXA Log C* log encoding curve / opto-electronic transfer function. Parameters ---------- x Linear data :math:`x`. firmware Alexa firmware version. method Conversion method. EI Exposure Index :math:`EI`. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *ARRI ALEXA Log C* encoded data :math:`t`. References ---------- :cite:`ARRI2012a` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> log_encoding_ALEXALogC(0.18) # doctest: +ELLIPSIS 0.3910068... """ x = to_domain_1(x) firmware = validate_method(firmware, ["SUP 3.x", "SUP 2.x"]) method = validate_method( method, ["Linear Scene Exposure Factor", "Normalised Sensor Signal"]) cut, a, b, c, d, e, f, _e_cut_f = DATA_ALEXA_LOG_C_CURVE_CONVERSION[ firmware][method][EI] t = np.where(x > cut, c * np.log10(a * x + b) + d, e * x + f) return as_float(from_range_1(t))
def ootf_reverse_BT2100_HLG(F_D, L_B=0, L_W=1000, gamma=None): """ Defines *Recommendation ITU-R BT.2100* *Reference HLG* reverse opto-optical transfer function (OOTF / OOCF). Parameters ---------- F_D : numeric or array_like :math:`F_D` is the luminance of a displayed linear component :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`. L_B : numeric, optional :math:`L_B` is the display luminance for black in :math:`cd/m^2`. L_W : numeric, optional :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2` for achromatic pixels. gamma : numeric, optional System gamma value, 1.2 at the nominal display peak luminance of :math:`1000 cd/m^2`. Returns ------- numeric or ndarray :math:`E` is the signal for each colour component :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled by camera exposure. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``F_D`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Borer2017a`, :cite:`InternationalTelecommunicationUnion2016a` Examples -------- >>> ootf_reverse_BT2100_HLG(63.095734448019336) # doctest: +ELLIPSIS 0.1000000... """ F_D = np.atleast_1d(to_domain_1(F_D)) if F_D.shape[-1] != 3: usage_warning( '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses ' 'RGB Luminance in computations and expects a vector input, thus ' 'the given input array will be stacked to compose a vector for ' 'internal computations but a single component will be output.') R_D = G_D = B_D = F_D else: R_D, G_D, B_D = tsplit(F_D) Y_D = np.sum(BT2100_HLG_WEIGHTS * tstack([R_D, G_D, B_D]), axis=-1) alpha = L_W - L_B beta = L_B if gamma is None: gamma = gamma_function_BT2100_HLG(L_W) R_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (R_D - beta) / alpha, ) G_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (G_D - beta) / alpha, ) B_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (B_D - beta) / alpha, ) if F_D.shape[-1] != 3: return as_float(from_range_1(R_S)) else: RGB_S = tstack([R_S, G_S, B_S]) return from_range_1(RGB_S)
def eotf_DICOMGSDF(J, in_int=False): """ Defines the *DICOM - Grayscale Standard Display Function* electro-optical transfer function (EOTF / EOCF). Parameters ---------- J : numeric or array_like Just-Noticeable Difference (JND) Index, :math:`j`. in_int : bool, optional Whether to treat the input value as integer code value or float equivalent of a code value at a given bit depth. Returns ------- numeric or ndarray Corresponding *luminance* :math:`L`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``J`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`NationalElectricalManufacturersAssociation2004b` Examples -------- >>> eotf_DICOMGSDF(0.500486263438448) # doctest: +ELLIPSIS 130.0628647... >>> eotf_DICOMGSDF(512, in_int=True) # doctest: +ELLIPSIS 130.0652840... """ J = to_domain_1(J) if not in_int: J = J * 1023 a = DICOMGSDF_CONSTANTS.a b = DICOMGSDF_CONSTANTS.b c = DICOMGSDF_CONSTANTS.c d = DICOMGSDF_CONSTANTS.d e = DICOMGSDF_CONSTANTS.e f = DICOMGSDF_CONSTANTS.f g = DICOMGSDF_CONSTANTS.g h = DICOMGSDF_CONSTANTS.h k = DICOMGSDF_CONSTANTS.k m = DICOMGSDF_CONSTANTS.m J_ln = np.log(J) J_ln2 = J_ln**2 J_ln3 = J_ln**3 J_ln4 = J_ln**4 J_ln5 = J_ln**5 L = ((a + c * J_ln + e * J_ln2 + g * J_ln3 + m * J_ln4) / (1 + b * J_ln + d * J_ln2 + f * J_ln3 + h * J_ln4 + k * J_ln5)) L = 10**L return as_float(from_range_1(L))
def oetf_DICOMGSDF(L, out_int=False): """ Defines the *DICOM - Grayscale Standard Display Function* opto-electronic transfer function (OETF / OECF). Parameters ---------- L : numeric or array_like *Luminance* :math:`L`. out_int : bool, optional Whether to return value as integer code value or float equivalent of a code value at a given bit depth. Returns ------- numeric or ndarray Just-Noticeable Difference (JND) Index, :math:`j`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``J`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`NationalElectricalManufacturersAssociation2004b` Examples -------- >>> oetf_DICOMGSDF(130.0662) # doctest: +ELLIPSIS 0.5004862... >>> oetf_DICOMGSDF(130.0662, out_int=True) 512 """ L = to_domain_1(L) L_lg = np.log10(L) A = DICOMGSDF_CONSTANTS.A B = DICOMGSDF_CONSTANTS.B C = DICOMGSDF_CONSTANTS.C D = DICOMGSDF_CONSTANTS.D E = DICOMGSDF_CONSTANTS.E F = DICOMGSDF_CONSTANTS.F G = DICOMGSDF_CONSTANTS.G H = DICOMGSDF_CONSTANTS.H I = DICOMGSDF_CONSTANTS.I # noqa J = (A + B * L_lg + C * L_lg**2 + D * L_lg**3 + E * L_lg**4 + F * L_lg**5 + G * L_lg**6 + H * L_lg**7 + I * L_lg**8) if out_int: return as_int(np.round(J)) else: return as_float(from_range_1(J / 1023))
def log_decoding_SLog3( y: FloatingOrArrayLike, bit_depth: Integer = 10, in_normalised_code_value: Boolean = True, out_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Sony S-Log3* log decoding curve / electro-optical transfer function. Parameters ---------- y Non-linear *Sony S-Log3* data :math:`y`. bit_depth Bit depth used for conversion. in_normalised_code_value Whether the non-linear *Sony S-Log3* data :math:`y` is encoded as normalised code values. out_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporationd` Examples -------- >>> log_decoding_SLog3(0.410557184750733) # doctest: +ELLIPSIS 0.1... """ y = to_domain_1(y) y = y if in_normalised_code_value else full_to_legal(y, bit_depth) x = np.where( y >= 171.2102946929 / 1023, ((10**((y * 1023 - 420) / 261.5)) * (0.18 + 0.01) - 0.01), (y * 1023 - 95) * 0.01125000 / (171.2102946929 - 95), ) if not out_reflection: x = x / 0.9 return as_float(from_range_1(x))
def full_to_legal( CV: Union[FloatingOrArrayLike, IntegerOrArrayLike], bit_depth: Integer = 10, in_int: Boolean = False, out_int: Boolean = False, ) -> Union[FloatingOrNDArray, IntegerOrNDArray]: """ Convert given code value :math:`CV` or float equivalent of a code value at a given bit depth from full range (full swing) to legal range (studio swing). Parameters ---------- CV Full range code value :math:`CV` or float equivalent of a code value at a given bit depth. bit_depth Bit depth used for conversion. in_int Whether to treat the input value as integer code value or float equivalent of a code value at a given bit depth. out_int Whether to return value as integer code value or float equivalent of a code value at a given bit depth. Returns ------- :class:`numpy.floating` or :class:`numpy.integer` or :class:`numpy.ndarray` Legal range code value :math:`CV` or float equivalent of a code value at a given bit depth. Examples -------- >>> full_to_legal(0.0) # doctest: +ELLIPSIS 0.0625610... >>> full_to_legal(1.0) # doctest: +ELLIPSIS 0.9188660... >>> full_to_legal(0.0, out_int=True) 64 >>> full_to_legal(1.0, out_int=True) 940 >>> full_to_legal(0, in_int=True) # doctest: +ELLIPSIS 0.0625610... >>> full_to_legal(1023, in_int=True) # doctest: +ELLIPSIS 0.9188660... >>> full_to_legal(0, in_int=True, out_int=True) 64 >>> full_to_legal(1023, in_int=True, out_int=True) 940 """ CV = as_float_array(CV) MV = 2**bit_depth - 1 CV_legal = as_int_array(np.round(CV / MV)) if in_int else CV B, W = CV_range(bit_depth, True, True) CV_legal = (W - B) * CV_legal + B if out_int: return as_int(np.round(CV_legal)) else: return as_float(CV_legal / MV)