def whiteness_Berger1959(XYZ, XYZ_0): """ Returns the *whiteness* index :math:`WI` of given sample *CIE XYZ* tristimulus values using *Berger (1959)* method. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of sample. XYZ_0 : array_like *CIE XYZ* tristimulus values of reference white. Returns ------- numeric or ndarray *Whiteness* :math:`WI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_0`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``WI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ - *Whiteness* :math:`WI` values larger than 33.33 indicate a bluish white and values smaller than 33.33 indicate a yellowish white. References ---------- :cite:`X-Rite2012a` Examples -------- >>> import numpy as np >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> XYZ_0 = np.array([94.80966767, 100.00000000, 107.30513595]) >>> whiteness_Berger1959(XYZ, XYZ_0) # doctest: +ELLIPSIS 30.3638017... """ X, Y, Z = tsplit(to_domain_100(XYZ)) X_0, _Y_0, Z_0 = tsplit(to_domain_100(XYZ_0)) WI = 0.333 * Y + 125 * (Z / Z_0) - 125 * (X / X_0) return from_range_100(WI)
def whiteness_Taube1960(XYZ: ArrayLike, XYZ_0: ArrayLike) -> FloatingOrNDArray: """ Return the *whiteness* index :math:`WI` of given sample *CIE XYZ* tristimulus values using *Taube (1960)* method. Parameters ---------- XYZ *CIE XYZ* tristimulus values of the sample. XYZ_0 *CIE XYZ* tristimulus values of the reference white. Returns ------- :class:`np.floating` or :class:`numpy.ndarray` *Whiteness* :math:`WI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_0`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``WI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ - *Whiteness* :math:`WI` values larger than 100 indicate a bluish white and values smaller than 100 indicate a yellowish white. References ---------- :cite:`X-Rite2012a` Examples -------- >>> import numpy as np >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> XYZ_0 = np.array([94.80966767, 100.00000000, 107.30513595]) >>> whiteness_Taube1960(XYZ, XYZ_0) # doctest: +ELLIPSIS 91.4071738... """ _X, Y, Z = tsplit(to_domain_100(XYZ)) _X_0, _Y_0, Z_0 = tsplit(to_domain_100(XYZ_0)) WI = 400 * (Z / Z_0) - 3 * Y return as_float(from_range_100(WI))
def delta_E_CIE1976(Lab_1, Lab_2): """ Returns the difference :math:`\\Delta E_{76}` between two given *CIE L\\*a\\*b\\** colourspace arrays using *CIE 1976* recommendation. Parameters ---------- Lab_1 : array_like *CIE L\\*a\\*b\\** colourspace array 1. Lab_2 : array_like *CIE L\\*a\\*b\\** colourspace array 2. Returns ------- numeric or ndarray Colour difference :math:`\\Delta E_{76}`. Notes ----- +------------+-----------------------+-------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===================+ | ``Lab_1`` | ``L_1`` : [0, 100] | ``L_1`` : [0, 1] | | | | | | | ``a_1`` : [-100, 100] | ``a_1`` : [-1, 1] | | | | | | | ``b_1`` : [-100, 100] | ``b_1`` : [-1, 1] | +------------+-----------------------+-------------------+ | ``Lab_2`` | ``L_2`` : [0, 100] | ``L_2`` : [0, 1] | | | | | | | ``a_2`` : [-100, 100] | ``a_2`` : [-1, 1] | | | | | | | ``b_2`` : [-100, 100] | ``b_2`` : [-1, 1] | +------------+-----------------------+-------------------+ References ---------- :cite:`Lindbloom2003c` Examples -------- >>> Lab_1 = np.array([100.00000000, 21.57210357, 272.22819350]) >>> Lab_2 = np.array([100.00000000, 426.67945353, 72.39590835]) >>> delta_E_CIE1976(Lab_1, Lab_2) # doctest: +ELLIPSIS 451.7133019... """ d_E = euclidean_distance(to_domain_100(Lab_1), to_domain_100(Lab_2)) return d_E
def yellowness_ASTME313( XYZ: ArrayLike, C_XZ: ArrayLike = YELLOWNESS_COEFFICIENTS_ASTME313[ "CIE 1931 2 Degree Standard Observer"]["D65"], ) -> FloatingOrNDArray: """ Return the *yellowness* index :math:`YI` of given sample *CIE XYZ* tristimulus values using *ASTM E313* method. ASTM E313 has successfully been used for a variety of white or near white materials. This includes coatings, plastics, textiles. Parameters ---------- XYZ *CIE XYZ* tristimulus values of the sample. C_XZ Coefficients :math:`C_X` and :math:`C_Z` for the *CIE 1931 2 Degree Standard Observer* and *CIE 1964 10 Degree Standard Observer* and *CIE Illuminant C* and *CIE Standard Illuminant D65*. Returns ------- :class:`np.floating` or :class:`numpy.ndarray` *Yellowness* :math:`YI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``YI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`ASTMInternational2015` Examples -------- >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> yellowness_ASTME313(XYZ) # doctest: +ELLIPSIS 4.3400000... """ X, Y, Z = tsplit(to_domain_100(XYZ)) C_X, C_Z = tsplit(C_XZ) WI = 100 * (C_X * X - C_Z * Z) / Y return as_float(from_range_100(WI))
def test_to_domain_100(self): """Test :func:`colour.utilities.common.to_domain_100` definition.""" with domain_range_scale("Reference"): self.assertEqual(to_domain_100(1), 1) with domain_range_scale("1"): self.assertEqual(to_domain_100(1), 100) with domain_range_scale("100"): self.assertEqual(to_domain_100(1), 1) with domain_range_scale("1"): self.assertEqual(to_domain_100(1, np.pi), np.pi) with domain_range_scale("100"): self.assertEqual( to_domain_100(1, dtype=np.float16).dtype, np.float16)
def Luv_to_uv( Luv: ArrayLike, illuminant: ArrayLike = CCS_ILLUMINANTS[ "CIE 1931 2 Degree Standard Observer"]["D65"], ) -> NDArray: """ Return the :math:`uv^p` chromaticity coordinates from given *CIE L\\*u\\*v\\** colourspace array. Parameters ---------- Luv *CIE L\\*u\\*v\\** colourspace array. illuminant Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- :class:`numpy.ndarray` :math:`uv^p` chromaticity coordinates. 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] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004j` Examples -------- >>> import numpy as np >>> Luv = np.array([41.52787529, 96.83626054, 17.75210149]) >>> Luv_to_uv(Luv) # doctest: +ELLIPSIS array([ 0.3772021..., 0.5012026...]) """ Luv = to_domain_100(Luv) X, Y, Z = tsplit(Luv_to_XYZ(Luv, illuminant)) X_Y_Z = X + 15 * Y + 3 * Z uv = tstack([4 * X / X_Y_Z, 9 * Y / X_Y_Z]) return uv
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 yellowness_ASTME313_alternative(XYZ: ArrayLike) -> FloatingOrNDArray: """ Return the *yellowness* index :math:`YI` of given sample *CIE XYZ* tristimulus values using the alternative *ASTM E313* method. In the original form of *Test Method E313*, an alternative equation was recommended for a *yellowness* index. In terms of colorimeter readings, it was :math:`YI = 100(1 − B/G)` where :math:`B` and :math:`G` are, respectively, blue and green colorimeter readings. Its derivation assumed that, because of the limitation of the concept to yellow (or blue) colors, it was not necessary to take account of variations in the amber or red colorimeter reading :math:`A`. This equation is no longer recommended. Parameters ---------- XYZ *CIE XYZ* tristimulus values of the sample. Returns ------- :class:`np.floating` or :class:`numpy.ndarray` *Yellowness* :math:`YI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``YI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ - Input *CIE XYZ* tristimulus values must be adapted to *CIE Illuminant C*. References ---------- :cite:`ASTMInternational2015`, :cite:`X-Rite2012a` Examples -------- >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> yellowness_ASTME313_alternative(XYZ) # doctest: +ELLIPSIS 11.0650000... """ _X, Y, Z = tsplit(to_domain_100(XYZ)) WI = 100 * (1 - (0.847 * Z) / Y) return as_float(from_range_100(WI))
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 lightness_CIE1976(Y, Y_n=100): """ Returns the *Lightness* :math:`L^*` of given *luminance* :math:`Y` using given reference white *luminance* :math:`Y_n` as per *CIE 1976* recommendation. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. Y_n : numeric or array_like, optional White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like *Lightness* :math:`L^*`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_star`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Lindbloom2003d`, :cite:`Wyszecki2000bd` Examples -------- >>> lightness_CIE1976(12.19722535) # doctest: +ELLIPSIS 41.5278758... """ Y = to_domain_100(Y) Y_n = as_float_array(Y_n) L_star = Y / Y_n L_star = as_float( np.where( L_star <= CIE_E, CIE_K * L_star, 116 * spow(L_star, 1 / 3) - 16, )) return from_range_100(L_star)
def luminance_CIE1976(L_star, Y_n=100): """ Returns the *luminance* :math:`Y` of given *Lightness* :math:`L^*` with given reference white *luminance* :math:`Y_n`. Parameters ---------- L_star : numeric or array_like *Lightness* :math:`L^*` Y_n : numeric or array_like White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like *luminance* :math:`Y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_star`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Lindbloom2003d`, :cite:`Wyszecki2000bd` Examples -------- >>> luminance_CIE1976(41.527875844653451) # doctest: +ELLIPSIS 12.1972253... >>> luminance_CIE1976(41.527875844653451, 95) # doctest: +ELLIPSIS 11.5873640... """ L_star = to_domain_100(L_star) Y_n = as_float_array(Y_n) Y = as_float( np.where( L_star > CIE_K * CIE_E, Y_n * ((L_star + 16) / 116)**3, Y_n * (L_star / CIE_K), )) return from_range_100(Y)
def whiteness_Stensby1968(Lab): """ Returns the *whiteness* index :math:`WI` of given sample *CIE L\\*a\\*b\\** colourspace array using *Stensby (1968)* method. Parameters ---------- Lab : array_like *CIE L\\*a\\*b\\** colourspace array of sample. Returns ------- numeric or ndarray *Whiteness* :math:`WI`. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``Lab`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``a`` : [-100, 100] | ``a`` : [-1, 1] | | | | | | | ``b`` : [-100, 100] | ``b`` : [-1, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``WI`` | [0, 100] | [0, 1] | +------------+-----------------------+-----------------+ - *Whiteness* :math:`WI` values larger than 100 indicate a bluish white and values smaller than 100 indicate a yellowish white. References ---------- :cite:`X-Rite2012a` Examples -------- >>> import numpy as np >>> Lab = np.array([100.00000000, -2.46875131, -16.72486654]) >>> whiteness_Stensby1968(Lab) # doctest: +ELLIPSIS 142.7683456... """ L, a, b = tsplit(to_domain_100(Lab)) WI = L - 3 * b + 3 * a return from_range_100(WI)
def yellowness_ASTMD1925(XYZ: ArrayLike) -> FloatingOrNDArray: """ Return the *yellowness* index :math:`YI` of given sample *CIE XYZ* tristimulus values using *ASTM D1925* method. ASTM D1925 has been specifically developed for the definition of the yellowness of homogeneous, non-fluorescent, almost neutral-transparent, white-scattering or opaque plastics as they will be reviewed under daylight condition. It can be other materials as well, as long as they fit into this description. Parameters ---------- XYZ *CIE XYZ* tristimulus values of the sample. Returns ------- :class:`np.floating` or :class:`numpy.ndarray` *Yellowness* :math:`YI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``YI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ - Input *CIE XYZ* tristimulus values must be adapted to *CIE Illuminant C*. References ---------- :cite:`ASTMInternational2015`, :cite:`X-Rite2012a` Examples -------- >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> yellowness_ASTMD1925(XYZ) # doctest: +ELLIPSIS 10.2999999... """ X, Y, Z = tsplit(to_domain_100(XYZ)) YI = (100 * (1.28 * X - 1.06 * Z)) / Y return as_float(from_range_100(YI))
def test_to_domain_100(self): """ Tests :func:`colour.utilities.common.to_domain_100` definition. """ with domain_range_scale('Reference'): self.assertEqual(to_domain_100(1), 1) with domain_range_scale('1'): self.assertEqual(to_domain_100(1), 100) with domain_range_scale('100'): self.assertEqual(to_domain_100(1), 1) with domain_range_scale('1'): self.assertEqual(to_domain_100(1, np.pi), np.pi) with domain_range_scale('100'): self.assertEqual( to_domain_100(1, dtype=np.float16).dtype, np.float16)
def yellowness_ASTMD1925(XYZ): """ Returns the *yellowness* index :math:`YI` of given sample *CIE XYZ* tristimulus values using *ASTM D1925* method. ASTM D1925 has been specifically developed for the definition of the Yellowness of homogeneous, non-fluorescent, almost neutral-transparent, white-scattering or opaque plastics as they will be reviewed under daylight condition. It can be other materials as well, as long as they fit into this description. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of sample. Returns ------- numeric or ndarray *Whiteness* :math:`YI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``YI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`X-Rite2012a` Examples -------- >>> import numpy as np >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> yellowness_ASTMD1925(XYZ) # doctest: +ELLIPSIS 10.2999999... """ X, Y, Z = tsplit(to_domain_100(XYZ)) YI = (100 * (1.28 * X - 1.06 * Z)) / Y return from_range_100(YI)
def luminance_Fairchild2010( L_hdr: FloatingOrArrayLike, epsilon: FloatingOrArrayLike = 1.836) -> FloatingOrNDArray: """ Compute *luminance* :math:`Y` of given *Lightness* :math:`L_{hdr}` using *Fairchild and Wyble (2010)* method according to *Michaelis-Menten* kinetics. Parameters ---------- L_hdr *Lightness* :math:`L_{hdr}`. epsilon :math:`\\epsilon` exponent. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Luminance* :math:`Y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_hdr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`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_MichaelisMenten_Michaelis1913( L_hdr - 0.02, 100, spow(0.184, epsilon))) / epsilon) return as_float(from_range_1(Y))
def Luv_to_uv( Luv, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Returns the :math:`uv^p` chromaticity coordinates from given *CIE L\\*u\\*v\\** colourspace array. 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 :math:`uv^p` chromaticity coordinates. 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] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004j` Examples -------- >>> Luv = np.array([41.52787529, 96.83626054, 17.75210149]) >>> Luv_to_uv(Luv) # doctest: +ELLIPSIS array([ 0.3772021..., 0.5012026...]) """ Luv = to_domain_100(Luv) X, Y, Z = tsplit(Luv_to_XYZ(Luv, illuminant)) uv = tstack([4 * X / (X + 15 * Y + 3 * Z), 9 * Y / (X + 15 * Y + 3 * Z)]) return uv
def luminance_CIE1976(L_star, Y_n=100): """ Returns the *luminance* :math:`Y` of given *Lightness* :math:`L^*` with given reference white *luminance* :math:`Y_n`. Parameters ---------- L_star : numeric or array_like *Lightness* :math:`L^*` Y_n : numeric or array_like White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like *luminance* :math:`Y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_star`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> luminance_CIE1976(41.527875844653451) # doctest: +ELLIPSIS 12.1972253... >>> luminance_CIE1976(41.527875844653451, 95) # doctest: +ELLIPSIS 11.5873640... """ L_star = to_domain_100(L_star) Y_n = as_float_array(Y_n) f_Y_Y_n = (L_star + 16) / 116 Y = intermediate_luminance_function_CIE1976(f_Y_Y_n, Y_n) return from_range_100(Y)
def luminance_CIE1976(L_star: FloatingOrArrayLike, Y_n: FloatingOrArrayLike = 100) -> FloatingOrNDArray: """ Return the *luminance* :math:`Y` of given *Lightness* :math:`L^*` with given reference white *luminance* :math:`Y_n`. Parameters ---------- L_star *Lightness* :math:`L^*` Y_n White reference *luminance* :math:`Y_n`. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Luminance* :math:`Y`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_star`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> luminance_CIE1976(41.527875844653451) # doctest: +ELLIPSIS 12.1972253... >>> luminance_CIE1976(41.527875844653451, 95) # doctest: +ELLIPSIS 11.5873640... """ L_star = to_domain_100(L_star) Y_n = as_float_array(Y_n) f_Y_Y_n = (L_star + 16) / 116 Y = intermediate_luminance_function_CIE1976(f_Y_Y_n, Y_n) return as_float(from_range_100(Y))
def lightness_Wyszecki1963(Y: FloatingOrArrayLike) -> FloatingOrNDArray: """ Return the *Lightness* :math:`W` of given *luminance* :math:`Y` using *Wyszecki (1963)* method. Parameters ---------- Y *Luminance* :math:`Y`. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Lightness* :math:`W`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``W`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Wyszecki1963b` Examples -------- >>> lightness_Wyszecki1963(12.19722535) # doctest: +ELLIPSIS 40.5475745... """ Y = to_domain_100(Y) if np.any(Y < 1) or np.any(Y > 98): usage_warning( '"W*" Lightness computation is only applicable for ' '1% < "Y" < 98%, unpredictable results may occur!' ) W = 25 * spow(Y, 1 / 3) - 17 return as_float(from_range_100(W))
def yellowness_ASTME313(XYZ): """ Returns the *yellowness* index :math:`YI` of given sample *CIE XYZ* tristimulus values using *ASTM E313* method. ASTM E313 has successfully been used for a variety of white or near white materials. This includes coatings, Plastics, Textiles. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of sample. Returns ------- numeric or ndarray *Whiteness* :math:`YI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``YI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`X-Rite2012a` Examples -------- >>> import numpy as np >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> yellowness_ASTME313(XYZ) # doctest: +ELLIPSIS 11.0650000... """ _X, Y, Z = tsplit(to_domain_100(XYZ)) WI = 100 * (1 - (0.847 * Z) / Y) return from_range_100(WI)
def lightness_Wyszecki1963(Y): """ Returns the *Lightness* :math:`W` of given *luminance* :math:`Y` using *Wyszecki (1963)* method. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. Returns ------- numeric or array_like *Lightness* :math:`W`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``W`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Wyszecki1963b` Examples -------- >>> lightness_Wyszecki1963(12.19722535) # doctest: +ELLIPSIS 40.5475745... """ Y = to_domain_100(Y) if np.any(Y < 1) or np.any(Y > 98): usage_warning('"W*" Lightness computation is only applicable for ' '1% < "Y" < 98%, unpredictable results may occur!') W = 25 * spow(Y, 1 / 3) - 17 return from_range_100(W)
def lightness_CIE1976( Y: FloatingOrArrayLike, Y_n: FloatingOrArrayLike = 100 ) -> FloatingOrNDArray: """ Return the *Lightness* :math:`L^*` of given *luminance* :math:`Y` using given reference white *luminance* :math:`Y_n` as per *CIE 1976* recommendation. Parameters ---------- Y *Luminance* :math:`Y`. Y_n White reference *luminance* :math:`Y_n`. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Lightness* :math:`L^*`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_star`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> lightness_CIE1976(12.19722535) # doctest: +ELLIPSIS 41.5278758... """ Y = to_domain_100(Y) Y_n = as_float_array(Y_n) L_star = 116 * intermediate_lightness_function_CIE1976(Y, Y_n) - 16 return as_float(from_range_100(L_star))
def lightness_CIE1976(Y, Y_n=100): """ Returns the *Lightness* :math:`L^*` of given *luminance* :math:`Y` using given reference white *luminance* :math:`Y_n` as per *CIE 1976* recommendation. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. Y_n : numeric or array_like, optional White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like *Lightness* :math:`L^*`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L_star`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> lightness_CIE1976(12.19722535) # doctest: +ELLIPSIS 41.5278758... """ Y = to_domain_100(Y) Y_n = as_float_array(Y_n) L_star = 116 * intermediate_lightness_function_CIE1976(Y, Y_n) - 16 return from_range_100(L_star)
def whiteness_ASTME313(XYZ): """ Returns the *whiteness* index :math:`WI` of given sample *CIE XYZ* tristimulus values using *ASTM E313* method. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of sample. Returns ------- numeric or ndarray *Whiteness* :math:`WI`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``WI`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`X-Rite2012a` Examples -------- >>> import numpy as np >>> XYZ = np.array([95.00000000, 100.00000000, 105.00000000]) >>> whiteness_ASTME313(XYZ) # doctest: +ELLIPSIS 55.7400000... """ _X, Y, Z = tsplit(to_domain_100(XYZ)) WI = 3.388 * Z - 3 * Y return from_range_100(WI)
def lightness_Glasser1958(Y): """ Returns the *Lightness* :math:`L` of given *luminance* :math:`Y` using *Glasser et al. (1958)* method. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. Returns ------- numeric or array_like *Lightness* :math:`L`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Glasser1958a` Examples -------- >>> lightness_Glasser1958(12.19722535) # doctest: +ELLIPSIS 39.8351264... """ Y = to_domain_100(Y) L = 25.29 * spow(Y, 1 / 3) - 18.38 return from_range_100(L)
def lightness_Glasser1958(Y: FloatingOrArrayLike) -> FloatingOrNDArray: """ Return the *Lightness* :math:`L` of given *luminance* :math:`Y` using *Glasser et al. (1958)* method. Parameters ---------- Y *Luminance* :math:`Y`. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Lightness* :math:`L`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Glasser1958a` Examples -------- >>> lightness_Glasser1958(12.19722535) # doctest: +ELLIPSIS 39.8351264... """ Y = to_domain_100(Y) L = 25.29 * spow(Y, 1 / 3) - 18.38 return as_float(from_range_100(L))
def Lab_to_XYZ( Lab, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE L\\*a\\*b\\** colourspace to *CIE XYZ* tristimulus values. Parameters ---------- Lab : array_like *CIE L\\*a\\*b\\** 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** | +================+=======================+=================+ | ``Lab`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``a`` : [-100, 100] | ``a`` : [-1, 1] | | | | | | | ``b`` : [-100, 100] | ``b`` : [-1, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m` Examples -------- >>> Lab = np.array([41.52787529, 52.63858304, 26.92317922]) >>> Lab_to_XYZ(Lab) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ L, a, b = tsplit(to_domain_100(Lab)) X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) f_Y_Y_n = (L + 16) / 116 f_X_X_n = a / 500 + f_Y_Y_n f_Z_Z_n = f_Y_Y_n - b / 200 X = intermediate_luminance_function_CIE1976(f_X_X_n, X_n) Y = intermediate_luminance_function_CIE1976(f_Y_Y_n, Y_n) Z = intermediate_luminance_function_CIE1976(f_Z_Z_n, Z_n) XYZ = tstack([X, Y, Z]) return from_range_1(XYZ)
def Lab_to_DIN99(Lab, k_E=1, k_CH=1): """ Converts from *CIE L\\*a\\*b\\** colourspace to *DIN99* colourspace. Parameters ---------- Lab : array_like *CIE L\\*a\\*b\\** colourspace array. k_E : numeric, optional Parametric factor :math:`K_E` used to compensate for texture and other specimen presentation effects. k_CH : numeric, optional Parametric factor :math:`K_{CH}` used to compensate for texture and other specimen presentation effects. Returns ------- ndarray *DIN99* colourspace array. Notes ----- +------------+------------------------+--------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+========================+====================+ | ``Lab`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``a`` : [-100, 100] | ``a`` : [-1, 1] | | | | | | | ``b`` : [-100, 100] | ``b`` : [-1, 1] | +------------+------------------------+--------------------+ +------------+------------------------+--------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+========================+====================+ | ``Lab_99`` | ``L_99`` : [0, 100] | ``L_99`` : [0, 1] | | | | | | | ``a_99`` : [-100, 100] | ``a_99`` : [-1, 1] | | | | | | | ``b_99`` : [-100, 100] | ``b_99`` : [-1, 1] | +------------+------------------------+--------------------+ References ---------- :cite:`ASTMInternational2007` Examples -------- >>> import numpy as np >>> Lab = np.array([41.52787529, 52.63858304, 26.92317922]) >>> Lab_to_DIN99(Lab) # doctest: +ELLIPSIS array([ 53.2282198..., 28.4163465..., 3.8983955...]) """ L, a, b = tsplit(to_domain_100(Lab)) cos_16 = np.cos(np.radians(16)) sin_16 = np.sin(np.radians(16)) e = cos_16 * a + sin_16 * b f = 0.7 * (-sin_16 * a + cos_16 * b) G = spow(e ** 2 + f ** 2, 0.5) h_ef = np.arctan2(f, e) C_99 = (np.log(1 + 0.045 * G)) / (0.045 * k_CH * k_E) # Hue angle is unused currently. # h_99 = np.degrees(h_ef) a_99 = C_99 * np.cos(h_ef) b_99 = C_99 * np.sin(h_ef) L_99 = 105.509 * (np.log(1 + 0.0158 * L)) * k_E Lab_99 = tstack([L_99, a_99, b_99]) return from_range_100(Lab_99)
def UVW_to_XYZ( UVW, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts *CIE 1964 U\\*V\\*W\\** colourspace to *CIE XYZ* tristimulus values. Parameters ---------- UVW : array_like *CIE 1964 U\\*V\\*W\\** colourspace array. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Warning ------- The input domain and output range of that definition are non standard! Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``UVW`` | ``U`` : [-100, 100] | ``U`` : [-1, 1] | | | | | | | ``V`` : [-100, 100] | ``V`` : [-1, 1] | | | | | | | ``W`` : [0, 100] | ``W`` : [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`Wikipedia2008a` Examples -------- >>> import numpy as np >>> UVW = np.array([94.55035725, 11.55536523, 40.54757405]) >>> UVW_to_XYZ(UVW) array([ 20.654008, 12.197225, 5.136952]) """ U, V, W = tsplit(to_domain_100(UVW)) u_0, v_0 = tsplit(xy_to_UCS_uv(xyY_to_xy(illuminant))) Y = ((W + 17) / 25) ** 3 u = U / (13 * W) + u_0 v = V / (13 * W) + v_0 x, y = tsplit(UCS_uv_to_xy(tstack([u, v]))) XYZ = xyY_to_XYZ(tstack([x, y, Y])) return from_range_100(XYZ)
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 chromatic_adaptation_Fairchild1990(XYZ_1, XYZ_n, XYZ_r, Y_n, discount_illuminant=False): """ Adapts given stimulus *CIE XYZ_1* tristimulus values from test viewing conditions to reference viewing conditions using *Fairchild (1990)* chromatic adaptation model. Parameters ---------- XYZ_1 : array_like *CIE XYZ_1* tristimulus values of test sample / stimulus. XYZ_n : array_like Test viewing condition *CIE XYZ_n* tristimulus values of whitepoint. XYZ_r : array_like Reference viewing condition *CIE XYZ_r* tristimulus values of whitepoint. Y_n : numeric or array_like Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- ndarray Adapted *CIE XYZ_2* tristimulus values of stimulus. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_1`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_n`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_r`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_2`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild1991a`, :cite:`Fairchild2013s` Examples -------- >>> XYZ_1 = np.array([19.53, 23.07, 24.97]) >>> XYZ_n = np.array([111.15, 100.00, 35.20]) >>> XYZ_r = np.array([94.81, 100.00, 107.30]) >>> Y_n = 200 >>> chromatic_adaptation_Fairchild1990(XYZ_1, XYZ_n, XYZ_r, Y_n) ... # doctest: +ELLIPSIS array([ 23.3252634..., 23.3245581..., 76.1159375...]) """ XYZ_1 = to_domain_100(XYZ_1) XYZ_n = to_domain_100(XYZ_n) XYZ_r = to_domain_100(XYZ_r) Y_n = as_float_array(Y_n) LMS_1 = dot_vector(FAIRCHILD1990_XYZ_TO_RGB_MATRIX, XYZ_1) LMS_n = dot_vector(FAIRCHILD1990_XYZ_TO_RGB_MATRIX, XYZ_n) LMS_r = dot_vector(FAIRCHILD1990_XYZ_TO_RGB_MATRIX, XYZ_r) p_LMS = degrees_of_adaptation( LMS_1, Y_n, discount_illuminant=discount_illuminant) a_LMS_1 = p_LMS / LMS_n a_LMS_2 = p_LMS / LMS_r A_1 = row_as_diagonal(a_LMS_1) A_2 = row_as_diagonal(a_LMS_2) LMSp_1 = dot_vector(A_1, LMS_1) c = 0.219 - 0.0784 * np.log10(Y_n) C = row_as_diagonal(tstack([c, c, c])) LMS_a = dot_vector(C, LMSp_1) LMSp_2 = dot_vector(np.linalg.inv(C), LMS_a) LMS_c = dot_vector(np.linalg.inv(A_2), LMSp_2) XYZ_c = dot_vector(FAIRCHILD1990_RGB_TO_XYZ_MATRIX, LMS_c) return from_range_100(XYZ_c)
def XYZ_to_UVW( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE XYZ* tristimulus values to *CIE 1964 U\\*V\\*W\\** 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 1964 U\\*V\\*W\\** colourspace array. Warning ------- The input domain and output range of that definition are non standard! Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``UVW`` | ``U`` : [-100, 100] | ``U`` : [-1, 1] | | | | | | | ``V`` : [-100, 100] | ``V`` : [-1, 1] | | | | | | | ``W`` : [0, 100] | ``W`` : [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`Wikipedia2008a` Examples -------- >>> import numpy as np >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) * 100 >>> XYZ_to_UVW(XYZ) # doctest: +ELLIPSIS array([ 94.5503572..., 11.5553652..., 40.5475740...]) """ XYZ = to_domain_100(XYZ) xy = xyY_to_xy(illuminant) xyY = XYZ_to_xyY(XYZ, xy) _x, _y, Y = tsplit(xyY) u, v = tsplit(UCS_to_uv(XYZ_to_UCS(XYZ))) u_0, v_0 = tsplit(xy_to_UCS_uv(xy)) W = 25 * spow(Y, 1 / 3) - 17 U = 13 * W * (u - u_0) V = 13 * W * (v - v_0) UVW = tstack([U, V, W]) return from_range_100(UVW)
def JMh_CIECAM02_to_UCS_Luo2006(JMh, coefficients): """ Converts from *CIECAM02* :math:`JMh` correlates array to one of the *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces :math:`J'a'b'` array. The :math:`JMh` correlates array is constructed using the CIECAM02 correlate of *Lightness* :math:`J`, the *CIECAM02* correlate of *colourfulness* :math:`M` and the *CIECAM02* *Hue* angle :math:`h` in degrees. Parameters ---------- JMh : array_like *CIECAM02* correlates array :math:`JMh`. coefficients : array_like Coefficients of one of the *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces. Returns ------- ndarray *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces :math:`J'a'b'` array. Notes ----- +------------+------------------------+--------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+========================+====================+ | ``JMh`` | ``J`` : [0, 100] | ``J`` : [0, 1] | | | | | | | ``M`` : [0, 100] | ``M`` : [0, 1] | | | | | | | ``h`` : [0, 360] | ``h`` : [0, 1] | +------------+------------------------+--------------------+ +------------+------------------------+--------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+========================+====================+ | ``Jpapbp`` | ``Jp_1`` : [0, 100] | ``Jp_1`` : [0, 1] | | | | | | | ``ap_1`` : [-100, 100] | ``ap_1`` : [-1, 1] | | | | | | | ``bp_1`` : [-100, 100] | ``bp_1`` : [-1, 1] | +------------+------------------------+--------------------+ Examples -------- >>> from colour.appearance import ( ... CIECAM02_VIEWING_CONDITIONS, ... XYZ_to_CIECAM02) >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_w = np.array([95.05, 100.00, 108.88]) >>> L_A = 318.31 >>> Y_b = 20.0 >>> surround = CIECAM02_VIEWING_CONDITIONS['Average'] >>> specification = XYZ_to_CIECAM02( ... XYZ, XYZ_w, L_A, Y_b, surround) >>> JMh = (specification.J, specification.M, specification.h) >>> JMh_CIECAM02_to_UCS_Luo2006(JMh, COEFFICIENTS_UCS_LUO2006['CAM02-LCD']) ... # doctest: +ELLIPSIS array([ 54.9043313..., -0.0845039..., -0.0685483...]) """ J, M, h = tsplit(JMh) J = to_domain_100(J) M = to_domain_100(M) h = to_domain_degrees(h) _K_L, c_1, c_2 = tsplit(coefficients) J_p = ((1 + 100 * c_1) * J) / (1 + c_1 * J) M_p = (1 / c_2) * np.log(1 + c_2 * M) a_p, b_p = tsplit(polar_to_cartesian(tstack([M_p, np.radians(h)]))) Jpapbp = tstack([J_p, a_p, b_p]) return from_range_100(Jpapbp)
def UCS_Luo2006_to_JMh_CIECAM02(Jpapbp, coefficients): """ Converts from one of the *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces :math:`J'a'b'` array to *CIECAM02* :math:`JMh` correlates array. Parameters ---------- Jpapbp : array_like *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces :math:`J'a'b'` array. coefficients : array_like Coefficients of one of the *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces. Returns ------- ndarray *CIECAM02* correlates array :math:`JMh`. Notes ----- +------------+------------------------+--------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+========================+====================+ | ``Jpapbp`` | ``Jp_1`` : [0, 100] | ``Jp_1`` : [0, 1] | | | | | | | ``ap_1`` : [-100, 100] | ``ap_1`` : [-1, 1] | | | | | | | ``bp_1`` : [-100, 100] | ``bp_1`` : [-1, 1] | +------------+------------------------+--------------------+ +------------+------------------------+--------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+========================+====================+ | ``JMh`` | ``J`` : [0, 100] | ``J`` : [0, 1] | | | | | | | ``M`` : [0, 100] | ``M`` : [0, 1] | | | | | | | ``h`` : [0, 360] | ``h`` : [0, 1] | +------------+------------------------+--------------------+ Examples -------- >>> Jpapbp = np.array([54.90433134, -0.08450395, -0.06854831]) >>> UCS_Luo2006_to_JMh_CIECAM02( ... Jpapbp, COEFFICIENTS_UCS_LUO2006['CAM02-LCD']) ... # doctest: +ELLIPSIS array([ 4.1731091...e+01, 1.0884217...e-01, 2.1904843...e+02]) """ J_p, a_p, b_p = tsplit(to_domain_100(Jpapbp)) _K_L, c_1, c_2 = tsplit(coefficients) J = -J_p / (c_1 * J_p - 1 - 100 * c_1) M_p, h = tsplit(cartesian_to_polar(tstack([a_p, b_p]))) M = (np.exp(M_p / (1 / c_2)) - 1) / c_2 JMh = tstack([ from_range_100(J), from_range_100(M), from_range_degrees(np.degrees(h) % 360) ]) return JMh
def delta_E_CMC(Lab_1, Lab_2, l=2, c=1): # noqa """ Returns the difference :math:`\\Delta E_{CMC}` between two given *CIE L\\*a\\*b\\** colourspace arrays using *Colour Measurement Committee* recommendation. The quasimetric has two parameters: *Lightness* (l) and *chroma* (c), allowing the users to weight the difference based on the ratio of l:c. Commonly used values are 2:1 for acceptability and 1:1 for the threshold of imperceptibility. Parameters ---------- Lab_1 : array_like *CIE L\\*a\\*b\\** colourspace array 1. Lab_2 : array_like *CIE L\\*a\\*b\\** colourspace array 2. l : numeric, optional Lightness weighting factor. c : numeric, optional Chroma weighting factor. Returns ------- numeric or ndarray Colour difference :math:`\\Delta E_{CMC}`. Notes ----- +------------+-----------------------+-------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===================+ | ``Lab_1`` | ``L_1`` : [0, 100] | ``L_1`` : [0, 1] | | | | | | | ``a_1`` : [-100, 100] | ``a_1`` : [-1, 1] | | | | | | | ``b_1`` : [-100, 100] | ``b_1`` : [-1, 1] | +------------+-----------------------+-------------------+ | ``Lab_2`` | ``L_2`` : [0, 100] | ``L_2`` : [0, 1] | | | | | | | ``a_2`` : [-100, 100] | ``a_2`` : [-1, 1] | | | | | | | ``b_2`` : [-100, 100] | ``b_2`` : [-1, 1] | +------------+-----------------------+-------------------+ References ---------- :cite:`Lindbloom2009f` Examples -------- >>> Lab_1 = np.array([100.00000000, 21.57210357, 272.22819350]) >>> Lab_2 = np.array([100.00000000, 426.67945353, 72.39590835]) >>> delta_E_CMC(Lab_1, Lab_2) # doctest: +ELLIPSIS 172.7047712... """ L_1, a_1, b_1 = tsplit(to_domain_100(Lab_1)) L_2, a_2, b_2 = tsplit(to_domain_100(Lab_2)) c_1 = np.hypot(a_1, b_1) c_2 = np.hypot(a_2, b_2) s_l = np.where(L_1 < 16, 0.511, (0.040975 * L_1) / (1 + 0.01765 * L_1)) s_c = 0.0638 * c_1 / (1 + 0.0131 * c_1) + 0.638 h_1 = np.degrees(np.arctan2(b_1, a_1)) % 360 t = np.where( np.logical_and(h_1 >= 164, h_1 <= 345), 0.56 + np.fabs(0.2 * np.cos(np.deg2rad(h_1 + 168))), 0.36 + np.fabs(0.4 * np.cos(np.deg2rad(h_1 + 35))), ) c_4 = c_1 * c_1 * c_1 * c_1 f = np.sqrt(c_4 / (c_4 + 1900)) s_h = s_c * (f * t + 1 - f) delta_L = L_1 - L_2 delta_C = c_1 - c_2 delta_A = a_1 - a_2 delta_B = b_1 - b_2 delta_H2 = delta_A ** 2 + delta_B ** 2 - delta_C ** 2 v_1 = delta_L / (l * s_l) v_2 = delta_C / (c * s_c) v_3 = s_h d_E = np.sqrt(v_1 ** 2 + v_2 ** 2 + (delta_H2 / (v_3 * v_3))) return d_E
def delta_E_CIE2000(Lab_1, Lab_2, textiles=False): """ Returns the difference :math:`\\Delta E_{00}` between two given *CIE L\\*a\\*b\\** colourspace arrays using *CIE 2000* recommendation. Parameters ---------- Lab_1 : array_like *CIE L\\*a\\*b\\** colourspace array 1. Lab_2 : array_like *CIE L\\*a\\*b\\** colourspace array 2. textiles : bool, optional Textiles application specific parametric factors :math:`k_L=2,\\ k_C=k_H=1` weights are used instead of :math:`k_L=k_C=k_H=1`. Returns ------- numeric or ndarray Colour difference :math:`\\Delta E_{00}`. Notes ----- +------------+-----------------------+-------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===================+ | ``Lab_1`` | ``L_1`` : [0, 100] | ``L_1`` : [0, 1] | | | | | | | ``a_1`` : [-100, 100] | ``a_1`` : [-1, 1] | | | | | | | ``b_1`` : [-100, 100] | ``b_1`` : [-1, 1] | +------------+-----------------------+-------------------+ | ``Lab_2`` | ``L_2`` : [0, 100] | ``L_2`` : [0, 1] | | | | | | | ``a_2`` : [-100, 100] | ``a_2`` : [-1, 1] | | | | | | | ``b_2`` : [-100, 100] | ``b_2`` : [-1, 1] | +------------+-----------------------+-------------------+ - *CIE 2000* colour differences are not symmetrical: difference between ``Lab_1`` and ``Lab_2`` may not be the same as difference between ``Lab_2`` and ``Lab_1`` thus one colour must be understood to be the reference against which a sample colour is compared. - Parametric factors :math:`k_L=k_C=k_H=1` weights under *reference conditions*: - Illumination: D65 source - Illuminance: 1000 lx - Observer: Normal colour vision - Background field: Uniform, neutral gray with :math:`L^*=50` - Viewing mode: Object - Sample size: Greater than 4 degrees - Sample separation: Direct edge contact - Sample colour-difference magnitude: Lower than 5.0 :math:`\\Delta E_{00}` - Sample structure: Homogeneous (without texture) References ---------- :cite:`Lindbloom2009e`, :cite:`Melgosa2013b` Examples -------- >>> Lab_1 = np.array([100.00000000, 21.57210357, 272.22819350]) >>> Lab_2 = np.array([100.00000000, 426.67945353, 72.39590835]) >>> delta_E_CIE2000(Lab_1, Lab_2) # doctest: +ELLIPSIS 94.0356490... >>> Lab_2 = np.array([50.00000000, 426.67945353, 72.39590835]) >>> delta_E_CIE2000(Lab_1, Lab_2) # doctest: +ELLIPSIS 100.8779470... >>> delta_E_CIE2000(Lab_1, Lab_2, textiles=True) # doctest: +ELLIPSIS 95.7920535... """ L_1, a_1, b_1 = tsplit(to_domain_100(Lab_1)) L_2, a_2, b_2 = tsplit(to_domain_100(Lab_2)) k_L = 2 if textiles else 1 k_C = 1 k_H = 1 l_bar_prime = 0.5 * (L_1 + L_2) c_1 = np.hypot(a_1, b_1) c_2 = np.hypot(a_2, b_2) c_bar = 0.5 * (c_1 + c_2) c_bar7 = c_bar ** 7 g = 0.5 * (1 - np.sqrt(c_bar7 / (c_bar7 + 25 ** 7))) a_1_prime = a_1 * (1 + g) a_2_prime = a_2 * (1 + g) c_1_prime = np.hypot(a_1_prime, b_1) c_2_prime = np.hypot(a_2_prime, b_2) c_bar_prime = 0.5 * (c_1_prime + c_2_prime) h_1_prime = np.degrees(np.arctan2(b_1, a_1_prime)) % 360 h_2_prime = np.degrees(np.arctan2(b_2, a_2_prime)) % 360 h_bar_prime = np.where( np.fabs(h_1_prime - h_2_prime) <= 180, 0.5 * (h_1_prime + h_2_prime), (0.5 * (h_1_prime + h_2_prime + 360)), ) t = (1 - 0.17 * np.cos(np.deg2rad(h_bar_prime - 30)) + 0.24 * np.cos(np.deg2rad(2 * h_bar_prime)) + 0.32 * np.cos(np.deg2rad(3 * h_bar_prime + 6)) - 0.20 * np.cos(np.deg2rad(4 * h_bar_prime - 63))) h = h_2_prime - h_1_prime delta_h_prime = np.where(h_2_prime <= h_1_prime, h - 360, h + 360) delta_h_prime = np.where(np.fabs(h) <= 180, h, delta_h_prime) delta_L_prime = L_2 - L_1 delta_C_prime = c_2_prime - c_1_prime delta_H_prime = (2 * np.sqrt(c_1_prime * c_2_prime) * np.sin( np.deg2rad(0.5 * delta_h_prime))) s_L = 1 + ((0.015 * (l_bar_prime - 50) * (l_bar_prime - 50)) / np.sqrt(20 + (l_bar_prime - 50) * (l_bar_prime - 50))) s_C = 1 + 0.045 * c_bar_prime s_H = 1 + 0.015 * c_bar_prime * t delta_theta = ( 30 * np.exp(-((h_bar_prime - 275) / 25) * ((h_bar_prime - 275) / 25))) c_bar_prime7 = c_bar_prime ** 7 r_C = np.sqrt(c_bar_prime7 / (c_bar_prime7 + 25 ** 7)) r_T = -2 * r_C * np.sin(np.deg2rad(2 * delta_theta)) d_E = np.sqrt((delta_L_prime / (k_L * s_L)) ** 2 + (delta_C_prime / (k_C * s_C)) ** 2 + (delta_H_prime / (k_H * s_H)) ** 2 + (delta_C_prime / (k_C * s_C)) * (delta_H_prime / (k_H * s_H)) * r_T) return d_E
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 XYZ_to_Nayatani95(XYZ, XYZ_n, Y_o, E_o, E_or, n=1): """ Computes the *Nayatani (1995)* colour appearance model correlates. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_n : array_like *CIE XYZ* tristimulus values of reference white. Y_o : numeric or array_like Luminance factor :math:`Y_o` of achromatic background as percentage normalised to domain [0.18, 1.0] in **'Reference'** domain-range scale. E_o : numeric or array_like Illuminance :math:`E_o` of the viewing field in lux. E_or : numeric or array_like Normalising illuminance :math:`E_{or}` in lux usually normalised to domain [1000, 3000]. n : numeric or array_like, optional Noise term used in the non linear chromatic adaptation model. Returns ------- Nayatani95_Specification *Nayatani (1995)* colour appearance model specification. Notes ----- +--------------------------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================================+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +--------------------------------+-----------------------+---------------+ | ``XYZ_n`` | [0, 100] | [0, 1] | +--------------------------------+-----------------------+---------------+ +--------------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================================+=======================+===============+ | ``Nayatani95_Specification.h`` | [0, 360] | [0, 1] | +--------------------------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2013ba`, :cite:`Nayatani1995a` Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_n = np.array([95.05, 100.00, 108.88]) >>> Y_o = 20.0 >>> E_o = 5000.0 >>> E_or = 1000.0 >>> XYZ_to_Nayatani95(XYZ, XYZ_n, Y_o, E_o, E_or) # doctest: +ELLIPSIS Nayatani95_Specification(L_star_P=49.9998829..., C=0.0133550..., \ h=257.5232268..., s=0.0133550..., Q=62.6266734..., M=0.0167262..., H=None, \ HC=None, L_star_N=50.0039154...) """ XYZ = to_domain_100(XYZ) XYZ_n = to_domain_100(XYZ_n) Y_o = as_float_array(Y_o) E_o = as_float_array(E_o) E_or = as_float_array(E_or) # Computing adapting luminance :math:`L_o` and normalising luminance # :math:`L_{or}` in in :math:`cd/m^2`. # L_o = illuminance_to_luminance(E_o, Y_o) L_or = illuminance_to_luminance(E_or, Y_o) # Computing :math:`\\xi` :math:`\\eta`, :math:`\\zeta` values. xez = intermediate_values(XYZ_to_xy(XYZ_n / 100)) xi, eta, _zeta = tsplit(xez) # Computing adapting field cone responses. RGB_o = (((Y_o[..., np.newaxis] * E_o[..., np.newaxis]) / (100 * np.pi)) * xez) # Computing stimulus cone responses. RGB = XYZ_to_RGB_Nayatani95(XYZ) R, G, _B = tsplit(RGB) # Computing exponential factors of the chromatic adaptation. bRGB_o = exponential_factors(RGB_o) bL_or = beta_1(L_or) # Computing scaling coefficients :math:`e(R)` and :math:`e(G)` eR = scaling_coefficient(R, xi) eG = scaling_coefficient(G, eta) # Computing opponent colour dimensions. # Computing achromatic response :math:`Q`: Q_response = achromatic_response(RGB, bRGB_o, xez, bL_or, eR, eG, n) # Computing tritanopic response :math:`t`: t_response = tritanopic_response(RGB, bRGB_o, xez, n) # Computing protanopic response :math:`p`: p_response = protanopic_response(RGB, bRGB_o, xez, n) # Computing the correlate of *brightness* :math:`B_r`. B_r = brightness_correlate(bRGB_o, bL_or, Q_response) # Computing *brightness* :math:`B_{rw}` of ideal white. brightness_ideal_white = ideal_white_brightness_correlate( bRGB_o, xez, bL_or, n) # Computing the correlate of achromatic *Lightness* :math:`L_p^\\star`. L_star_P = (achromatic_lightness_correlate(Q_response)) # Computing the correlate of normalised achromatic *Lightness* # :math:`L_n^\\star`. L_star_N = (normalised_achromatic_lightness_correlate( B_r, brightness_ideal_white)) # Computing the *hue* angle :math:`\\theta`. theta = hue_angle(p_response, t_response) # TODO: Implement hue quadrature & composition computation. # Computing the correlate of *saturation* :math:`S`. S_RG, S_YB = tsplit( saturation_components(theta, bL_or, t_response, p_response)) S = saturation_correlate(S_RG, S_YB) # Computing the correlate of *chroma* :math:`C`. # C_RG, C_YB = tsplit(chroma_components(L_star_P, S_RG, S_YB)) C = chroma_correlate(L_star_P, S) # Computing the correlate of *colourfulness* :math:`M`. # TODO: Investigate components usage. # M_RG, M_YB = tsplit(colourfulness_components(C_RG, C_YB, # brightness_ideal_white)) M = colourfulness_correlate(C, brightness_ideal_white) return Nayatani95_Specification(L_star_P, C, from_range_degrees(theta), S, B_r, M, None, None, L_star_N)
def XYZ_to_ATD95(XYZ, XYZ_0, Y_0, k_1, k_2, sigma=300): """ Computes the *ATD (1995)* colour vision model correlates. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_0 : array_like *CIE XYZ* tristimulus values of reference white. Y_0 : numeric or array_like Absolute adapting field luminance in :math:`cd/m^2`. k_1 : numeric or array_like Application specific weight :math:`k_1`. k_2 : numeric or array_like Application specific weight :math:`k_2`. sigma : numeric or array_like, optional Constant :math:`\\sigma` varied to predict different types of data. Returns ------- CAM_Specification_ATD95 *ATD (1995)* colour vision model specification. Notes ----- +---------------------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +===========================+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``XYZ_0`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ +-------------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +===============================+=======================+===============+ | ``CAM_Specification_ATD95.h`` | [0, 360] | [0, 1] | +-------------------------------+-----------------------+---------------+ - For unrelated colors, there is only self-adaptation and :math:`k_1` is set to 1.0 while :math:`k_2` is set to 0.0. For related colors such as typical colorimetric applications, :math:`k_1` is set to 0.0 and :math:`k_2` is set to a value between 15 and 50 *(Guth, 1995)*. References ---------- :cite:`Fairchild2013v`, :cite:`Guth1995a` Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_0 = np.array([95.05, 100.00, 108.88]) >>> Y_0 = 318.31 >>> k_1 = 0.0 >>> k_2 = 50.0 >>> XYZ_to_ATD95(XYZ, XYZ_0, Y_0, k_1, k_2) # doctest: +ELLIPSIS CAM_Specification_ATD95(h=1.9089869..., C=1.2064060..., Q=0.1814003..., \ A_1=0.1787931... T_1=0.0286942..., D_1=0.0107584..., A_2=0.0192182..., \ T_2=0.0205377..., D_2=0.0107584...) """ XYZ = to_domain_100(XYZ) XYZ_0 = to_domain_100(XYZ_0) Y_0 = as_float_array(Y_0) k_1 = as_float_array(k_1) k_2 = as_float_array(k_2) sigma = as_float_array(sigma) XYZ = luminance_to_retinal_illuminance(XYZ, Y_0) XYZ_0 = luminance_to_retinal_illuminance(XYZ_0, Y_0) # Computing adaptation model. LMS = XYZ_to_LMS_ATD95(XYZ) XYZ_a = k_1[..., np.newaxis] * XYZ + k_2[..., np.newaxis] * XYZ_0 LMS_a = XYZ_to_LMS_ATD95(XYZ_a) LMS_g = LMS * (sigma[..., np.newaxis] / (sigma[..., np.newaxis] + LMS_a)) # Computing opponent colour dimensions. A_1, T_1, D_1, A_2, T_2, D_2 = tsplit(opponent_colour_dimensions(LMS_g)) # Computing the correlate of *brightness* :math:`Br`. Br = spow(A_1**2 + T_1**2 + D_1**2, 0.5) # Computing the correlate of *saturation* :math:`C`. C = spow(T_2**2 + D_2**2, 0.5) / A_2 # Computing the *hue* :math:`H`. Note that the reference does not take the # modulus of the :math:`H`, thus :math:`H` can exceed 360 degrees. H = T_2 / D_2 return CAM_Specification_ATD95(from_range_degrees(H), C, Br, A_1, T_1, D_1, A_2, T_2, D_2)
def XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround=CIECAM02_VIEWING_CONDITIONS['Average'], discount_illuminant=False): """ Computes the *CIECAM02* colour appearance model correlates from given *CIE XYZ* tristimulus values. This is the *forward* implementation. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_w : array_like *CIE XYZ* tristimulus values of reference white. L_A : numeric or array_like Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken to be 20% of the luminance of a white object in the scene). Y_b : numeric or array_like Relative luminance of background :math:`Y_b` in :math:`cd/m^2`. surround : CIECAM02_InductionFactors, optional Surround viewing conditions induction factors. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- CIECAM02_Specification *CIECAM02* colour appearance model specification. Notes ----- +------------------------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +==============================+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------------------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +------------------------------+-----------------------+---------------+ +------------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +==============================+=======================+===============+ | ``CIECAM02_specification.h`` | [0, 360] | [0, 1] | +------------------------------+-----------------------+---------------+ | ``CIECAM02_specification.H`` | [0, 360] | [0, 1] | +------------------------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2004c`, :cite:`Luo2013`, :cite:`Moroneya`, :cite:`Wikipedia2007a` Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_w = np.array([95.05, 100.00, 108.88]) >>> L_A = 318.31 >>> Y_b = 20.0 >>> surround = CIECAM02_VIEWING_CONDITIONS['Average'] >>> XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround) # doctest: +ELLIPSIS CIECAM02_Specification(J=41.7310911..., C=0.1047077..., h=219.0484326..., \ s=2.3603053..., Q=195.3713259..., M=0.1088421..., H=278.0607358..., HC=None) """ XYZ = to_domain_100(XYZ) XYZ_w = to_domain_100(XYZ_w) _X_w, Y_w, _Z_w = tsplit(XYZ_w) L_A = as_float_array(L_A) Y_b = as_float_array(Y_b) n, F_L, N_bb, N_cb, z = tsplit( viewing_condition_dependent_parameters(Y_b, Y_w, L_A)) # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform # sharpened *RGB* values. RGB = dot_vector(CAT02_CAT, XYZ) RGB_w = dot_vector(CAT02_CAT, XYZ_w) # Computing degree of adaptation :math:`D`. D = (degree_of_adaptation(surround.F, L_A) if not discount_illuminant else np.ones(L_A.shape)) # Computing full chromatic adaptation. RGB_c = full_chromatic_adaptation_forward(RGB, RGB_w, Y_w, D) RGB_wc = full_chromatic_adaptation_forward(RGB_w, RGB_w, Y_w, D) # Converting to *Hunt-Pointer-Estevez* colourspace. RGB_p = RGB_to_rgb(RGB_c) RGB_pw = RGB_to_rgb(RGB_wc) # Applying forward post-adaptation non linear response compression. RGB_a = post_adaptation_non_linear_response_compression_forward(RGB_p, F_L) RGB_aw = post_adaptation_non_linear_response_compression_forward( RGB_pw, F_L) # Converting to preliminary cartesian coordinates. a, b = tsplit(opponent_colour_dimensions_forward(RGB_a)) # Computing the *hue* angle :math:`h`. h = hue_angle(a, b) # Computing hue :math:`h` quadrature :math:`H`. H = hue_quadrature(h) # TODO: Compute hue composition. # Computing eccentricity factor *e_t*. e_t = eccentricity_factor(h) # Computing achromatic responses for the stimulus and the whitepoint. A = achromatic_response_forward(RGB_a, N_bb) A_w = achromatic_response_forward(RGB_aw, N_bb) # Computing the correlate of *Lightness* :math:`J`. J = lightness_correlate(A, A_w, surround.c, z) # Computing the correlate of *brightness* :math:`Q`. Q = brightness_correlate(surround.c, J, A_w, F_L) # Computing the correlate of *chroma* :math:`C`. C = chroma_correlate(J, n, surround.N_c, N_cb, e_t, a, b, RGB_a) # Computing the correlate of *colourfulness* :math:`M`. M = colourfulness_correlate(C, F_L) # Computing the correlate of *saturation* :math:`s`. s = saturation_correlate(M, Q) return CIECAM02_Specification(J, C, from_range_degrees(h), s, Q, M, from_range_degrees(H), None)
def DIN99_to_Lab(Lab_99, k_E=1, k_CH=1): """ Converts from *DIN99* colourspace to *CIE L\\*a\\*b\\** colourspace. Parameters ---------- Lab_99 : array_like *DIN99* colourspace array. k_E : numeric, optional Parametric factor :math:`K_E` used to compensate for texture and other specimen presentation effects. k_CH : numeric, optional Parametric factor :math:`K_{CH}` used to compensate for texture and other specimen presentation effects. Returns ------- ndarray *CIE L\\*a\\*b\\** colourspace array. Notes ----- +------------+------------------------+--------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+========================+====================+ | ``Lab_99`` | ``L_99`` : [0, 100] | ``L_99`` : [0, 1] | | | | | | | ``a_99`` : [-100, 100] | ``a_99`` : [-1, 1] | | | | | | | ``b_99`` : [-100, 100] | ``b_99`` : [-1, 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:`ASTMInternational2007` Examples -------- >>> import numpy as np >>> Lab_99 = np.array([53.22821988, 28.41634656, 3.89839552]) >>> DIN99_to_Lab(Lab_99) # doctest: +ELLIPSIS array([ 41.5278752..., 52.6385830..., 26.9231792...]) """ L_99, a_99, b_99 = tsplit(to_domain_100(Lab_99)) cos_16 = np.cos(np.radians(16)) sin_16 = np.sin(np.radians(16)) h_99 = np.arctan2(b_99, a_99) C_99 = np.sqrt(a_99 ** 2 + b_99 ** 2) G = (np.exp(0.045 * C_99 * k_CH * k_E) - 1) / 0.045 e = G * np.cos(h_99) f = G * np.sin(h_99) a = e * cos_16 - (f / 0.7) * sin_16 b = e * sin_16 + (f / 0.7) * cos_16 L = (np.exp(L_99 * k_E / 105.509) - 1) / 0.0158 Lab = tstack([L, a, b]) return from_range_100(Lab)
def chromatic_adaptation_Zhai2018( XYZ_b: ArrayLike, XYZ_wb: ArrayLike, XYZ_wd: ArrayLike, D_b: FloatingOrArrayLike = 1, D_d: FloatingOrArrayLike = 1, XYZ_wo: ArrayLike = np.array([1, 1, 1]), transform: Union[Literal["CAT02", "CAT16"], str] = "CAT02", ) -> NDArray: """ Adapt given sample colour :math:`XYZ_{\\beta}` tristimulus values from input viewing conditions under :math:`\\beta` illuminant to output viewing conditions under :math:`\\delta` illuminant using *Zhai and Luo (2018)* chromatic adaptation model. According to the definition of :math:`D`, a one-step CAT such as CAT02 can only be used to transform colors from an incomplete adapted field into a complete adapted field. When CAT02 are used to transform an incomplete to incomplete case, :math:`D` has no baseline level to refer to. *Smet et al. (2017)* proposed a new concept of two-step CAT to replace the present CATs such as CAT02 with only one-step transform in order to define :math:`D` more clearly. A two-step CAT involves an illuminant representing the baseline states between the test and reference illuminants for the calculation. In the first step the test color is transformed from test illuminant to the baseline illuminant (:math:`BI`), and it is then transformed to the reference illuminant Degrees of adaptation under the other illuminants should be calculated relative to the adaptation under the :math:`BI`. When :math:`D` becomes lower towards zero, the adaptation point of the observer moves towards the :math:`BI`. Therefore, the chromaticity of the :math:`BI` should be an intrinsic property of the human vision system. Parameters ---------- XYZ_b Sample colour :math:`XYZ_{\\beta}` under input illuminant :math:`\\beta`. XYZ_wb Input illuminant :math:`\\beta`. XYZ_wd Output illuminant :math:`\\delta`. D_b Degree of adaptation :math:`D_{\\beta}` of input illuminant :math:`\\beta`. D_d Degree of adaptation :math:`D_{\\delta}` of output illuminant :math:`\\delta`. XYZ_wo Baseline illuminant (:math:`BI`) :math:`o`. transform Chromatic adaptation transform. Returns ------- :class:`numpy.ndarray` Sample corresponding colour :math:`XYZ_{\\delta}` tristimulus values under output illuminant :math:`D_{\\delta}`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_b`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wb`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wd`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wo`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_d`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Zhai2018` Examples -------- >>> XYZ_b = np.array([48.900, 43.620, 6.250]) >>> XYZ_wb = np.array([109.850, 100, 35.585]) >>> XYZ_wd = np.array([95.047, 100, 108.883]) >>> D_b = 0.9407 >>> D_d = 0.9800 >>> XYZ_wo = np.array([100, 100, 100]) >>> chromatic_adaptation_Zhai2018( ... XYZ_b, XYZ_wb, XYZ_wd, D_b, D_d, XYZ_wo) # doctest: +ELLIPSIS array([ 39.1856164..., 42.1546179..., 19.2367203...]) >>> XYZ_d = np.array([39.18561644, 42.15461798, 19.23672036]) >>> chromatic_adaptation_Zhai2018( ... XYZ_d, XYZ_wd, XYZ_wb, D_d, D_b, XYZ_wo) # doctest: +ELLIPSIS array([ 48.9 , 43.62, 6.25]) """ XYZ_b = to_domain_100(XYZ_b) XYZ_wb = to_domain_100(XYZ_wb) XYZ_wd = to_domain_100(XYZ_wd) XYZ_wo = to_domain_100(XYZ_wo) D_b = as_float_array(D_b) D_d = as_float_array(D_d) Y_wb = XYZ_wb[..., 1][..., np.newaxis] Y_wd = XYZ_wd[..., 1][..., np.newaxis] Y_wo = XYZ_wo[..., 1][..., np.newaxis] transform = validate_method(transform, ["CAT02", "CAT16"]) M = CHROMATIC_ADAPTATION_TRANSFORMS[transform] RGB_b = vector_dot(M, XYZ_b) RGB_wb = vector_dot(M, XYZ_wb) RGB_wd = vector_dot(M, XYZ_wd) RGB_wo = vector_dot(M, XYZ_wo) D_RGB_b = D_b * (Y_wb / Y_wo) * (RGB_wo / RGB_wb) + 1 - D_b D_RGB_d = D_d * (Y_wd / Y_wo) * (RGB_wo / RGB_wd) + 1 - D_d D_RGB = D_RGB_b / D_RGB_d RGB_d = D_RGB * RGB_b XYZ_d = vector_dot(np.linalg.inv(M), RGB_d) return from_range_100(XYZ_d)
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 CIECAM02_to_XYZ(CIECAM02_specification, XYZ_w, L_A, Y_b, surround=CIECAM02_VIEWING_CONDITIONS['Average'], discount_illuminant=False): """ Converts *CIECAM02* specification to *CIE XYZ* tristimulus values. This is the *reverse* implementation. Parameters ---------- CIECAM02_specification : CIECAM02_Specification *CIECAM02* colour appearance model specification. Correlate of *Lightness* :math:`J`, correlate of *chroma* :math:`C` or correlate of *colourfulness* :math:`M` and *hue* angle :math:`h` in degrees must be specified, e.g. :math:`JCh` or :math:`JMh`. XYZ_w : array_like *CIE XYZ* tristimulus values of reference white. L_A : numeric or array_like Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken to be 20% of the luminance of a white object in the scene). Y_b : numeric or array_like Relative luminance of background :math:`Y_b` in :math:`cd/m^2`. surround : CIECAM02_InductionFactors, optional Surround viewing conditions. discount_illuminant : bool, optional Discount the illuminant. Returns ------- XYZ : ndarray *CIE XYZ* tristimulus values. Raises ------ ValueError If neither *C* or *M* correlates have been defined in the ``CIECAM02_specification`` argument. Warning ------- The output range of that definition is non standard! Notes ----- +------------------------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +==============================+=======================+===============+ | ``CIECAM02_specification.h`` | [0, 360] | [0, 1] | +------------------------------+-----------------------+---------------+ | ``CIECAM02_specification.H`` | [0, 360] | [0, 1] | +------------------------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +------------------------------+-----------------------+---------------+ +------------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +==============================+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------------------------+-----------------------+---------------+ - ``CIECAM02_specification`` can also be passed as a compatible argument to :func:`colour.utilities.as_namedtuple` definition. References ---------- :cite:`Fairchild2004c`, :cite:`Luo2013`, :cite:`Moroneya`, :cite:`Wikipedia2007a` Examples -------- >>> specification = CIECAM02_Specification(J=41.731091132513917, ... C=0.104707757171031, ... h=219.048432658311780) >>> XYZ_w = np.array([95.05, 100.00, 108.88]) >>> L_A = 318.31 >>> Y_b = 20.0 >>> CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b) # doctest: +ELLIPSIS array([ 19.01..., 20... , 21.78...]) """ J, C, h, _s, _Q, M, _H, _HC = as_namedtuple(CIECAM02_specification, CIECAM02_Specification) L_A = as_float_array(L_A) h = to_domain_degrees(h) XYZ_w = to_domain_100(XYZ_w) _X_w, Y_w, _Z_w = tsplit(XYZ_w) n, F_L, N_bb, N_cb, z = tsplit( viewing_condition_dependent_parameters(Y_b, Y_w, L_A)) if C is None and M is not None: C = M / spow(F_L, 0.25) elif C is None: raise ValueError('Either "C" or "M" correlate must be defined in ' 'the "CIECAM02_specification" argument!') # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform # sharpened *RGB* values. RGB_w = dot_vector(CAT02_CAT, XYZ_w) # Computing degree of adaptation :math:`D`. D = (degree_of_adaptation(surround.F, L_A) if not discount_illuminant else np.ones(L_A.shape)) # Computing full chromatic adaptation. RGB_wc = full_chromatic_adaptation_forward(RGB_w, RGB_w, Y_w, D) # Converting to *Hunt-Pointer-Estevez* colourspace. RGB_pw = RGB_to_rgb(RGB_wc) # Applying post-adaptation non linear response compression. RGB_aw = post_adaptation_non_linear_response_compression_forward( RGB_pw, F_L) # Computing achromatic response for the whitepoint. A_w = achromatic_response_forward(RGB_aw, N_bb) # Computing temporary magnitude quantity :math:`t`. t = temporary_magnitude_quantity_reverse(C, J, n) # Computing eccentricity factor *e_t*. e_t = eccentricity_factor(h) # Computing achromatic response :math:`A` for the stimulus. A = achromatic_response_reverse(A_w, J, surround.c, z) # Computing *P_1* to *P_3*. P_n = P(surround.N_c, N_cb, e_t, t, A, N_bb) _P_1, P_2, _P_3 = tsplit(P_n) # Computing opponent colour dimensions :math:`a` and :math:`b`. a, b = tsplit(opponent_colour_dimensions_reverse(P_n, h)) # Computing post-adaptation non linear response compression matrix. RGB_a = post_adaptation_non_linear_response_compression_matrix(P_2, a, b) # Applying reverse post-adaptation non linear response compression. RGB_p = post_adaptation_non_linear_response_compression_reverse(RGB_a, F_L) # Converting to *Hunt-Pointer-Estevez* colourspace. RGB_c = rgb_to_RGB(RGB_p) # Applying reverse full chromatic adaptation. RGB = full_chromatic_adaptation_reverse(RGB_c, RGB_w, Y_w, D) # Converting *CMCCAT2000* transform sharpened *RGB* values to *CIE XYZ* # tristimulus values. XYZ = dot_vector(CAT02_INVERSE_CAT, RGB) return from_range_100(XYZ)
def chromatic_adaptation_forward_CMCCAT2000( XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, surround=CMCCAT2000_VIEWING_CONDITIONS['Average']): """ Adapts given stimulus *CIE XYZ* tristimulus values from test viewing conditions to reference viewing conditions using *CMCCAT2000* forward chromatic adaptation model. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of the stimulus to adapt. XYZ_w : array_like Test viewing condition *CIE XYZ* tristimulus values of the whitepoint. XYZ_wr : array_like Reference viewing condition *CIE XYZ* tristimulus values of the whitepoint. L_A1 : numeric or array_like Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`. L_A2 : numeric or array_like Luminance of reference adapting field :math:`L_{A2}` in :math:`cd/m^2`. surround : CMCCAT2000_InductionFactors, optional Surround viewing conditions induction factors. Returns ------- ndarray *CIE XYZ_c* tristimulus values of the stimulus corresponding colour. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wr`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_c`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Li2002a`, :cite:`Westland2012k` Examples -------- >>> XYZ = np.array([22.48, 22.74, 8.54]) >>> XYZ_w = np.array([111.15, 100.00, 35.20]) >>> XYZ_wr = np.array([94.81, 100.00, 107.30]) >>> L_A1 = 200 >>> L_A2 = 200 >>> chromatic_adaptation_forward_CMCCAT2000(XYZ, XYZ_w, XYZ_wr, L_A1, L_A2) ... # doctest: +ELLIPSIS array([ 19.5269832..., 23.0683396..., 24.9717522...]) """ XYZ = to_domain_100(XYZ) XYZ_w = to_domain_100(XYZ_w) XYZ_wr = to_domain_100(XYZ_wr) L_A1 = as_float_array(L_A1) L_A2 = as_float_array(L_A2) RGB = dot_vector(CMCCAT2000_CAT, XYZ) RGB_w = dot_vector(CMCCAT2000_CAT, XYZ_w) RGB_wr = dot_vector(CMCCAT2000_CAT, XYZ_wr) D = (surround.F * (0.08 * np.log10(0.5 * (L_A1 + L_A2)) + 0.76 - 0.45 * (L_A1 - L_A2) / (L_A1 + L_A2))) D = np.clip(D, 0, 1) a = D * XYZ_w[..., 1] / XYZ_wr[..., 1] RGB_c = ( RGB * (a[..., np.newaxis] * (RGB_wr / RGB_w) + 1 - D[..., np.newaxis])) XYZ_c = dot_vector(CMCCAT2000_INVERSE_CAT, RGB_c) return from_range_100(XYZ_c)
def OSA_UCS_to_XYZ(Ljg, optimisation_parameters=None): """ Converts from *OSA UCS* colourspace to *CIE XYZ* tristimulus values under the *CIE 1964 10 Degree Standard Observer*. Parameters ---------- Ljg : array_like *OSA UCS* :math:`Ljg` lightness, jaune (yellowness), and greenness. optimisation_parameters : dict_like, optional Parameters for :func:`scipy.optimize.fmin` definition. Returns ------- ndarray *CIE XYZ* tristimulus values under the *CIE 1964 10 Degree Standard Observer*. Warnings -------- There is no analytical reverse transformation from *OSA UCS* to :math:`Ljg` lightness, jaune (yellowness), and greenness to *CIE XYZ* tristimulus values, the current implementation relies on optimization using :func:`scipy.optimize.fmin` definition and thus has reduced precision and poor performance. Notes ----- +------------+-----------------------+--------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+====================+ | ``Ljg`` | ``L`` : [-100, 100] | ``L`` : [-1, 1] | | | | | | | ``j`` : [-100, 100] | ``j`` : [-1, 1] | | | | | | | ``g`` : [-100, 100] | ``g`` : [-1, 1] | +------------+-----------------------+--------------------+ +------------+-----------------------+--------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+====================+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+--------------------+ - *OSA UCS* uses the *CIE 1964 10 Degree Standard Observer*. References ---------- :cite:`Cao2013`, :cite:`Moroney2003` Examples -------- >>> import numpy as np >>> Ljg = np.array([-3.00499790, 2.99713697, -9.66784231]) >>> OSA_UCS_to_XYZ(Ljg) # doctest: +ELLIPSIS array([ 20.6540240..., 12.1972369..., 5.1369372...]) """ Ljg = to_domain_100(Ljg) shape = Ljg.shape Ljg = np.atleast_1d(Ljg.reshape([-1, 3])) optimisation_settings = {'disp': False} if optimisation_parameters is not None: optimisation_settings.update(optimisation_parameters) def error_function(XYZ, Ljg): """ Error function. """ # Error must be computed in "reference" domain and range. with domain_range_scale('ignore'): error = np.linalg.norm(XYZ_to_OSA_UCS(XYZ) - Ljg) return error x_0 = np.array([30, 30, 30]) XYZ = np.array([ fmin(error_function, x_0, (Ljg_i, ), **optimisation_settings) for Ljg_i in Ljg ]) return from_range_100(XYZ.reshape(shape))
def chromatic_adaptation_Fairchild1990( XYZ_1: ArrayLike, XYZ_n: ArrayLike, XYZ_r: ArrayLike, Y_n: FloatingOrArrayLike, discount_illuminant: Boolean = False, ) -> NDArray: """ Adapt given stimulus *CIE XYZ_1* tristimulus values from test viewing conditions to reference viewing conditions using *Fairchild (1990)* chromatic adaptation model. Parameters ---------- XYZ_1 *CIE XYZ_1* tristimulus values of test sample / stimulus. XYZ_n Test viewing condition *CIE XYZ_n* tristimulus values of whitepoint. XYZ_r Reference viewing condition *CIE XYZ_r* tristimulus values of whitepoint. Y_n Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`. discount_illuminant Truth value indicating if the illuminant should be discounted. Returns ------- :class:`numpy.ndarray` Adapted *CIE XYZ_2* tristimulus values of stimulus. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_1`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_n`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_r`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_2`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild1991a`, :cite:`Fairchild2013s` Examples -------- >>> XYZ_1 = np.array([19.53, 23.07, 24.97]) >>> XYZ_n = np.array([111.15, 100.00, 35.20]) >>> XYZ_r = np.array([94.81, 100.00, 107.30]) >>> Y_n = 200 >>> chromatic_adaptation_Fairchild1990(XYZ_1, XYZ_n, XYZ_r, Y_n) ... # doctest: +ELLIPSIS array([ 23.3252634..., 23.3245581..., 76.1159375...]) """ XYZ_1 = to_domain_100(XYZ_1) XYZ_n = to_domain_100(XYZ_n) XYZ_r = to_domain_100(XYZ_r) Y_n = as_float_array(Y_n) LMS_1 = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_1) LMS_n = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_n) LMS_r = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_r) p_LMS = degrees_of_adaptation( LMS_1, Y_n, discount_illuminant=discount_illuminant ) a_LMS_1 = p_LMS / LMS_n a_LMS_2 = p_LMS / LMS_r A_1 = row_as_diagonal(a_LMS_1) A_2 = row_as_diagonal(a_LMS_2) LMSp_1 = vector_dot(A_1, LMS_1) c = 0.219 - 0.0784 * np.log10(Y_n) C = row_as_diagonal(tstack([c, c, c])) LMS_a = vector_dot(C, LMSp_1) LMSp_2 = vector_dot(np.linalg.inv(C), LMS_a) LMS_c = vector_dot(np.linalg.inv(A_2), LMSp_2) XYZ_c = vector_dot(MATRIX_RGB_TO_XYZ_FAIRCHILD1990, LMS_c) return from_range_100(XYZ_c)
def XYZ_to_OSA_UCS(XYZ): """ Converts from *CIE XYZ* tristimulus values under the *CIE 1964 10 Degree Standard Observer* to *OSA UCS* colourspace. The lightness axis, *L* is usually in range [-9, 5] and centered around middle gray (Munsell N/6). The yellow-blue axis, *j* is usually in range [-15, 15]. The red-green axis, *g* is usually in range [-20, 15]. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values under the *CIE 1964 10 Degree Standard Observer*. Returns ------- ndarray *OSA UCS* :math:`Ljg` lightness, jaune (yellowness), and greenness. Notes ----- +------------+-----------------------+--------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+====================+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+--------------------+ +------------+-----------------------+--------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+====================+ | ``Ljg`` | ``L`` : [-100, 100] | ``L`` : [-1, 1] | | | | | | | ``j`` : [-100, 100] | ``j`` : [-1, 1] | | | | | | | ``g`` : [-100, 100] | ``g`` : [-1, 1] | +------------+-----------------------+--------------------+ - *OSA UCS* uses the *CIE 1964 10 Degree Standard Observer*. References ---------- :cite:`Cao2013`, :cite:`Moroney2003` Examples -------- >>> import numpy as np >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) * 100 >>> XYZ_to_OSA_UCS(XYZ) # doctest: +ELLIPSIS array([-3.0049979..., 2.9971369..., -9.6678423...]) """ XYZ = to_domain_100(XYZ) x, y, Y = tsplit(XYZ_to_xyY(XYZ)) Y_0 = Y * (4.4934 * x ** 2 + 4.3034 * y ** 2 - 4.276 * x * y - 1.3744 * x - 2.5643 * y + 1.8103) o_3 = 1 / 3 Y_0_es = spow(Y_0, o_3) - 2 / 3 # Gracefully handles Y_0 < 30. Y_0_s = Y_0 - 30 Lambda = 5.9 * (Y_0_es + 0.042 * spow(Y_0_s, o_3)) RGB = dot_vector(M_XYZ_TO_RGB_OSA_UCS, XYZ) RGB_3 = spow(RGB, 1 / 3) C = Lambda / (5.9 * Y_0_es) L = (Lambda - 14.4) / spow(2, 1 / 2) j = C * np.dot(RGB_3, np.array([1.7, 8, -9.7])) g = C * np.dot(RGB_3, np.array([-13.7, 17.7, -4])) Ljg = tstack([L, j, g]) return from_range_100(Ljg)
def delta_E_CIE1994(Lab_1, Lab_2, textiles=False): """ Returns the difference :math:`\\Delta E_{94}` between two given *CIE L\\*a\\*b\\** colourspace arrays using *CIE 1994* recommendation. Parameters ---------- Lab_1 : array_like *CIE L\\*a\\*b\\** colourspace array 1. Lab_2 : array_like *CIE L\\*a\\*b\\** colourspace array 2. textiles : bool, optional Textiles application specific parametric factors :math:`k_L=2,\\ k_C=k_H=1,\\ k_1=0.048,\\ k_2=0.014` weights are used instead of :math:`k_L=k_C=k_H=1,\\ k_1=0.045,\\ k_2=0.015`. Returns ------- numeric or ndarray Colour difference :math:`\\Delta E_{94}`. Notes ----- +------------+-----------------------+-------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===================+ | ``Lab_1`` | ``L_1`` : [0, 100] | ``L_1`` : [0, 1] | | | | | | | ``a_1`` : [-100, 100] | ``a_1`` : [-1, 1] | | | | | | | ``b_1`` : [-100, 100] | ``b_1`` : [-1, 1] | +------------+-----------------------+-------------------+ | ``Lab_2`` | ``L_2`` : [0, 100] | ``L_2`` : [0, 1] | | | | | | | ``a_2`` : [-100, 100] | ``a_2`` : [-1, 1] | | | | | | | ``b_2`` : [-100, 100] | ``b_2`` : [-1, 1] | +------------+-----------------------+-------------------+ - *CIE 1994* colour differences are not symmetrical: difference between ``Lab_1`` and ``Lab_2`` may not be the same as difference between ``Lab_2`` and ``Lab_1`` thus one colour must be understood to be the reference against which a sample colour is compared. References ---------- :cite:`Lindbloom2011a` Examples -------- >>> Lab_1 = np.array([100.00000000, 21.57210357, 272.22819350]) >>> Lab_2 = np.array([100.00000000, 426.67945353, 72.39590835]) >>> delta_E_CIE1994(Lab_1, Lab_2) # doctest: +ELLIPSIS 83.7792255... >>> delta_E_CIE1994(Lab_1, Lab_2, textiles=True) # doctest: +ELLIPSIS 88.3355530... """ L_1, a_1, b_1 = tsplit(to_domain_100(Lab_1)) L_2, a_2, b_2 = tsplit(to_domain_100(Lab_2)) k_1 = 0.048 if textiles else 0.045 k_2 = 0.014 if textiles else 0.015 k_L = 2 if textiles else 1 k_C = 1 k_H = 1 C_1 = np.hypot(a_1, b_1) C_2 = np.hypot(a_2, b_2) s_L = 1 s_C = 1 + k_1 * C_1 s_H = 1 + k_2 * C_1 delta_L = L_1 - L_2 delta_C = C_1 - C_2 delta_A = a_1 - a_2 delta_B = b_1 - b_2 delta_H = np.sqrt(delta_A ** 2 + delta_B ** 2 - delta_C ** 2) L = (delta_L / (k_L * s_L)) ** 2 C = (delta_C / (k_C * s_C)) ** 2 H = (delta_H / (k_H * s_H)) ** 2 d_E = np.sqrt(L + C + H) return d_E
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)