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 uv_to_Luv( uv, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65'], Y=1): """ Returns the *CIE L\\*u\\*v\\** colourspace array from given :math:`uv^p` chromaticity coordinates by extending the array last dimension with given :math:`L` *Lightness*. Parameters ---------- uv : array_like :math:`uv^p` chromaticity coordinates. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array. Y : numeric, optional Optional :math:`Y` *luminance* value used to construct the intermediate *CIE XYZ* colourspace array, the default :math:`Y` *luminance* value is 1. Returns ------- ndarray *CIE L\\*u\\*v\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Luv`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``u`` : [-100, 100] | ``u`` : [-1, 1] | | | | | | | ``v`` : [-100, 100] | ``v`` : [-1, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004j` Examples -------- >>> uv = np.array([0.37720213, 0.50120264]) >>> uv_to_Luv(uv) # doctest: +ELLIPSIS array([ 100. , 233.1837603..., 42.7474385...]) """ u, v = tsplit(uv) Y = to_domain_1(Y) X = 9 * u / (4 * v) Z = (-5 * Y * v - 3 * u / 4 + 3) / v Y = np.full(u.shape, Y, DEFAULT_FLOAT_DTYPE) return XYZ_to_Luv(from_range_1(tstack([X, Y, Z])), illuminant)
def XYZ_to_xyY( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE XYZ* tristimulus values to *CIE xyY* colourspace and reference *illuminant*. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like, optional Reference *illuminant* chromaticity coordinates. Returns ------- ndarray *CIE xyY* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``xyY`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Lindbloom2003e`, :cite:`Wikipedia2005` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_xyY(XYZ) # doctest: +ELLIPSIS array([ 0.5436955..., 0.3210794..., 0.1219722...]) """ XYZ = to_domain_1(XYZ) X, Y, Z = tsplit(XYZ) xy_w = as_float_array(illuminant) XYZ_n = np.zeros(XYZ.shape) XYZ_n[..., 0:2] = xy_w xyY = np.where( np.all(XYZ == 0, axis=-1)[..., np.newaxis], XYZ_n, tstack([X / (X + Y + Z), Y / (X + Y + Z), from_range_1(Y)]), ) return xyY
def lightness_Fairchild2011(Y, epsilon=0.474, method='hdr-CIELAB'): """ Computes *Lightness* :math:`L_{hdr}` of given *luminance* :math:`Y` using *Fairchild and Chen (2011)* method according to *Michealis-Menten* kinetics. Parameters ---------- Y : array_like *luminance* :math:`Y`. epsilon : numeric or array_like, optional :math:`\\epsilon` exponent. method : unicode, optional **{'hdr-CIELAB', 'hdr-IPT'}**, *Lightness* :math:`L_{hdr}` computation method. Returns ------- array_like *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) if method.lower() == 'hdr-cielab': maximum_perception = 247 else: maximum_perception = 246 L_hdr = reaction_rate_MichealisMenten( spow(Y, epsilon), maximum_perception, 2 ** epsilon) + 0.02 return from_range_100(L_hdr)
def eotf_BT1886(V, L_B=0, L_W=1): """ Defines *Recommendation ITU-R BT.1886* electro-optical transfer function (EOTF / EOCF). Parameters ---------- V : numeric or array_like Input video signal level (normalised, black at :math:`V = 0`, to white at :math:`V = 1`. For content mastered per *Recommendation ITU-R BT.709*, 10-bit digital code values :math:`D` map into values of :math:`V` per the following equation: :math:`V = (D-64)/876` L_B : numeric, optional Screen luminance for black. L_W : numeric, optional Screen luminance for white. Returns ------- numeric or ndarray Screen luminance in :math:`cd/m^2`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`InternationalTelecommunicationUnion2011h` Examples -------- >>> eotf_BT1886(0.409007728864150) # doctest: +ELLIPSIS 0.1169918... """ V = to_domain_1(V) gamma = 2.40 gamma_d = 1 / gamma n = L_W ** gamma_d - L_B ** gamma_d a = n ** gamma b = L_B ** gamma_d / n L = a * np.maximum(V + b, 0) ** gamma return from_range_1(L)
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 log_encoding_PivotedLog(x, log_reference=445, linear_reference=0.18, negative_gamma=0.6, density_per_code_value=0.002): """ Defines the *Josh Pines* style *Pivoted Log* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Linear data :math:`x`. log_reference : numeric or array_like Log reference. linear_reference : numeric or array_like Linear reference. negative_gamma : numeric or array_like Negative gamma. density_per_code_value : numeric or array_like Density per code value. Returns ------- numeric or ndarray Non-linear 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:`SonyImageworks2012a` Examples -------- >>> log_encoding_PivotedLog(0.18) # doctest: +ELLIPSIS 0.4349951... """ x = to_domain_1(x) y = ((log_reference + np.log10(x / linear_reference) / (density_per_code_value / negative_gamma)) / 1023) return from_range_1(y)
def CMY_to_CMYK(CMY): """ Converts from *CMY* colourspace to *CMYK* colourspace. Parameters ---------- CMY : array_like *CMY* colourspace array. Returns ------- ndarray *CMYK* array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``CMY`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``CMYK`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBo` Examples -------- >>> CMY = np.array([0.54379481, 0.96918929, 0.95908048]) >>> CMY_to_CMYK(CMY) # doctest: +ELLIPSIS array([ 0. , 0.9324630..., 0.9103045..., 0.5437948...]) """ C, M, Y = tsplit(to_domain_1(CMY)) K = np.ones(C.shape) K = np.where(C < K, C, K) K = np.where(M < K, M, K) K = np.where(Y < K, Y, K) C = as_float_array((C - K) / (1 - K)) M = as_float_array((M - K) / (1 - K)) Y = as_float_array((Y - K) / (1 - K)) C[np.asarray(K == 1)] = 0 M[np.asarray(K == 1)] = 0 Y[np.asarray(K == 1)] = 0 CMYK = tstack([C, M, Y, K]) return from_range_1(CMYK)
def log_decoding_PivotedLog(y, log_reference=445, linear_reference=0.18, negative_gamma=0.6, density_per_code_value=0.002): """ Defines the *Josh Pines* style *Pivoted Log* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear data :math:`y`. log_reference : numeric or array_like Log reference. linear_reference : numeric or array_like Linear reference. negative_gamma : numeric or array_like Negative gamma. density_per_code_value : numeric or array_like Density per code value. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyImageworks2012a` Examples -------- >>> log_decoding_PivotedLog(0.434995112414467) # doctest: +ELLIPSIS 0.1... """ y = to_domain_1(y) x = (10 ** ((y * 1023 - log_reference) * (density_per_code_value / negative_gamma)) * linear_reference) return from_range_1(x)
def eotf_reverse_BT1886(L, L_B=0, L_W=1): """ Defines *Recommendation ITU-R BT.1886* reverse electro-optical transfer function (EOTF / EOCF). Parameters ---------- L : numeric or array_like Screen luminance in :math:`cd/m^2`. L_B : numeric, optional Screen luminance for black. L_W : numeric, optional Screen luminance for white. Returns ------- numeric or ndarray Input video signal level (normalised, black at :math:`V = 0`, to white at :math:`V = 1`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``V`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`InternationalTelecommunicationUnion2011h` Examples -------- >>> eotf_reverse_BT1886(0.11699185725296059) # doctest: +ELLIPSIS 0.4090077... """ L = to_domain_1(L) gamma = 2.40 gamma_d = 1 / gamma n = L_W ** gamma_d - L_B ** gamma_d a = n ** gamma b = L_B ** gamma_d / n V = (L / a) ** gamma_d - b return from_range_1(V)
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 lightness_Fairchild2010(Y, epsilon=1.836): """ Computes *Lightness* :math:`L_{hdr}` of given *luminance* :math:`Y` using *Fairchild and Wyble (2010)* method according to *Michealis-Menten* kinetics. Parameters ---------- Y : array_like *luminance* :math:`Y`. epsilon : numeric or array_like, optional :math:`\\epsilon` exponent. Returns ------- array_like *Lightness* :math:`L_{hdr}`. Warning ------- The input domain of that definition is non standard! Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_hdr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2010` Examples -------- >>> lightness_Fairchild2010(12.19722535 / 100) # doctest: +ELLIPSIS 31.9963902... """ Y = to_domain_1(Y) maximum_perception = 100 L_hdr = reaction_rate_MichealisMenten( spow(Y, epsilon), maximum_perception, 0.184 ** epsilon) + 0.02 return from_range_100(L_hdr)
def log_encoding_FilmicPro6(t): """ Defines the *FiLMiC Pro 6* log encoding curve / opto-electronic transfer function. Parameters ---------- t : numeric or array_like Linear data :math:`t`. Returns ------- numeric or ndarray Non-linear data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - The *FiLMiC Pro 6* log encoding curve / opto-electronic transfer function is only defined for domain (0, 1]. References ---------- :cite:`FiLMiCInc2017` Warnings -------- The *FiLMiC Pro 6* log encoding curve / opto-electronic transfer function was fitted with poor precision and has :math:`Y=1.000000819999999` value for :math:`t=1`. It also has no linear segment near zero and will thus be undefined for :math:`t=0` when computing its logarithm. Examples -------- >>> log_encoding_FilmicPro6(0.18) # doctest: +ELLIPSIS 0.6066345... """ t = to_domain_1(t) y = 0.371 * (np.sqrt(t) + 0.28257 * np.log(t) + 1.69542) return from_range_1(y)
def eotf_reverse_DCDM(XYZ, out_int=False): """ Defines the *DCDM* reverse electro-optical transfer function (EOTF / EOCF). Parameters ---------- XYZ : numeric or array_like *CIE XYZ* tristimulus values. 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 *CIE XYZ'* tristimulus values. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``XYZ_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:`DigitalCinemaInitiatives2007b` Examples -------- >>> eotf_reverse_DCDM(0.18) # doctest: +ELLIPSIS 0.1128186... >>> eotf_reverse_DCDM(0.18, out_int=True) 462 """ XYZ = to_domain_1(XYZ) XYZ_p = spow(XYZ / 52.37, 1 / 2.6) if out_int: return np.round(4095 * XYZ_p).astype(DEFAULT_INT_DTYPE) else: return from_range_1(XYZ_p)
def eotf_DCDM(XYZ_p, in_int=False): """ Defines the *DCDM* electro-optical transfer function (EOTF / EOCF). Parameters ---------- XYZ_p : numeric or array_like Non-linear *CIE XYZ'* tristimulus values. 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 *CIE XYZ* tristimulus values. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``XYZ_p`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``XYZ`` | [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:`DigitalCinemaInitiatives2007b` Examples -------- >>> eotf_DCDM(0.11281860951766724) # doctest: +ELLIPSIS 0.18... >>> eotf_DCDM(462, in_int=True) # doctest: +ELLIPSIS 0.18... """ XYZ_p = to_domain_1(XYZ_p) if in_int: XYZ_p = XYZ_p / 4095 XYZ = 52.37 * spow(XYZ_p, 2.6) return from_range_1(XYZ)
def XYZ_to_IPT(XYZ): """ Converts from *CIE XYZ* tristimulus values to *IPT* colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- ndarray *IPT* colourspace array. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``IPT`` | ``I`` : [0, 1] | ``I`` : [0, 1] | | | | | | | ``P`` : [-1, 1] | ``P`` : [-1, 1] | | | | | | | ``T`` : [-1, 1] | ``T`` : [-1, 1] | +------------+-----------------------+-----------------+ - Input *CIE XYZ* tristimulus values needs to be adapted for *CIE Standard Illuminant D Series* *D65*. References ---------- :cite:`Fairchild2013y` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_IPT(XYZ) # doctest: +ELLIPSIS array([ 0.3842619..., 0.3848730..., 0.1888683...]) """ XYZ = to_domain_1(XYZ) LMS = dot_vector(IPT_XYZ_TO_LMS_MATRIX, XYZ) LMS_prime = spow(LMS, 0.43) IPT = dot_vector(IPT_LMS_TO_IPT_MATRIX, LMS_prime) return from_range_1(IPT)
def test_to_domain_1(self): """ Tests :func:`colour.utilities.common.to_domain_1` definition. """ with domain_range_scale('Reference'): self.assertEqual(to_domain_1(1), 1) with domain_range_scale('1'): self.assertEqual(to_domain_1(1), 1) with domain_range_scale('100'): self.assertEqual(to_domain_1(1), 0.01) with domain_range_scale('100'): self.assertEqual(to_domain_1(1, np.pi), 1 / np.pi) with domain_range_scale('100'): self.assertEqual( to_domain_1(1, dtype=np.float16).dtype, np.float16)
def log_decoding_FilmicPro6(y): """ Defines the *FiLMiC Pro 6* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear data :math:`y`. Returns ------- numeric or ndarray Linear data :math:`t`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``t`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - The *FiLMiC Pro 6* log decoding curve / electro-optical transfer function is only defined for domain (0, 1]. References ---------- :cite:`FiLMiCInc2017` Warnings -------- The *FiLMiC Pro 6* log encoding curve / opto-electronic transfer function has no inverse in :math:`R`, we thus use a *LUT* based inversion. Examples -------- >>> log_decoding_FilmicPro6(0.6066345199247033) # doctest: +ELLIPSIS 0.1800000... """ y = to_domain_1(y) t = _log_decoding_FilmicPro6_interpolator()(y) return from_range_1(t)
def log_decoding_Log3G10(y, legacy_curve=False): """ Defines the *Log3G10* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear data :math:`y`. legacy_curve : bool, optional Whether to use the v1 *Log3G10* log encoding curve. Default is *False*. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Nattress2016a` Examples -------- >>> log_decoding_Log3G10(1.0 / 3, legacy_curve=True) # doctest: +ELLIPSIS 0.1799994... >>> log_decoding_Log3G10(1.0) # doctest: +ELLIPSIS 184.3223476... """ y = to_domain_1(y) if legacy_curve: x = (np.sign(y) * (10.0 ** (np.abs(y) / 0.222497) - 1) / 169.379333) else: x = (np.sign(y) * (10.0 ** (np.abs(y) / 0.224282) - 1) / 155.975327) - 0.01 return from_range_1(x)
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 IPT_to_XYZ(IPT): """ Converts from *IPT* colourspace to *CIE XYZ* tristimulus values. Parameters ---------- IPT : array_like *IPT* colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``IPT`` | ``I`` : [0, 1] | ``I`` : [0, 1] | | | | | | | ``P`` : [-1, 1] | ``P`` : [-1, 1] | | | | | | | ``T`` : [-1, 1] | ``T`` : [-1, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+-----------------+ References ---------- :cite:`Fairchild2013y` Examples -------- >>> IPT = np.array([0.38426191, 0.38487306, 0.18886838]) >>> IPT_to_XYZ(IPT) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ IPT = to_domain_1(IPT) LMS = dot_vector(IPT_IPT_TO_LMS_MATRIX, IPT) LMS_prime = spow(LMS, 1 / 0.43) XYZ = dot_vector(IPT_LMS_TO_XYZ_MATRIX, LMS_prime) return from_range_1(XYZ)
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 xyY_to_XYZ(xyY): """ Converts from *CIE xyY* colourspace to *CIE XYZ* tristimulus values. Parameters ---------- xyY : array_like *CIE xyY* colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``xyY`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Lindbloom2009d`, :cite:`Wikipedia2005` Examples -------- >>> xyY = np.array([0.54369557, 0.32107944, 0.12197225]) >>> xyY_to_XYZ(xyY) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ x, y, Y = tsplit(xyY) Y = to_domain_1(Y) XYZ = np.where( (y == 0)[..., np.newaxis], tstack([y, y, y]), tstack([x * Y / y, Y, (1 - x - y) * Y / y]), ) return from_range_1(XYZ)
def IPT_hue_angle(IPT): """ Computes the hue angle in degrees from *IPT* colourspace. Parameters ---------- IPT : array_like *IPT* colourspace array. Returns ------- numeric or ndarray Hue angle in degrees. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``IPT`` | ``I`` : [0, 1] | ``I`` : [0, 1] | | | | | | | ``P`` : [-1, 1] | ``P`` : [-1, 1] | | | | | | | ``T`` : [-1, 1] | ``T`` : [-1, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``hue`` | [0, 360] | [0, 1] | +------------+-----------------------+-----------------+ References ---------- :cite:`Fairchild2013y` Examples -------- >>> IPT = np.array([0.96907232, 1, 1.12179215]) >>> IPT_hue_angle(IPT) # doctest: +ELLIPSIS 48.2852074... """ _I, P, T = tsplit(to_domain_1(IPT)) hue = np.degrees(np.arctan2(T, P)) % 360 return from_range_degrees(hue)
def log_encoding_REDLog(x, black_offset=10 ** ((0 - 1023) / 511)): """ Defines the *REDLog* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Linear data :math:`x`. black_offset : numeric or array_like Black offset. Returns ------- numeric or ndarray Non-linear 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:`SonyImageworks2012a` Examples -------- >>> log_encoding_REDLog(0.18) # doctest: +ELLIPSIS 0.6376218... """ x = to_domain_1(x) y = (1023 + 511 * np.log10(x * (1 - black_offset) + black_offset)) / 1023 return from_range_1(y)
def log_decoding_Cineon(y, black_offset=10 ** ((95 - 685) / 300)): """ Defines the *Cineon* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear data :math:`y`. black_offset : numeric or array_like Black offset. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyImageworks2012a` Examples -------- >>> log_decoding_Cineon(0.457319613085418) # doctest: +ELLIPSIS 0.1799999... """ y = to_domain_1(y) x = ((10 ** ((1023 * y - 685) / 300) - black_offset) / (1 - black_offset)) return from_range_1(x)
def log_decoding_REDLog(y, black_offset=10 ** ((0 - 1023) / 511)): """ Defines the *REDLog* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear data :math:`y`. black_offset : numeric or array_like Black offset. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyImageworks2012a` Examples -------- >>> log_decoding_REDLog(0.637621845988175) # doctest: +ELLIPSIS 0.1... """ y = to_domain_1(y) x = ((10 ** ((1023 * y - 1023) / 511)) - black_offset) / (1 - black_offset) return from_range_1(x)
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_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 exponent_hdr_CIELab(Y_s, Y_abs, method='Fairchild 2011'): """ Computes *hdr-CIELAB* colourspace *Lightness* :math:`\\epsilon` exponent using *Fairchild and Wyble (2010)* or *Fairchild and Chen (2011)* method. Parameters ---------- Y_s : numeric or array_like Relative luminance :math:`Y_s` of the surround. Y_abs : numeric or array_like Absolute luminance :math:`Y_{abs}` of the scene diffuse white in :math:`cd/m^2`. method : unicode, optional **{'Fairchild 2011', 'Fairchild 2010'}**, Computation method. Returns ------- array_like *hdr-CIELAB* colourspace *Lightness* :math:`\\epsilon` exponent. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y_s`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> exponent_hdr_CIELab(0.2, 100) # doctest: +ELLIPSIS 0.4738510... >>> exponent_hdr_CIELab(0.2, 100, method='Fairchild 2010') ... # doctest: +ELLIPSIS 1.8360198... """ Y_s = to_domain_1(Y_s) Y_abs = as_float_array(Y_abs) method_l = method.lower() assert method.lower() in [ m.lower() for m in HDR_CIELAB_METHODS ], ('"{0}" method is invalid, must be one of {1}!'.format( method, HDR_CIELAB_METHODS)) if method_l == 'fairchild 2010': epsilon = 1.50 else: epsilon = 0.58 sf = 1.25 - 0.25 * (Y_s / 0.184) lf = np.log(318) / np.log(Y_abs) if method_l == 'fairchild 2010': epsilon *= sf * lf else: epsilon /= sf * lf return epsilon
def log_encoding_VLog(L_in, bit_depth=10, out_normalised_code_value=True, in_reflection=True, constants=VLOG_CONSTANTS, **kwargs): """ 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_normalised_code_value : bool, optional Whether the non-linear *Panasonic V-Log* data :math:`V_{out}` is encoded as normalised code values. in_reflection : bool, optional Whether the light level :math`L_{in}` to a camera is reflection. constants : Structure, optional *Panasonic V-Log* constants. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. 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... The values of *Fig.2.2 V-Log Code Value* table in :cite:`Panasonic2014a` are obtained as follows: >>> L_in = np.array([0, 18, 90]) / 100 >>> np.around(log_encoding_VLog(L_in, 10, False) * 100).astype(np.int) array([ 7, 42, 61]) >>> np.around(log_encoding_VLog(L_in) * (2 ** 10 - 1)).astype(np.int) array([128, 433, 602]) >>> np.around(log_encoding_VLog(L_in) * (2 ** 12 - 1)).astype(np.int) array([ 512, 1733, 2409]) Note that some values in the last column values of *Fig.2.2 V-Log Code Value* table in :cite:`Panasonic2014a` are different by a code: [512, 1732, 2408]. """ out_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['out_legal', 'out_normalised_code_value']], }, **kwargs).get('out_normalised_code_value', out_normalised_code_value) 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_normalised_code_value else legal_to_full( V_out, bit_depth)) return as_float(from_range_1(V_out))
def log_encoding_FLog(in_r, bit_depth=10, out_normalised_code_value=True, in_reflection=True, constants=FLOG_CONSTANTS): """ Defines the *Fujifilm F-Log* log encoding curve / opto-electronic transfer function. Parameters ---------- in_r : numeric or array_like Linear reflection data :math`in`. bit_depth : int, optional Bit depth used for conversion. out_normalised_code_value : bool, optional Whether the non-linear *Fujifilm F-Log* data :math:`out` is encoded as normalised code values. in_reflection : bool, optional Whether the light level :math`in` to a camera is reflection. constants : Structure, optional *Fujifilm F-Log* constants. Returns ------- numeric or 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:`Fujifilm2016` Examples -------- >>> log_encoding_FLog(0.18) # doctest: +ELLIPSIS 0.4593184... The values of *2-2. F-Log Code Value* table in :cite:`Fujifilm2016` are obtained as follows: >>> x = np.array([0, 18, 90]) / 100 >>> np.around(log_encoding_FLog(x, 10, False) * 100, 1) array([ 3.5, 46.3, 73.2]) >>> np.around(log_encoding_FLog(x) * (2 ** 10 - 1)).astype(np.int) array([ 95, 470, 705]) """ 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 e = constants.e f = constants.f out_r = np.where( in_r < cut1, e * in_r + f, c * np.log10(a * in_r + b) + d, ) out_r = (out_r if out_normalised_code_value else legal_to_full( out_r, bit_depth)) return as_float(from_range_1(out_r))
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 YCbCr_to_RGB(YCbCr, K=YCBCR_WEIGHTS['ITU-R BT.709'], in_bits=8, in_legal=True, in_int=False, out_bits=10, out_legal=False, out_int=False, **kwargs): """ Converts an array of *Y'CbCr* colour encoding values to the corresponding *R'G'B'* values array. Parameters ---------- YCbCr : array_like Input *Y'CbCr* colour encoding array of integer or float values. K : array_like, optional Luma weighting coefficients of red and blue. See :attr:`colour.YCBCR_WEIGHTS` for presets. Default is *(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Default is *10*. in_legal : bool, optional Whether to treat the input values as legal range. Default is *False*. in_int : bool, optional Whether to treat the input values as ``in_bits`` integer code values. Default is *False*. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Ignored if ``out_legal`` and ``out_int`` are both *False*. Default is *8*. out_legal : bool, optional Whether to return legal range values. Default is *True*. out_int : bool, optional Whether to return values as ``out_bits`` integer code values. Default is *False*. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as *in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range`` is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using :func:`colour.models.rgb.ycbcr.YCbCr_ranges` definition. out_range : array_like, optional Array overriding the computed range such as *out_range = (RGB_min, RGB_max)*. If ``out_range`` is undefined, *RGB_min* and *RGB_max* will be computed using :func:`colour.CV_range` definition. Returns ------- ndarray *R'G'B'* array of integer or float values. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``YCbCr`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ - \\* This definition has input and output integer switches, thus the domain-range scale information is only given for the floating point mode. Warning ------- For *Recommendation ITU-R BT.2020*, :func:`colour.YCbCr_to_RGB` definition is only applicable to the non-constant luminance implementation. :func:`colour.YcCbcCrc_to_RGB` definition should be used for the constant luminance case as per :cite:`InternationalTelecommunicationUnion2015h`. References ---------- :cite:`InternationalTelecommunicationUnion2011e`, :cite:`InternationalTelecommunicationUnion2015i`, :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`, :cite:`Wikipedia2004d` Examples -------- >>> YCbCr = np.array([502, 512, 512]) >>> YCbCr_to_RGB(YCbCr, in_bits=10, in_legal=True, in_int=True) array([ 0.5, 0.5, 0.5]) """ if in_int: YCbCr = as_float_array(YCbCr) else: YCbCr = to_domain_1(YCbCr) Y, Cb, Cr = tsplit(YCbCr.astype(DEFAULT_FLOAT_DTYPE)) Kr, Kb = K Y_min, Y_max, C_min, C_max = kwargs.get( 'in_range', YCbCr_ranges(in_bits, in_legal, in_int)) RGB_min, RGB_max = kwargs.get('out_range', CV_range(out_bits, out_legal, out_int)) Y -= Y_min Cb -= (C_max + C_min) / 2 Cr -= (C_max + C_min) / 2 Y *= 1 / (Y_max - Y_min) Cb *= 1 / (C_max - C_min) Cr *= 1 / (C_max - C_min) R = Y + (2 - 2 * Kr) * Cr B = Y + (2 - 2 * Kb) * Cb G = (Y - Kr * R - Kb * B) / (1 - Kr - Kb) RGB = tstack([R, G, B]) RGB *= RGB_max - RGB_min RGB += RGB_min RGB = np.round(RGB).astype(DEFAULT_INT_DTYPE) if out_int else from_range_1( RGB) return RGB
def RGB_to_YCbCr(RGB, K=YCBCR_WEIGHTS['ITU-R BT.709'], in_bits=10, in_legal=False, in_int=False, out_bits=8, out_legal=True, out_int=False, **kwargs): """ Converts an array of *R'G'B'* values to the corresponding *Y'CbCr* colour encoding values array. Parameters ---------- RGB : array_like Input *R'G'B'* array of floats or integer values. K : array_like, optional Luma weighting coefficients of red and blue. See :attr:`colour.YCBCR_WEIGHTS` for presets. Default is *(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Default is *10*. in_legal : bool, optional Whether to treat the input values as legal range. Default is *False*. in_int : bool, optional Whether to treat the input values as ``in_bits`` integer code values. Default is *False*. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Ignored if ``out_legal`` and ``out_int`` are both *False*. Default is *8*. out_legal : bool, optional Whether to return legal range values. Default is *True*. out_int : bool, optional Whether to return values as ``out_bits`` integer code values. Default is *False*. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as *in_range = (RGB_min, RGB_max)*. If ``in_range`` is undefined, *RGB_min* and *RGB_max* will be computed using :func:`colour.CV_range` definition. out_range : array_like, optional Array overriding the computed range such as *out_range = (Y_min, Y_max, C_min, C_max)`. If ``out_range`` is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using :func:`colour.models.rgb.ycbcr.YCbCr_ranges` definition. Returns ------- ndarray *Y'CbCr* colour encoding array of integer or float values. Warning ------- For *Recommendation ITU-R BT.2020*, :func:`colour.RGB_to_YCbCr` definition is only applicable to the non-constant luminance implementation. :func:`colour.RGB_to_YcCbcCrc` definition should be used for the constant luminance case as per :cite:`InternationalTelecommunicationUnion2015h`. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``YCbCr`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ - \\* This definition has input and output integer switches, thus the domain-range scale information is only given for the floating point mode. - The default arguments, ``**{'in_bits': 10, 'in_legal': False, 'in_int': False, 'out_bits': 8, 'out_legal': True, 'out_int': False}`` transform a float *R'G'B'* input array normalised to domain [0, 1] (``in_bits`` is ignored) to a float *Y'CbCr* output array where *Y'* is normalised to range [16 / 255, 235 / 255] and *Cb* and *Cr* are normalised to range [16 / 255, 240./255]. The float values are calculated based on an [0, 255] integer range, but no 8-bit quantisation or clamping are performed. References ---------- :cite:`InternationalTelecommunicationUnion2011e`, :cite:`InternationalTelecommunicationUnion2015i`, :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`, :cite:`Wikipedia2004d` Examples -------- >>> RGB = np.array([1.0, 1.0, 1.0]) >>> RGB_to_YCbCr(RGB) # doctest: +ELLIPSIS array([ 0.9215686..., 0.5019607..., 0.5019607...]) Matching float output of The Foundry Nuke's Colorspace node set to YCbCr: >>> RGB_to_YCbCr(RGB, ... out_range=(16 / 255, 235 / 255, 15.5 / 255, 239.5 / 255)) ... # doctest: +ELLIPSIS array([ 0.9215686..., 0.5 , 0.5 ]) Matching float output of The Foundry Nuke's Colorspace node set to YPbPr: >>> RGB_to_YCbCr(RGB, out_legal=False, out_int=False) ... # doctest: +ELLIPSIS array([ 1., 0., 0.]) Creating integer code values as per standard 10-bit SDI: >>> RGB_to_YCbCr(RGB, out_legal=True, out_bits=10, out_int=True) array([940, 512, 512]) For JFIF JPEG conversion as per ITU-T T.871 :cite:`InternationalTelecommunicationUnion2011e`: >>> RGB = np.array([102, 0, 51]) >>> RGB_to_YCbCr(RGB, K=YCBCR_WEIGHTS['ITU-R BT.601'], in_range=(0, 255), ... out_range=(0, 255, 0, 256), out_int=True) array([ 36, 136, 175]) Note the use of 256 for the max *Cb / Cr* value, which is required so that the *Cb* and *Cr* output is centered about 128. Using 255 centres it about 127.5, meaning that there is no integer code value to represent achromatic colours. This does however create the possibility of output integer codes with value of 256, which cannot be stored in 8-bit integer representation. Recommendation ITU-T T.871 specifies these should be clamped to 255. These JFIF JPEG ranges are also obtained as follows: >>> RGB_to_YCbCr(RGB, K=YCBCR_WEIGHTS['ITU-R BT.601'], in_bits=8, ... in_int=True, out_legal=False, out_int=True) array([ 36, 136, 175]) """ if in_int: RGB = as_float_array(RGB) else: RGB = to_domain_1(RGB) Kr, Kb = K RGB_min, RGB_max = kwargs.get('in_range', CV_range(in_bits, in_legal, in_int)) Y_min, Y_max, C_min, C_max = kwargs.get( 'out_range', YCbCr_ranges(out_bits, out_legal, out_int)) RGB_float = RGB.astype(DEFAULT_FLOAT_DTYPE) - RGB_min RGB_float *= 1 / (RGB_max - RGB_min) R, G, B = tsplit(RGB_float) Y = Kr * R + (1 - Kr - Kb) * G + Kb * B Cb = 0.5 * (B - Y) / (1 - Kb) Cr = 0.5 * (R - Y) / (1 - Kr) Y *= Y_max - Y_min Y += Y_min Cb *= C_max - C_min Cr *= C_max - C_min Cb += (C_max + C_min) / 2 Cr += (C_max + C_min) / 2 YCbCr = tstack([Y, Cb, Cr]) YCbCr = np.round(YCbCr).astype( DEFAULT_INT_DTYPE) if out_int else from_range_1(YCbCr) return YCbCr
def log_encoding_CanonLog(x, bit_depth=10, out_normalised_code_value=True, in_reflection=True, **kwargs): """ 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_normalised_code_value : bool, optional Whether the *Canon Log* non-linear data is encoded as normalised code values. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. 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... The values of *Table 2 Canon-Log Code Values* table in :cite:`Thorpe2012a` are obtained as follows: >>> x = np.array([0, 2, 18, 90, 720]) / 100 >>> np.around(log_encoding_CanonLog(x) * (2 ** 10 - 1)).astype(np.int) array([ 128, 169, 351, 614, 1016]) >>> np.around(log_encoding_CanonLog(x, 10, False) * 100, 1) array([ 7.3, 12. , 32.8, 62.7, 108.7]) """ out_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['out_legal', 'out_normalised_code_value']], }, **kwargs).get('out_normalised_code_value', out_normalised_code_value) 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_normalised_code_value else clog) return as_float(from_range_1(clog))
def log_decoding_CanonLog3(clog3, bit_depth=10, in_normalised_code_value=True, out_reflection=True, **kwargs): """ Defines the *Canon Log 3* log decoding curve / electro-optical transfer function. Parameters ---------- clog3 : numeric or array_like *Canon Log 3* non-linear data. bit_depth : int, optional Bit depth used for conversion. in_normalised_code_value : bool, optional Whether the *Canon Log 3* non-linear data is encoded with normalised code values. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``clog3`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Canona` Examples -------- >>> log_decoding_CanonLog3(34.338936938868677 / 100) # doctest: +ELLIPSIS 0.1800000... """ in_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['in_legal', 'in_normalised_code_value']], }, **kwargs).get('in_normalised_code_value', in_normalised_code_value) clog3 = to_domain_1(clog3) clog3 = (legal_to_full(clog3, bit_depth) if in_normalised_code_value else clog3) x = np.select( (clog3 < 0.04076162, clog3 <= 0.105357102, clog3 > 0.105357102), (-(10**((0.07623209 - clog3) / 0.42889912) - 1) / 14.98325, (clog3 - 0.073059361) / 2.3069815, (10**((clog3 - 0.069886632) / 0.42889912) - 1) / 14.98325)) if out_reflection: x = x * 0.9 return as_float(from_range_1(x))
def log_decoding_VLog( V_out: FloatingOrArrayLike, bit_depth: Integer = 10, in_normalised_code_value: Boolean = True, out_reflection: Boolean = True, constants: Structure = CONSTANTS_VLOG, ) -> FloatingOrNDArray: """ Define the *Panasonic V-Log* log decoding curve / electro-optical transfer function. Parameters ---------- V_out Non-linear data :math:`V_{out}`. bit_depth Bit depth used for conversion. in_normalised_code_value Whether the non-linear *Panasonic V-Log* data :math:`V_{out}` is encoded as normalised code values. out_reflection Whether the light level :math`L_{in}` to a camera is reflection. constants *Panasonic V-Log* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.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_normalised_code_value 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 log_decoding_CanonLog2(clog2, bit_depth=10, in_normalised_code_value=True, out_reflection=True, **kwargs): """ Defines the *Canon Log 2* log decoding curve / electro-optical transfer function. Parameters ---------- clog2 : numeric or array_like *Canon Log 2* non-linear data. bit_depth : int, optional Bit depth used for conversion. in_normalised_code_value : bool, optional Whether the *Canon Log 2* non-linear data is encoded with normalised code values. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``clog2`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Canona` Examples -------- >>> log_decoding_CanonLog2(39.825469498316735 / 100) # doctest: +ELLIPSIS 0.1799999... """ in_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['in_legal', 'in_normalised_code_value']], }, **kwargs).get('in_normalised_code_value', in_normalised_code_value) clog2 = to_domain_1(clog2) clog2 = (legal_to_full(clog2, bit_depth) if in_normalised_code_value else clog2) x = np.where( clog2 < 0.035388128, -(10**((0.035388128 - clog2) / 0.281863093) - 1) / 87.09937546, (10**((clog2 - 0.035388128) / 0.281863093) - 1) / 87.09937546, ) if out_reflection: x = x * 0.9 return as_float(from_range_1(x))
def log_encoding_CanonLog2(x, bit_depth=10, out_normalised_code_value=True, in_reflection=True, **kwargs): """ Defines the *Canon Log 2* 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_normalised_code_value : bool, optional Whether the *Canon Log 2* non-linear data is encoded as normalised code values. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. Returns ------- numeric or ndarray *Canon Log 2* non-linear data. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``clog2`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Canona` Examples -------- >>> log_encoding_CanonLog2(0.18) * 100 # doctest: +ELLIPSIS 39.8254694... """ out_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['out_legal', 'out_normalised_code_value']], }, **kwargs).get('out_normalised_code_value', out_normalised_code_value) x = to_domain_1(x) if in_reflection: x = x / 0.9 with domain_range_scale('ignore'): clog2 = np.where( x < log_decoding_CanonLog2(0.035388128, bit_depth, False), -(0.281863093 * (np.log10(-x * 87.09937546 + 1)) - 0.035388128), 0.281863093 * np.log10(x * 87.09937546 + 1) + 0.035388128, ) clog2 = (full_to_legal(clog2, bit_depth) if out_normalised_code_value else clog2) return as_float(from_range_1(clog2))
def log_decoding_CanonLog(clog, bit_depth=10, in_normalised_code_value=True, out_reflection=True, **kwargs): """ Defines the *Canon Log* log decoding curve / electro-optical transfer function. Parameters ---------- clog : numeric or array_like *Canon Log* non-linear data. bit_depth : int, optional Bit depth used for conversion. in_normalised_code_value : bool, optional Whether the *Canon Log* non-linear data is encoded with normalised code values. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. Returns ------- numeric or ndarray Linear data :math:`x`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``clog`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Thorpe2012a` Examples -------- >>> log_decoding_CanonLog(34.338965172606912 / 100) # doctest: +ELLIPSIS 0.17999999... """ in_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['in_legal', 'in_normalised_code_value']], }, **kwargs).get('in_normalised_code_value', in_normalised_code_value) clog = to_domain_1(clog) clog = (legal_to_full(clog, bit_depth) if in_normalised_code_value else clog) x = np.where( clog < 0.0730597, -(10**((0.0730597 - clog) / 0.529136) - 1) / 10.1596, (10**((clog - 0.0730597) / 0.529136) - 1) / 10.1596, ) if out_reflection: x = x * 0.9 return as_float(from_range_1(x))
def RGB_to_YcCbcCrc(RGB, out_bits=10, out_legal=True, out_int=False, is_12_bits_system=False, **kwargs): """ Converts an array of *RGB* linear values to the corresponding *Yc'Cbc'Crc'* colour encoding values array. Parameters ---------- RGB : array_like Input *RGB* array of linear float values. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Ignored if ``out_legal`` and ``out_int`` are both *False*. Default is *10*. out_legal : bool, optional Whether to return legal range values. Default is *True*. out_int : bool, optional Whether to return values as ``out_bits`` integer code values. Default is *False*. is_12_bits_system : bool, optional *Recommendation ITU-R BT.2020* OETF (OECF) adopts different parameters for 10 and 12 bit systems. Default is *False*. Other Parameters ---------------- out_range : array_like, optional Array overriding the computed range such as *out_range = (Y_min, Y_max, C_min, C_max)*. If ``out_range`` is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using :func:`colour.models.rgb.ycbcr.YCbCr_ranges` definition. Returns ------- ndarray *Yc'Cbc'Crc'* colour encoding array of integer or float values. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``YcCbcCrc`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ - \\* This definition has input and output integer switches, thus the domain-range scale information is only given for the floating point mode. Warning ------- This definition is specifically for usage with *Recommendation ITU-R BT.2020* when adopting the constant luminance implementation. References ---------- :cite:`InternationalTelecommunicationUnion2015h`, :cite:`Wikipedia2004d` Examples -------- >>> RGB = np.array([0.18, 0.18, 0.18]) >>> RGB_to_YcCbcCrc(RGB, out_legal=True, out_bits=10, out_int=True, ... is_12_bits_system=False) array([422, 512, 512]) """ R, G, B = tsplit(to_domain_1(RGB)) Y_min, Y_max, C_min, C_max = kwargs.get( 'out_range', YCbCr_ranges(out_bits, out_legal, out_int)) Yc = 0.2627 * R + 0.6780 * G + 0.0593 * B with domain_range_scale('ignore'): Yc = oetf_BT2020(Yc, is_12_bits_system=is_12_bits_system) R = oetf_BT2020(R, is_12_bits_system=is_12_bits_system) B = oetf_BT2020(B, is_12_bits_system=is_12_bits_system) Cbc = np.where((B - Yc) <= 0, (B - Yc) / 1.9404, (B - Yc) / 1.5816) Crc = np.where((R - Yc) <= 0, (R - Yc) / 1.7184, (R - Yc) / 0.9936) Yc *= Y_max - Y_min Yc += Y_min Cbc *= C_max - C_min Crc *= C_max - C_min Cbc += (C_max + C_min) / 2 Crc += (C_max + C_min) / 2 YcCbcCrc = tstack([Yc, Cbc, Crc]) YcCbcCrc = (np.round(YcCbcCrc).astype(DEFAULT_INT_DTYPE) if out_int else from_range_1(YcCbcCrc)) return YcCbcCrc
def XYZ_to_hdr_CIELab( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65'], Y_s=0.2, Y_abs=100, method='Fairchild 2011'): """ Converts from *CIE XYZ* tristimulus values to *hdr-CIELAB* colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array. Y_s : numeric or array_like Relative luminance :math:`Y_s` of the surround. Y_abs : numeric or array_like Absolute luminance :math:`Y_{abs}` of the scene diffuse white in :math:`cd/m^2`. method : unicode, optional **{'Fairchild 2011', 'Fairchild 2010'}**, Computation method. Returns ------- ndarray *hdr-CIELAB* colourspace array. Notes ----- +----------------+-------------------------+---------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=========================+=====================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ | ``Y_s`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ +----------------+-------------------------+---------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=========================+=====================+ | ``Lab_hdr`` | ``L_hdr`` : [0, 100] | ``L_hdr`` : [0, 1] | | | | | | | ``a_hdr`` : [-100, 100] | ``a_hdr`` : [-1, 1] | | | | | | | ``b_hdr`` : [-100, 100] | ``b_hdr`` : [-1, 1] | +----------------+-------------------------+---------------------+ - Conversion to polar coordinates to compute the *chroma* :math:`C_{hdr}` and *hue* :math:`h_{hdr}` correlates can be safely performed with :func:`colour.Lab_to_LCHab` definition. - Conversion to cartesian coordinates from the *Lightness* :math:`L_{hdr}`, *chroma* :math:`C_{hdr}` and *hue* :math:`h_{hdr}` correlates can be safely performed with :func:`colour.LCHab_to_Lab` definition. References ---------- :cite:`Fairchild2010`, :cite:`Fairchild2011` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_hdr_CIELab(XYZ) # doctest: +ELLIPSIS array([ 51.8700206..., 60.4763385..., 32.1455191...]) >>> XYZ_to_hdr_CIELab(XYZ, method='Fairchild 2010') # doctest: +ELLIPSIS array([ 31.9962111..., 128.0076303..., 48.7695230...]) """ X, Y, Z = tsplit(to_domain_1(XYZ)) X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) method_l = method.lower() assert method.lower() in [ m.lower() for m in HDR_CIELAB_METHODS ], ('"{0}" method is invalid, must be one of {1}!'.format( method, HDR_CIELAB_METHODS)) if method_l == 'fairchild 2010': lightness_callable = lightness_Fairchild2010 else: lightness_callable = lightness_Fairchild2011 e = exponent_hdr_CIELab(Y_s, Y_abs, method) # Domain and range scaling has already be handled. with domain_range_scale('ignore'): L_hdr = lightness_callable(Y / Y_n, e) a_hdr = 5 * (lightness_callable(X / X_n, e) - L_hdr) b_hdr = 2 * (L_hdr - lightness_callable(Z / Z_n, e)) Lab_hdr = tstack([L_hdr, a_hdr, b_hdr]) return from_range_100(Lab_hdr)
def YcCbcCrc_to_RGB(YcCbcCrc, in_bits=10, in_legal=True, in_int=False, is_12_bits_system=False, **kwargs): """ Converts an array of *Yc'Cbc'Crc'* colour encoding values to the corresponding *RGB* array of linear values. Parameters ---------- YcCbcCrc : array_like Input *Yc'Cbc'Crc'* colour encoding array of linear float values. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Default is *10*. in_legal : bool, optional Whether to treat the input values as legal range. Default is *False*. in_int : bool, optional Whether to treat the input values as ``in_bits`` integer code values. Default is *False*. is_12_bits_system : bool, optional *Recommendation ITU-R BT.2020* EOTF (EOCF) adopts different parameters for 10 and 12 bit systems. Default is *False*. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as *in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range`` is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using :func:`colour.models.rgb.ycbcr.YCbCr_ranges` definition. Returns ------- ndarray *RGB* array of linear float values. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``YcCbcCrc`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ - \\* This definition has input and output integer switches, thus the domain-range scale information is only given for the floating point mode. Warning ------- This definition is specifically for usage with *Recommendation ITU-R BT.2020* when adopting the constant luminance implementation. References ---------- :cite:`InternationalTelecommunicationUnion2015h`, :cite:`Wikipedia2004d` Examples -------- >>> YcCbcCrc = np.array([1689, 2048, 2048]) >>> YcCbcCrc_to_RGB(YcCbcCrc, in_legal=True, in_bits=12, in_int=True, ... is_12_bits_system=True) ... # doctest: +ELLIPSIS array([ 0.1800903..., 0.1800903..., 0.1800903...]) """ if in_int: YcCbcCrc = as_float_array(YcCbcCrc) else: YcCbcCrc = to_domain_1(YcCbcCrc) Yc, Cbc, Crc = tsplit(YcCbcCrc.astype(DEFAULT_FLOAT_DTYPE)) Y_min, Y_max, C_min, C_max = kwargs.get( 'in_range', YCbCr_ranges(in_bits, in_legal, in_int)) Yc -= Y_min Cbc -= (C_max + C_min) / 2 Crc -= (C_max + C_min) / 2 Yc *= 1 / (Y_max - Y_min) Cbc *= 1 / (C_max - C_min) Crc *= 1 / (C_max - C_min) B = np.where(Cbc <= 0, Cbc * 1.9404 + Yc, Cbc * 1.5816 + Yc) R = np.where(Crc <= 0, Crc * 1.7184 + Yc, Crc * 0.9936 + Yc) with domain_range_scale('ignore'): Yc = eotf_BT2020(Yc, is_12_bits_system=is_12_bits_system) B = eotf_BT2020(B, is_12_bits_system=is_12_bits_system) R = eotf_BT2020(R, is_12_bits_system=is_12_bits_system) G = (Yc - 0.0593 * B - 0.2627 * R) / 0.6780 RGB = tstack([R, G, B]) return from_range_1(RGB)
def log_decoding_FLog(out_r, bit_depth=10, in_normalised_code_value=True, out_reflection=True, constants=FLOG_CONSTANTS): """ Defines the *Fujifilm F-Log* log decoding curve / electro-optical transfer function. Parameters ---------- out_r : numeric or array_like Non-linear data :math:`out`. bit_depth : int, optional Bit depth used for conversion. in_normalised_code_value : bool, optional Whether the non-linear *Fujifilm F-Log* data :math:`out` is encoded as normalised code values. out_reflection : bool, optional Whether the light level :math`in` to a camera is reflection. constants : Structure, optional *Fujifilm F-Log* constants. Returns ------- numeric or 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:`Fujifilm2016` Examples -------- >>> log_decoding_FLog(0.45931845866162124) # doctest: +ELLIPSIS 0.1800000... """ 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 e = constants.e f = constants.f in_r = np.where( out_r < cut2, (out_r - f) / e, (10**((out_r - d) / c)) / a - b / a, ) if not out_reflection: in_r = in_r / 0.9 return as_float(from_range_1(in_r))
def oetf_inverse_ARIBSTDB67( E_p: FloatingOrArrayLike, r: FloatingOrArrayLike = 0.5, constants: Structure = CONSTANTS_ARIBSTDB67, ) -> FloatingOrNDArray: """ Define *ARIB STD-B67 (Hybrid Log-Gamma)* inverse opto-electrical transfer function (OETF). Parameters ---------- E_p Non-linear signal :math:`E'`. r Video level corresponding to reference white level. constants *ARIB STD-B67 (Hybrid Log-Gamma)* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.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] | +------------+-----------------------+---------------+ - This definition uses the *mirror* negative number handling mode of :func:`colour.models.gamma_function` definition to the sign of negative numbers. References ---------- :cite:`AssociationofRadioIndustriesandBusinesses2015a` Examples -------- >>> oetf_inverse_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), gamma_function((E_p / r), 2, "mirror"), np.exp((E_p - c) / a) + b, ) return as_float(from_range_1(E))
def log_decoding_VLog(V_out, bit_depth=10, in_normalised_code_value=True, out_reflection=True, constants=VLOG_CONSTANTS, **kwargs): """ 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_normalised_code_value : bool, optional Whether the non-linear *Panasonic V-Log* data :math:`V_{out}` is encoded as normalised code values. out_reflection : bool, optional Whether the light level :math`L_{in}` to a camera is reflection. constants : Structure, optional *Panasonic V-Log* constants. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. 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... """ in_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['in_legal', 'in_normalised_code_value']], }, **kwargs).get('in_normalised_code_value', in_normalised_code_value) V_out = to_domain_1(V_out) V_out = (V_out if in_normalised_code_value 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 oetf_ARIBSTDB67( E: FloatingOrArrayLike, r: FloatingOrArrayLike = 0.5, constants: Structure = CONSTANTS_ARIBSTDB67, ) -> FloatingOrNDArray: """ Define *ARIB STD-B67 (Hybrid Log-Gamma)* opto-electrical transfer function (OETF). Parameters ---------- E 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 Video level corresponding to reference white level. constants *ARIB STD-B67 (Hybrid Log-Gamma)* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.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] | +------------+-----------------------+---------------+ - This definition uses the *mirror* negative number handling mode of :func:`colour.models.gamma_function` definition to the sign of negative numbers. References ---------- :cite:`AssociationofRadioIndustriesandBusinesses2015a` Examples -------- >>> oetf_ARIBSTDB67(0.18) # doctest: +ELLIPSIS 0.2121320... """ E = to_domain_1(E) r = as_float_array(r) a = constants.a b = constants.b c = constants.c E_p = np.where(E <= 1, r * gamma_function(E, 0.5, "mirror"), a * np.log(E - b) + c) return as_float(from_range_1(E_p))
def cctf_encoding_ROMMRGB(X, bit_depth=8, out_int=False): """ Defines the *ROMM RGB* encoding colour component transfer function (Encoding CCTF). 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 -------- >>> cctf_encoding_ROMMRGB(0.18) # doctest: +ELLIPSIS 0.3857114... >>> cctf_encoding_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_encoding_Log3G10(x, legacy_curve=False): """ Defines the *Log3G10* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Linear data :math:`x`. legacy_curve : bool, optional Whether to use the v1 *Log3G10* log encoding curve. Default is *False*. Returns ------- numeric or ndarray Non-linear data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - The v1 *Log3G10* log encoding curve is the one used in *REDCINE-X beta 42*. *Resolve 12.5.2* also uses the v1 curve. *RED* is planning to use v2 *Log3G10* log encoding curve in the release version of the *RED SDK*. Use the `legacy_curve=True` argument to switch to the v1 curve for compatibility with the current (as of September 21, 2016) *RED SDK*. - The intent of the v1 *Log3G10* log encoding curve is that zero maps to zero, 0.18 maps to 1/3, and 10 stops above 0.18 maps to 1.0. The name indicates this in a similar way to the naming conventions of *Sony HyperGamma* curves. The constants used in the functions do not in fact quite hit these values, but rather than use corrected constants, the functions here use the official *RED* values, in order to match the output of the *RED SDK*. For those interested, solving for constants which exactly hit 1/3 and 1.0 yields the following values:: B = 25 * (np.sqrt(4093.0) - 3) / 9 A = 1 / np.log10(B * 184.32 + 1) where the function takes the form:: Log3G10(x) = A * np.log10(B * x + 1) Similarly for *Log3G12*, the values which hit exactly 1/3 and 1.0 are:: B = 25 * (np.sqrt(16381.0) - 3) / 9 A = 1 / np.log10(B * 737.28 + 1) References ---------- :cite:`Nattress2016a` Examples -------- >>> log_encoding_Log3G10(0.18, legacy_curve=True) # doctest: +ELLIPSIS 0.3333336... >>> log_encoding_Log3G10(0.0) # doctest: +ELLIPSIS 0.0915514... """ x = to_domain_1(x) if legacy_curve: y = np.sign(x) * 0.222497 * np.log10((np.abs(x) * 169.379333) + 1) else: y = (np.sign(x + 0.01) * 0.224282 * np.log10((np.abs(x + 0.01) * 155.975327) + 1)) return from_range_1(y)
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 XYZ_to_Lab( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE XYZ* tristimulus values to *CIE L\\*a\\*b\\** colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- ndarray *CIE L\\*a\\*b\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Lab`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``a`` : [-100, 100] | ``a`` : [-1, 1] | | | | | | | ``b`` : [-100, 100] | ``b`` : [-1, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_Lab(XYZ) # doctest: +ELLIPSIS array([ 41.5278752..., 52.6385830..., 26.9231792...]) """ X, Y, Z = tsplit(to_domain_1(XYZ)) X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) f_X_X_n = intermediate_lightness_function_CIE1976(X, X_n) f_Y_Y_n = intermediate_lightness_function_CIE1976(Y, Y_n) f_Z_Z_n = intermediate_lightness_function_CIE1976(Z, Z_n) L = 116 * f_Y_Y_n - 16 a = 500 * (f_X_X_n - f_Y_Y_n) b = 200 * (f_Y_Y_n - f_Z_Z_n) Lab = tstack([L, a, b]) return from_range_100(Lab)
def cctf_decoding_RIMMRGB(X_p, bit_depth=8, in_int=False, E_clip=2.0): """ Defines the *RIMM RGB* decoding colour component transfer function (Encoding CCTF). 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 -------- >>> cctf_decoding_RIMMRGB(0.291673732475746) # doctest: +ELLIPSIS 0.1... >>> cctf_decoding_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 < cctf_encoding_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 JzAzBz_to_XYZ(JzAzBz, constants=JZAZBZ_CONSTANTS): """ Converts from :math:`J_zA_zB_z` colourspace to *CIE XYZ* tristimulus values. Parameters ---------- JzAzBz : array_like :math:`J_zA_zB_z` colourspace array where :math:`J_z` is Lightness, :math:`A_z` is redness-greenness and :math:`B_z` is yellowness-blueness. constants : Structure, optional :math:`J_zA_zB_z` colourspace constants. Returns ------- ndarray *CIE XYZ* tristimulus values under *CIE Standard Illuminant D Series D65*. Notes ----- +------------+-----------------------+------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+==================+ | ``JzAzBz`` | ``Jz`` : [0, 1] | ``Jz`` : [0, 1] | | | | | | | ``Az`` : [-1, 1] | ``Az`` : [-1, 1] | | | | | | | ``Bz`` : [-1, 1] | ``Bz`` : [-1, 1] | +------------+-----------------------+------------------+ +------------+-----------------------+------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+==================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+------------------+ References ---------- :cite:`Safdar2017` Examples -------- >>> JzAzBz = np.array([0.00535048, 0.00924302, 0.00526007]) >>> JzAzBz_to_XYZ(JzAzBz) # doctest: +ELLIPSIS array([ 0.2065402..., 0.1219723..., 0.0513696...]) """ J_z, A_z, B_z = tsplit(to_domain_1(JzAzBz)) I_z = ((J_z + constants.d_0) / (1 + constants.d - constants.d * (J_z + constants.d_0))) LMS_p = dot_vector(JZAZBZ_IZAZBZ_TO_LMS_P_MATRIX, tstack([I_z, A_z, B_z])) with domain_range_scale('ignore'): LMS = eotf_ST2084(LMS_p, 10000, constants) X_p_D65, Y_p_D65, Z_p_D65 = tsplit( dot_vector(JZAZBZ_LMS_TO_XYZ_MATRIX, LMS)) X_D65 = (X_p_D65 + (constants.b - 1) * Z_p_D65) / constants.b Y_D65 = (Y_p_D65 + (constants.g - 1) * X_D65) / constants.g XYZ_D65 = tstack([X_D65, Y_D65, Z_p_D65]) return from_range_1(XYZ_D65)
def XYZ_to_JzAzBz(XYZ_D65, constants=JZAZBZ_CONSTANTS): """ Converts from *CIE XYZ* tristimulus values to :math:`J_zA_zB_z` colourspace. Parameters ---------- XYZ_D65 : array_like *CIE XYZ* tristimulus values under *CIE Standard Illuminant D Series D65*. constants : Structure, optional :math:`J_zA_zB_z` colourspace constants. Returns ------- ndarray :math:`J_zA_zB_z` colourspace array where :math:`J_z` is Lightness, :math:`A_z` is redness-greenness and :math:`B_z` is yellowness-blueness. Notes ----- +------------+-----------------------+------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+==================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+------------------+ +------------+-----------------------+------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+==================+ | ``JzAzBz`` | ``Jz`` : [0, 1] | ``Jz`` : [0, 1] | | | | | | | ``Az`` : [-1, 1] | ``Az`` : [-1, 1] | | | | | | | ``Bz`` : [-1, 1] | ``Bz`` : [-1, 1] | +------------+-----------------------+------------------+ References ---------- :cite:`Safdar2017` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_JzAzBz(XYZ) # doctest: +ELLIPSIS array([ 0.0053504..., 0.0092430..., 0.0052600...]) """ X_D65, Y_D65, Z_D65 = tsplit(to_domain_1(XYZ_D65)) X_p_D65 = constants.b * X_D65 - (constants.b - 1) * Z_D65 Y_p_D65 = constants.g * Y_D65 - (constants.g - 1) * X_D65 XYZ_p_D65 = tstack([X_p_D65, Y_p_D65, Z_D65]) LMS = dot_vector(JZAZBZ_XYZ_TO_LMS_MATRIX, XYZ_p_D65) with domain_range_scale('ignore'): LMS_p = oetf_ST2084(LMS, 10000, constants) I_z, A_z, B_z = tsplit(dot_vector(JZAZBZ_LMS_P_TO_IZAZBZ_MATRIX, LMS_p)) J_z = ((1 + constants.d) * I_z) / (1 + constants.d * I_z) - constants.d_0 JzAzBz = tstack([J_z, A_z, B_z]) return from_range_1(JzAzBz)
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 log_encoding_CanonLog3(x, bit_depth=10, out_normalised_code_value=True, in_reflection=True, **kwargs): """ Defines the *Canon Log 3* 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_normalised_code_value : bool, optional Whether the *Canon Log 3* non-linear data is encoded as normalised code values. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. Returns ------- numeric or ndarray *Canon Log 3* non-linear data. Notes ----- - Introspection of the grafting points by Shaw, N. (2018) shows that the *Canon Log 3* IDT was likely derived from its encoding curve as the later is grafted at *+/-0.014*:: >>> clog3 = 0.04076162 >>> (clog3 - 0.073059361) / 2.3069815 -0.014000000000000002 >>> clog3 = 0.105357102 >>> (clog3 - 0.073059361) / 2.3069815 0.013999999999999997 Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``clog3`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Canona` Examples -------- >>> log_encoding_CanonLog3(0.18) * 100 # doctest: +ELLIPSIS 34.3389369... """ out_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['out_legal', 'out_normalised_code_value']], }, **kwargs).get('out_normalised_code_value', out_normalised_code_value) x = to_domain_1(x) if in_reflection: x = x / 0.9 with domain_range_scale('ignore'): clog3 = np.select( (x < log_decoding_CanonLog3(0.04076162, bit_depth, False, False), x <= log_decoding_CanonLog3(0.105357102, bit_depth, False, False), x > log_decoding_CanonLog3(0.105357102, bit_depth, False, False)), (-0.42889912 * np.log10(-x * 14.98325 + 1) + 0.07623209, 2.3069815 * x + 0.073059361, 0.42889912 * np.log10(x * 14.98325 + 1) + 0.069886632)) clog3 = (full_to_legal(clog3, bit_depth) if out_normalised_code_value else clog3) return as_float(from_range_1(clog3))
def log_encoding_VLog( L_in: FloatingOrArrayLike, bit_depth: Integer = 10, out_normalised_code_value: Boolean = True, in_reflection: Boolean = True, constants: Structure = CONSTANTS_VLOG, ) -> FloatingOrNDArray: """ Define the *Panasonic V-Log* log encoding curve / opto-electronic transfer function. Parameters ---------- L_in Linear reflection data :math`L_{in}`. bit_depth Bit depth used for conversion. out_normalised_code_value Whether the non-linear *Panasonic V-Log* data :math:`V_{out}` is encoded as normalised code values. in_reflection Whether the light level :math`L_{in}` to a camera is reflection. constants *Panasonic V-Log* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.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... The values of *Fig.2.2 V-Log Code Value* table in :cite:`Panasonic2014a` are obtained as follows: >>> L_in = np.array([0, 18, 90]) / 100 >>> np.around(log_encoding_VLog(L_in, 10, False) * 100).astype(np.int) array([ 7, 42, 61]) >>> np.around(log_encoding_VLog(L_in) * (2 ** 10 - 1)).astype(np.int) array([128, 433, 602]) >>> np.around(log_encoding_VLog(L_in) * (2 ** 12 - 1)).astype(np.int) array([ 512, 1733, 2409]) Note that some values in the last column values of *Fig.2.2 V-Log Code Value* table in :cite:`Panasonic2014a` are different by a code: [512, 1732, 2408]. """ 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_cv = (V_out if out_normalised_code_value else legal_to_full( V_out, bit_depth)) return as_float(from_range_1(V_out_cv))