def test_from_range_1(self): """ Tests :func:`colour.utilities.common.from_range_1` definition. """ with domain_range_scale('Reference'): self.assertEqual(from_range_1(1), 1) with domain_range_scale('1'): self.assertEqual(from_range_1(1), 1) with domain_range_scale('100'): self.assertEqual(from_range_1(1), 100) with domain_range_scale('100'): self.assertEqual(from_range_1(1, np.pi), 1 * np.pi)
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 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 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 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 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 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_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 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 luminance_Fairchild2010(L_hdr, epsilon=1.836): """ Computes *luminance* :math:`Y` of given *Lightness* :math:`L_{hdr}` using *Fairchild and Wyble (2010)* method according to *Michealis-Menten* kinetics. Parameters ---------- L_hdr : array_like *Lightness* :math:`L_{hdr}`. epsilon : numeric or array_like, optional :math:`\\epsilon` exponent. Returns ------- array_like *luminance* :math:`Y`. Warning ------- The output range of that definition is non standard! Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_hdr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2010` Examples -------- >>> luminance_Fairchild2010(31.996390226262736, 1.836) ... # doctest: +ELLIPSIS 0.1219722... """ L_hdr = to_domain_100(L_hdr) Y = np.exp( np.log( substrate_concentration_MichealisMenten(L_hdr - 0.02, 100, 0.184 ** epsilon)) / epsilon) 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 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 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 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 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 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 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 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 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 HEX_to_RGB(HEX): """ Converts from hexadecimal triplet representation to *RGB* colourspace. Parameters ---------- HEX : unicode or array_like Hexadecimal triplet representation. Returns ------- ndarray *RGB* colourspace array. Notes ----- Notes ----- +-----------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +===========+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +-----------+-----------------------+---------------+ Examples -------- >>> HEX = '#aaddff' >>> HEX_to_RGB(HEX) # doctest: +ELLIPSIS array([ 0.6666666..., 0.8666666..., 1. ]) """ HEX = np.core.defchararray.lstrip(HEX, '#') def to_RGB(x): """ Converts given hexadecimal representation to *RGB*. """ l_x = len(x) return [int(x[i:i + l_x // 3], 16) for i in range(0, l_x, l_x // 3)] to_RGB_v = np.vectorize(to_RGB, otypes=[np.ndarray]) RGB = np.asarray(to_RGB_v(HEX).tolist()) / 255 return from_range_1(RGB)
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_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_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_decoding_Log3G12(y): """ Defines the *Log3G12* 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:`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_Log3G12(1.0 / 3) # doctest: +ELLIPSIS 0.1800015... """ y = to_domain_1(y) x = np.sign(y) * (10.0 ** (np.abs(y) / 0.184904) - 1) / 347.189667 return from_range_1(x)
def uv_to_UCS(uv, V=1): """ Returns the *CIE 1960 UCS* colourspace array from given *uv* chromaticity coordinates. Parameters ---------- uv : array_like *uv* chromaticity coordinates. V : numeric, optional Optional :math:`V` *luminance* value used to construct the *CIE 1960 UCS* colourspace array, the default :math:`V` *luminance* is set to 1. Returns ------- ndarray *CIE 1960 UCS* colourspace array. References ---------- :cite:`Wikipedia2008c` Examples -------- >>> uv = np.array([0.37720213, 0.33413508]) >>> uv_to_UCS(uv) # doctest: +ELLIPSIS array([ 1.1288911..., 1. , 0.8639104...]) """ u, v = tsplit(uv) V = np.full(u.shape, V, DEFAULT_FLOAT_DTYPE) U = V * u / v W = -V * (u + v - 1) / v UVW = tstack([U, V, W]) return from_range_1(UVW)
def uv_to_UCS(uv: ArrayLike, V: Floating = 1) -> NDArray: """ Return the *CIE 1960 UCS* colourspace array from given *uv* chromaticity coordinates. Parameters ---------- uv *uv* chromaticity coordinates. V Optional :math:`V` *luminance* value used to construct the *CIE 1960 UCS* colourspace array, the default :math:`V` *luminance* is set to 1. Returns ------- :class:`numpy.ndarray` *CIE 1960 UCS* colourspace array. References ---------- :cite:`Wikipedia2008c` Examples -------- >>> import numpy as np >>> uv = np.array([0.37720213, 0.33413508]) >>> uv_to_UCS(uv) # doctest: +ELLIPSIS array([ 1.1288911..., 1. , 0.8639104...]) """ u, v = tsplit(uv) V = as_float_scalar(to_domain_1(V)) UVW = tstack([V * u / v, full(u.shape, V), -V * (u + v - 1) / v]) return from_range_1(UVW)
def log_decoding_SLog(y, bit_depth=10, in_normalised_code_value=True, out_reflection=True, **kwargs): """ Defines the *Sony S-Log* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear *Sony S-Log* data :math:`y`. bit_depth : int, optional Bit depth used for conversion. in_normalised_code_value : bool, optional Whether the non-linear *Sony S-Log* data :math:`y` is encoded as 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 Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporation2012a` Examples -------- >>> log_decoding_SLog(0.384970815928670) # doctest: +ELLIPSIS 0.1... """ in_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['in_legal', 'in_normalised_code_value']], }, **kwargs).get('in_normalised_code_value', in_normalised_code_value) y = to_domain_1(y) x = legal_to_full(y, bit_depth) if in_normalised_code_value else y with domain_range_scale('ignore'): x = np.where( y >= log_encoding_SLog(0.0, bit_depth, in_normalised_code_value), 10**((x - 0.616596 - 0.03) / 0.432699) - 0.037584, (x - 0.030001222851889303) / 5.0, ) if out_reflection: x = x * 0.9 return as_float(from_range_1(x))
def log_decoding_CanonLog3( clog3: FloatingOrArrayLike, bit_depth: Integer = 10, in_normalised_code_value: Boolean = True, out_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Canon Log 3* log decoding curve / electro-optical transfer function. Parameters ---------- clog3 *Canon Log 3* non-linear data. bit_depth Bit depth used for conversion. in_normalised_code_value Whether the *Canon Log 3* non-linear data is encoded with normalised code values. out_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.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... """ 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_encoding_CanonLog( x: FloatingOrArrayLike, bit_depth: Integer = 10, out_normalised_code_value: Boolean = True, in_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Canon Log* log encoding curve / opto-electronic transfer function. Parameters ---------- x Linear data :math:`x`. bit_depth Bit depth used for conversion. out_normalised_code_value Whether the *Canon Log* non-linear data is encoded as normalised code values. in_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *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]) """ 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_cv = (full_to_legal(clog, bit_depth) if out_normalised_code_value else clog) return as_float(from_range_1(clog_cv))
def log_decoding_CanonLog2( clog2: FloatingOrArrayLike, bit_depth: Integer = 10, in_normalised_code_value: Boolean = True, out_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Canon Log 2* log decoding curve / electro-optical transfer function. Parameters ---------- clog2 *Canon Log 2* non-linear data. bit_depth Bit depth used for conversion. in_normalised_code_value Whether the *Canon Log 2* non-linear data is encoded with normalised code values. out_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.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... """ 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_CanonLog3( x: FloatingOrArrayLike, bit_depth: Integer = 10, out_normalised_code_value: Boolean = True, in_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Canon Log 3* log encoding curve / opto-electronic transfer function. Parameters ---------- x Linear data :math:`x`. bit_depth Bit depth used for conversion. out_normalised_code_value Whether the *Canon Log 3* non-linear data is encoded as normalised code values. in_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *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 +------------+-----------------------+---------------+ | **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... """ 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_cv = (full_to_legal(clog3, bit_depth) if out_normalised_code_value else clog3) return as_float(from_range_1(clog3_cv))
def log_decoding_CanonLog( clog: FloatingOrArrayLike, bit_depth: Integer = 10, in_normalised_code_value: Boolean = True, out_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Canon Log* log decoding curve / electro-optical transfer function. Parameters ---------- clog *Canon Log* non-linear data. bit_depth Bit depth used for conversion. in_normalised_code_value Whether the *Canon Log* non-linear data is encoded with normalised code values. out_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.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... """ 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 log_encoding_CanonLog2( x: FloatingOrArrayLike, bit_depth: Integer = 10, out_normalised_code_value: Boolean = True, in_reflection: Boolean = True, ) -> FloatingOrNDArray: """ Define the *Canon Log 2* log encoding curve / opto-electronic transfer function. Parameters ---------- x Linear data :math:`x`. bit_depth Bit depth used for conversion. out_normalised_code_value Whether the *Canon Log 2* non-linear data is encoded as normalised code values. in_reflection Whether the light level :math:`x` to a camera is reflection. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *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... """ 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_cv = (full_to_legal(clog2, bit_depth) if out_normalised_code_value else clog2) return as_float(from_range_1(clog2_cv))
def RGB_to_HCL(RGB: ArrayLike, gamma: Floating = 3, Y_0: Floating = 100) -> NDArray: """ Convert from *RGB* colourspace to *HCL* colourspace according to *Sarifuddin and Missaoui (2005)* method. Parameters ---------- RGB *RGB* colourspace array. gamma Non-linear lightness exponent matching *Lightness* :math:`L^*`. Y_0 White reference luminance :math:`Y_0`. Returns ------- :class:`numpy.ndarray` *HCL* array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HCL`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Sarifuddin2005`, :cite:`Wikipedia2015` Examples -------- >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952]) >>> RGB_to_HCL(RGB) # doctest: +ELLIPSIS array([-0.0316785..., 0.2841715..., 0.2285964...]) """ R, G, B = tsplit(to_domain_1(RGB)) Min = np.minimum(np.minimum(R, G), B) Max = np.maximum(np.maximum(R, G), B) alpha = (Min / Max) / Y_0 Q = np.exp(alpha * gamma) L = (Q * Max + (Q - 1) * Min) / 2 R_G = R - G G_B = G - B B_R = B - R C = Q * (np.abs(R_G) + np.abs(G_B) + np.abs(B_R)) / 3 H = np.arctan(G_B / R_G) _2_3_H = 2 / 3 * H _4_3_H = 4 / 3 * H H = np.select( [ np.logical_and(R_G >= 0, G_B >= 0), np.logical_and(R_G >= 0, G_B < 0), np.logical_and(R_G < 0, G_B >= 0), np.logical_and(R_G < 0, G_B < 0), ], [ _2_3_H, _4_3_H, np.pi + _4_3_H, _2_3_H - np.pi, ], ) HCL = tstack([H, C, L]) return from_range_1(HCL)
def HSV_to_RGB(HSV: ArrayLike) -> NDArray: """ Convert from *HSV* colourspace to *RGB* colourspace. Parameters ---------- HSV *HSV* colourspace array. Returns ------- :class:`numpy.ndarray` *RGB* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HSV`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBn`, :cite:`Smith1978b`, :cite:`Wikipedia2003` Examples -------- >>> HSV = np.array([0.99603944, 0.93246304, 0.45620519]) >>> HSV_to_RGB(HSV) # doctest: +ELLIPSIS array([ 0.4562051..., 0.0308107..., 0.0409195...]) """ H, S, V = tsplit(to_domain_1(HSV)) h = as_float_array(H * 6) h[np.asarray(h == 6)] = 0 i = np.floor(h) j = V * (1 - S) k = V * (1 - S * (h - i)) l = V * (1 - S * (1 - (h - i))) # noqa i = tstack([i, i, i]).astype(np.uint8) RGB = np.choose( i, [ tstack([V, l, j]), tstack([k, V, j]), tstack([j, V, l]), tstack([j, k, V]), tstack([l, j, V]), tstack([V, j, k]), ], mode="clip", ) return from_range_1(RGB)
def log_decoding_CanonLog2(clog2, bit_depth=10, in_legal=True, out_reflection=True): """ 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_legal : bool, optional Whether the *Canon Log 2* non-linear data is encoded in legal range. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. 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... """ clog2 = to_domain_1(clog2) clog2 = legal_to_full(clog2, bit_depth) if in_legal 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 RGB_to_HSV(RGB: ArrayLike) -> NDArray: """ Convert from *RGB* colourspace to *HSV* colourspace. Parameters ---------- RGB *RGB* colourspace array. Returns ------- :class:`numpy.ndarray` *HSV* array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HSV`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBj`, :cite:`Smith1978b`, :cite:`Wikipedia2003` Examples -------- >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952]) >>> RGB_to_HSV(RGB) # doctest: +ELLIPSIS array([ 0.9960394..., 0.9324630..., 0.4562051...]) """ RGB = to_domain_1(RGB) maximum = np.amax(RGB, -1) delta = np.ptp(RGB, -1) V = maximum R, G, B = tsplit(RGB) S = as_float_array(delta / maximum) S[np.asarray(delta == 0)] = 0 delta_R = (((maximum - R) / 6) + (delta / 2)) / delta delta_G = (((maximum - G) / 6) + (delta / 2)) / delta delta_B = (((maximum - B) / 6) + (delta / 2)) / delta H = delta_B - delta_G H = np.where(G == maximum, (1 / 3) + delta_R - delta_B, H) H = np.where(B == maximum, (2 / 3) + delta_G - delta_R, H) H[np.asarray(H < 0)] += 1 H[np.asarray(H > 1)] -= 1 H[np.asarray(delta == 0)] = 0 HSV = tstack([H, S, V]) return from_range_1(HSV)
def oetf_inverse_ARIBSTDB67(E_p, r=0.5, constants=ARIBSTDB67_CONSTANTS): """ Defines *ARIB STD-B67 (Hybrid Log-Gamma)* inverse 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] | +------------+-----------------------+---------------+ - 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 chromatic_adaptation_VonKries(XYZ, XYZ_w, XYZ_wr, transform='CAT02'): """ Adapts given stimulus from test viewing conditions to reference viewing conditions. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of stimulus to adapt. XYZ_w : array_like Test viewing condition *CIE XYZ* tristimulus values of whitepoint. XYZ_wr : array_like Reference viewing condition *CIE XYZ* tristimulus values of whitepoint. transform : unicode, optional **{'CAT02', 'XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp', 'Fairchild', 'CMCCAT97', 'CMCCAT2000', 'CAT02_BRILL_CAT', 'Bianco', 'Bianco PC'}**, Chromatic adaptation transform. Returns ------- ndarray *CIE XYZ_c* tristimulus values of the stimulus corresponding colour. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_n`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_r`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_c`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2013t` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775]) >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460]) >>> chromatic_adaptation_VonKries(XYZ, XYZ_w, XYZ_wr) # doctest: +ELLIPSIS array([ 0.2163881..., 0.1257 , 0.0384749...]) Using Bradford method: >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775]) >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460]) >>> transform = 'Bradford' >>> chromatic_adaptation_VonKries(XYZ, XYZ_w, XYZ_wr, transform) ... # doctest: +ELLIPSIS array([ 0.2166600..., 0.1260477..., 0.0385506...]) """ XYZ = to_domain_1(XYZ) M_CAT = chromatic_adaptation_matrix_VonKries(XYZ_w, XYZ_wr, transform) XYZ_a = dot_vector(M_CAT, XYZ) return from_range_1(XYZ_a)
def log_encoding_CanonLog(x, bit_depth=10, out_legal=True, in_reflection=True): """ Defines the *Canon Log* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Linear data :math:`x`. bit_depth : int, optional Bit depth used for conversion. out_legal : bool, optional Whether the *Canon Log* non-linear data is encoded in legal range. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray *Canon Log* non-linear data. References ---------- :cite:`Thorpe2012a` Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``clog`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> log_encoding_CanonLog(0.18) * 100 # doctest: +ELLIPSIS 34.3389651... """ x = to_domain_1(x) if in_reflection: x = x / 0.9 with domain_range_scale('ignore'): clog = np.where( x < log_decoding_CanonLog(0.0730597, bit_depth, False), -(0.529136 * (np.log10(-x * 10.1596 + 1)) - 0.0730597), 0.529136 * np.log10(10.1596 * x + 1) + 0.0730597, ) clog = full_to_legal(clog, bit_depth) if out_legal else clog return as_float(from_range_1(clog))
def log_decoding_CanonLog3(clog3, bit_depth=10, in_legal=True, out_reflection=True): """ 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_legal : bool, optional Whether the *Canon Log 3* non-linear data is encoded in legal range. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. 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... """ clog3 = to_domain_1(clog3) clog3 = legal_to_full(clog3, bit_depth) if in_legal 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_encoding_CanonLog3(x, bit_depth=10, out_legal=True, in_reflection=True): """ 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_legal : bool, optional Whether the *Canon Log 3* non-linear data is encoded in legal range. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray *Canon Log 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... """ 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_legal else clog3 return as_float(from_range_1(clog3))
def hdr_IPT_to_XYZ(IPT_hdr, Y_s=0.2, Y_abs=100, method='Fairchild 2011'): """ Converts from *hdr-IPT* colourspace to *CIE XYZ* tristimulus values. Parameters ---------- IPT_hdr : array_like *hdr-IPT* 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 *CIE XYZ* tristimulus values. Notes ----- +-------------+-------------------------+---------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +=============+=========================+=====================+ | ``IPT_hdr`` | ``I_hdr`` : [0, 100] | ``I_hdr`` : [0, 1] | | | | | | | ``P_hdr`` : [-100, 100] | ``P_hdr`` : [-1, 1] | | | | | | | ``T_hdr`` : [-100, 100] | ``T_hdr`` : [-1, 1] | +-------------+-------------------------+---------------------+ | ``Y_s`` | [0, 1] | [0, 1] | +-------------+-------------------------+---------------------+ +-------------+-------------------------+---------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +=============+=========================+=====================+ | ``XYZ`` | [0, 1] | [0, 1] | +-------------+-------------------------+---------------------+ References ---------- :cite:`Fairchild2010`, :cite:`Fairchild2011` Examples -------- >>> IPT_hdr = np.array([48.39376346, 42.44990202, 22.01954033]) >>> hdr_IPT_to_XYZ(IPT_hdr) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) >>> IPT_hdr = np.array([30.02873147, 83.93845061, 34.90287382]) >>> hdr_IPT_to_XYZ(IPT_hdr, method='Fairchild 2010') ... # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ IPT_hdr = to_domain_100(IPT_hdr) method_l = method.lower() assert method.lower() in [ m.lower() for m in HDR_IPT_METHODS ], ('"{0}" method is invalid, must be one of {1}!'.format( method, HDR_IPT_METHODS)) if method_l == 'fairchild 2010': luminance_callable = luminance_Fairchild2010 else: luminance_callable = luminance_Fairchild2011 e = exponent_hdr_IPT(Y_s, Y_abs, method)[..., np.newaxis] LMS = dot_vector(IPT_IPT_TO_LMS_MATRIX, IPT_hdr) # Domain and range scaling has already be handled. with domain_range_scale('ignore'): LMS_prime = np.sign(LMS) * np.abs(luminance_callable(LMS, e)) XYZ = dot_vector(IPT_LMS_TO_XYZ_MATRIX, LMS_prime) return from_range_1(XYZ)
def log_encoding_SLog3(x, bit_depth=10, out_normalised_code_value=True, in_reflection=True, **kwargs): """ Defines the *Sony S-Log3* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. bit_depth : int, optional Bit depth used for conversion. out_normalised_code_value : bool, optional Whether the non-linear *Sony S-Log3* data :math:`y` 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 Non-linear *Sony S-Log3* data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporationd` Examples -------- >>> log_encoding_SLog3(0.18) # doctest: +ELLIPSIS 0.4105571... The values of *S-Log3 10bit code values (18%, 90%)* table in :cite:`SonyCorporationd` are obtained as follows: >>> x = np.array([0, 18, 90]) / 100 >>> np.around(log_encoding_SLog3(x, 10, False) * 100).astype(np.int) array([ 4, 41, 61]) >>> np.around(log_encoding_SLog3(x) * (2 ** 10 - 1)).astype(np.int) array([ 95, 420, 598]) """ 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 not in_reflection: x = x * 0.9 y = np.where( x >= 0.01125000, (420 + np.log10((x + 0.01) / (0.18 + 0.01)) * 261.5) / 1023, (x * (171.2102946929 - 95) / 0.01125000 + 95) / 1023, ) y = y if out_normalised_code_value else legal_to_full(y, bit_depth) return as_float(from_range_1(y))
def log_decoding_SLog3(y, bit_depth=10, in_normalised_code_value=True, out_reflection=True, **kwargs): """ Defines the *Sony S-Log3* log decoding curve / electro-optical transfer function. Parameters ---------- y : numeric or array_like Non-linear *Sony S-Log3* data :math:`y`. bit_depth : int, optional Bit depth used for conversion. in_normalised_code_value : bool, optional Whether the non-linear *Sony S-Log3* data :math:`y` is encoded as 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 Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporationd` Examples -------- >>> log_decoding_SLog3(0.410557184750733) # doctest: +ELLIPSIS 0.1... """ in_normalised_code_value = handle_arguments_deprecation( { 'ArgumentRenamed': [['in_legal', 'in_normalised_code_value']], }, **kwargs).get('in_normalised_code_value', in_normalised_code_value) y = to_domain_1(y) y = y if in_normalised_code_value else full_to_legal(y, bit_depth) x = np.where( y >= 171.2102946929 / 1023, ((10**((y * 1023 - 420) / 261.5)) * (0.18 + 0.01) - 0.01), (y * 1023 - 95) * 0.01125000 / (171.2102946929 - 95), ) if not out_reflection: x = x / 0.9 return as_float(from_range_1(x))
def log_encoding_SLog(x, bit_depth=10, out_normalised_code_value=True, in_reflection=True, **kwargs): """ Defines the *Sony S-Log* log encoding curve / opto-electronic transfer function. Parameters ---------- x : numeric or array_like Reflection or :math:`IRE / 100` input light level :math:`x` to a camera. bit_depth : int, optional Bit depth used for conversion. out_normalised_code_value : bool, optional Whether the non-linear *Sony S-Log* data :math:`y` 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 Non-linear *Sony S-Log* data :math:`y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``x`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`SonyCorporation2012a` Examples -------- >>> log_encoding_SLog(0.18) # doctest: +ELLIPSIS 0.3849708... The values of *IRE and CV of S-Log2 @ISO800* table in :cite:`SonyCorporation2012a` are obtained as follows: >>> x = np.array([0, 18, 90]) / 100 >>> np.around(log_encoding_SLog(x, 10, False) * 100).astype(np.int) array([ 3, 38, 65]) >>> np.around(log_encoding_SLog(x) * (2 ** 10 - 1)).astype(np.int) array([ 90, 394, 636]) """ 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 y = np.where( x >= 0, ((0.432699 * np.log10(x + 0.037584) + 0.616596) + 0.03), x * 5 + 0.030001222851889303, ) y = full_to_legal(y, bit_depth) if out_normalised_code_value else y return as_float(from_range_1(y))
def oetf_ST2084(C, L_p=10000, constants=ST2084_CONSTANTS): """ Defines *SMPTE ST 2084:2014* optimised perceptual opto-electronic transfer function (OETF / OECF). Parameters ---------- C : numeric or array_like Target optical output :math:`C` in :math:`cd/m^2` of the ideal reference display. L_p : numeric, optional System peak luminance :math:`cd/m^2`, this parameter should stay at its default :math:`10000 cd/m^2` value for practical applications. It is exposed so that the definition can be used as a fitting function. constants : Structure, optional *SMPTE ST 2084:2014* constants. Returns ------- numeric or ndarray Color value abbreviated as :math:`N`, that is directly proportional to the encoded signal representation, and which is not directly proportional to the optical output of a display device. Warnings -------- *SMPTE ST 2084:2014* is an absolute transfer function. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``C`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``N`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - *SMPTE ST 2084:2014* is an absolute transfer function, thus the domain and range values for the *Reference* and *1* scales are only indicative that the data is not affected by scale transformations. References ---------- :cite:`Miller2014a`, :cite:`SocietyofMotionPictureandTelevisionEngineers2014a` Examples -------- >>> oetf_ST2084(100) # doctest: +ELLIPSIS 0.5080784... """ C = to_domain_1(C) Y_p = spow(C / L_p, constants.m_1) N = spow((constants.c_1 + constants.c_2 * Y_p) / (constants.c_3 * Y_p + 1), constants.m_2) return from_range_1(N)
def eotf_ST2084(N, L_p=10000, constants=ST2084_CONSTANTS): """ Defines *SMPTE ST 2084:2014* optimised perceptual electro-optical transfer function (EOTF / EOCF). This perceptual quantizer (PQ) has been modeled by Dolby Laboratories using *Barten (1999)* contrast sensitivity function. Parameters ---------- N : numeric or array_like Color value abbreviated as :math:`N`, that is directly proportional to the encoded signal representation, and which is not directly proportional to the optical output of a display device. L_p : numeric, optional System peak luminance :math:`cd/m^2`, this parameter should stay at its default :math:`10000 cd/m^2` value for practical applications. It is exposed so that the definition can be used as a fitting function. constants : Structure, optional *SMPTE ST 2084:2014* constants. Returns ------- numeric or ndarray Target optical output :math:`C` in :math:`cd/m^2` of the ideal reference display. Warnings -------- *SMPTE ST 2084:2014* is an absolute transfer function. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``N`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``C`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - *SMPTE ST 2084:2014* is an absolute transfer function, thus the domain and range values for the *Reference* and *1* scales are only indicative that the data is not affected by scale transformations. References ---------- :cite:`Miller2014a`, :cite:`SocietyofMotionPictureandTelevisionEngineers2014a` Examples -------- >>> eotf_ST2084(0.508078421517399) # doctest: +ELLIPSIS 100.0000000... """ N = to_domain_1(N) m_1_d = 1 / constants.m_1 m_2_d = 1 / constants.m_2 V_p = spow(N, m_2_d) n = V_p - constants.c_1 # Limiting negative values. n = np.where(n < 0, 0, n) L = spow((n / (constants.c_2 - constants.c_3 * V_p)), m_1_d) C = L_p * L return from_range_1(C)
def HSL_to_RGB(HSL: ArrayLike) -> NDArray: """ Convert from *HSL* colourspace to *RGB* colourspace. Parameters ---------- HSL *HSL* colourspace array. Returns ------- :class:`numpy.ndarray` *RGB* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HSL`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBk`, :cite:`Smith1978b`, :cite:`Wikipedia2003` Examples -------- >>> HSL = np.array([0.99603944, 0.87347144, 0.24350795]) >>> HSL_to_RGB(HSL) # doctest: +ELLIPSIS array([ 0.4562051..., 0.0308107..., 0.0409195...]) """ H, S, L = tsplit(to_domain_1(HSL)) def H_to_RGB(vi: NDArray, vj: NDArray, vH: NDArray) -> NDArray: """Convert *hue* value to *RGB* colourspace.""" vH = as_float_array(vH) vH[np.asarray(vH < 0)] += 1 vH[np.asarray(vH > 1)] -= 1 v = np.where( 6 * vH < 1, vi + (vj - vi) * 6 * vH, np.nan, ) v = np.where(np.logical_and(2 * vH < 1, np.isnan(v)), vj, v) v = np.where( np.logical_and(3 * vH < 2, np.isnan(v)), vi + (vj - vi) * ((2 / 3) - vH) * 6, v, ) v = np.where(np.isnan(v), vi, v) return v j = np.where(L < 0.5, L * (1 + S), (L + S) - (S * L)) i = 2 * L - j R = H_to_RGB(i, j, H + (1 / 3)) G = H_to_RGB(i, j, H) B = H_to_RGB(i, j, H - (1 / 3)) R = np.where(S == 0, L, R) G = np.where(S == 0, L, G) B = np.where(S == 0, L, B) RGB = tstack([R, G, B]) return from_range_1(RGB)
def luminance_Fairchild2011(L_hdr, epsilon=0.474, method='hdr-CIELAB'): """ Computes *luminance* :math:`Y` of given *Lightness* :math:`L_{hdr}` using *Fairchild and Chen (2011)* method according to *Michealis-Menten* kinetics. Parameters ---------- L_hdr : array_like *Lightness* :math:`L_{hdr}`. 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 *luminance* :math:`Y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_hdr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2011` Examples -------- >>> luminance_Fairchild2011(51.852958445912506) # doctest: +ELLIPSIS 0.1219722... >>> luminance_Fairchild2011(51.643108411718522, method='hdr-IPT') ... # doctest: +ELLIPSIS 0.1219722... """ L_hdr = to_domain_100(L_hdr) if method.lower() == 'hdr-cielab': maximum_perception = 247 else: maximum_perception = 246 Y = np.exp( np.log( substrate_concentration_MichealisMenten( L_hdr - 0.02, maximum_perception, 2**epsilon)) / epsilon) return from_range_1(Y)
def HCL_to_RGB(HCL: ArrayLike, gamma: Floating = 3, Y_0: Floating = 100) -> NDArray: """ Convert from *HCL* colourspace to *RGB* colourspace according to *Sarifuddin and Missaoui (2005)* method. Parameters ---------- HCL *HCL* colourspace array. gamma Non-linear lightness exponent matching *Lightness* :math:`L^*`. Y_0 White reference luminance :math:`Y_0`. Returns ------- :class:`numpy.ndarray` *RGB* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HCL`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Sarifuddin2005`, :cite:`Wikipedia2015` Examples -------- >>> HCL = np.array([-0.03167854, 0.28417150, 0.22859647]) >>> HCL_to_RGB(HCL) # doctest: +ELLIPSIS array([ 0.4562033..., 0.0308104..., 0.0409192...]) """ H, C, L = tsplit(to_domain_1(HCL)) Q = np.exp((1 - (3 * C) / (4 * L)) * (gamma / Y_0)) Min = (4 * L - 3 * C) / (4 * Q - 2) Max = Min + (3 * C) / (2 * Q) def _1_2_3(a: ArrayLike) -> NDArray: """Tail-stack given :math:`a` array as a *bool* dtype.""" return tstack([a, a, a], dtype=np.bool_) tan_3_2_H = np.tan(3 / 2 * H) tan_3_4_H_MP = np.tan(3 / 4 * (H - np.pi)) tan_3_4_H = np.tan(3 / 4 * H) tan_3_2_H_PP = np.tan(3 / 2 * (H + np.pi)) RGB = np.select( [ _1_2_3(np.logical_and(0 <= H, H <= np.radians(60))), _1_2_3(np.logical_and(np.radians(60) < H, H <= np.radians(120))), _1_2_3(np.logical_and(np.radians(120) < H, H <= np.pi)), _1_2_3(np.logical_and(np.radians(-60) <= H, H < 0)), _1_2_3(np.logical_and(np.radians(-120) <= H, H < np.radians(-60))), _1_2_3(np.logical_and(-np.pi < H, H < np.radians(-120))), ], [ tstack([ Max, (Max * tan_3_2_H + Min) / (1 + tan_3_2_H), Min, ]), tstack([ (Max * (1 + tan_3_4_H_MP) - Min) / tan_3_4_H_MP, Max, Min, ]), tstack([ Min, Max, Max * (1 + tan_3_4_H_MP) - Min * tan_3_4_H_MP, ]), tstack([ Max, Min, Min * (1 + tan_3_4_H) - Max * tan_3_4_H, ]), tstack([ (Min * (1 + tan_3_4_H) - Max) / tan_3_4_H, Min, Max, ]), tstack([ Min, (Min * tan_3_2_H_PP + Max) / (1 + tan_3_2_H_PP), Max, ]), ], ) return from_range_1(RGB)
def log_decoding_CanonLog(clog, bit_depth=10, in_legal=True, out_reflection=True): """ 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_legal : bool, optional Whether the *Canon Log* non-linear data is encoded in legal range. out_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. 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... """ clog = to_domain_1(clog) clog = legal_to_full(clog, bit_depth) if in_legal 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 Luv_to_XYZ( Luv, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE L\\*u\\*v\\** colourspace to *CIE XYZ* tristimulus values. Parameters ---------- Luv : array_like *CIE L\\*u\\*v\\** colourspace array. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **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] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wikipedia2007b` Examples -------- >>> Luv = np.array([41.52787529, 96.83626054, 17.75210149]) >>> Luv_to_XYZ(Luv) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ L, u, v = tsplit(to_domain_100(Luv)) X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) with domain_range_scale('100'): Y = luminance_CIE1976(L, Y_r) a = 1 / 3 * ((52 * L / (u + 13 * L * (4 * X_r / (X_r + 15 * Y_r + 3 * Z_r)))) - 1) b = -5 * Y c = -1 / 3.0 d = Y * (39 * L / (v + 13 * L * (9 * Y_r / (X_r + 15 * Y_r + 3 * Z_r))) - 5) X = (d - b) / (a - c) Z = X * a + b XYZ = tstack([X, Y, Z]) return from_range_1(XYZ)
def log_encoding_CanonLog2(x, bit_depth=10, out_legal=True, in_reflection=True): """ 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_legal : bool, optional Whether the *Canon Log 2* non-linear data is encoded in legal range. in_reflection : bool, optional Whether the light level :math:`x` to a camera is reflection. Returns ------- numeric or ndarray *Canon Log 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... """ 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_legal else clog2 return as_float(from_range_1(clog2))