def XYZ_to_RGB_LLAB(XYZ: ArrayLike) -> NDArray: """ Convert from *CIE XYZ* tristimulus values to normalised cone responses. Parameters ---------- XYZ *CIE XYZ* tristimulus values. Returns ------- :class:`numpy.ndarray` Normalised cone responses. Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_to_RGB_LLAB(XYZ) # doctest: +ELLIPSIS array([ 0.9414279..., 1.0404012..., 1.0897088...]) """ _X, Y, _Z = tsplit(XYZ) Y = tstack([Y, Y, Y]) XYZ_n = XYZ / Y return vector_dot(MATRIX_XYZ_TO_RGB_LLAB, XYZ_n)
def test_vector_dot(self): """Test :func:`colour.utilities.array.vector_dot` definition.""" m = np.array( [ [0.7328, 0.4296, -0.1624], [-0.7036, 1.6975, 0.0061], [0.0030, 0.0136, 0.9834], ] ) m = np.reshape(np.tile(m, (6, 1)), (6, 3, 3)) v = np.array([0.20654008, 0.12197225, 0.05136952]) v = np.tile(v, (6, 1)) np.testing.assert_almost_equal( vector_dot(m, v), np.array( [ [0.19540944, 0.06203965, 0.05279523], [0.19540944, 0.06203965, 0.05279523], [0.19540944, 0.06203965, 0.05279523], [0.19540944, 0.06203965, 0.05279523], [0.19540944, 0.06203965, 0.05279523], [0.19540944, 0.06203965, 0.05279523], ] ), decimal=7, )
def XYZ_to_LMS_ATD95(XYZ: ArrayLike) -> NDArray: """ Convert from *CIE XYZ* tristimulus values to *LMS* cone responses. Parameters ---------- XYZ *CIE XYZ* tristimulus values. Returns ------- :class:`numpy.ndarray` *LMS* cone responses. Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_to_LMS_ATD95(XYZ) # doctest: +ELLIPSIS array([ 6.2283272..., 7.4780666..., 3.8859772...]) """ LMS = vector_dot( [ [0.2435, 0.8524, -0.0516], [-0.3954, 1.1642, 0.0837], [0.0000, 0.0400, 0.6225], ], XYZ, ) LMS *= np.array([0.66, 1.0, 0.43]) LMS_p = spow(LMS, 0.7) LMS_p += np.array([0.024, 0.036, 0.31]) return as_float_array(LMS_p)
def RGB_to_rgb(RGB: ArrayLike) -> NDArray: """ Convert given *RGB* array to *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace. Parameters ---------- RGB *RGB* array. Returns ------- :class:`numpy.ndarray` *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace array. Examples -------- >>> RGB = np.array([19.99370783, 20.00393634, 20.01326387]) >>> RGB_to_rgb(RGB) # doctest: +ELLIPSIS array([ 19.9969397..., 20.0018612..., 20.0135053...]) """ rgb = vector_dot(matrix_dot(MATRIX_XYZ_TO_HPE, CAT_INVERSE_CAT02), RGB) return rgb
def rgb_to_RGB(rgb: ArrayLike) -> NDArray: """ Convert given *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace array to *RGB* array. Parameters ---------- rgb *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace array. Returns ------- :class:`numpy.ndarray` *RGB* array. Examples -------- >>> rgb = np.array([19.99693975, 20.00186123, 20.01350530]) >>> rgb_to_RGB(rgb) # doctest: +ELLIPSIS array([ 19.9937078..., 20.0039363..., 20.0132638...]) """ RGB = vector_dot(matrix_dot(CAT_CAT02, MATRIX_HPE_TO_XYZ), rgb) return RGB
def RGB_to_YCoCg(RGB: ArrayLike) -> NDArray: """ Convert an array of *R'G'B'* values to the corresponding *YCoCg* colour encoding values array. Parameters ---------- RGB Input *R'G'B'* array. Returns ------- :class:`numpy.ndarray` *YCoCg* colour encoding array. References ---------- :cite:`Malvar2003` Examples -------- >>> RGB_to_YCoCg(np.array([1.0, 1.0, 1.0])) array([ 1., 0., 0.]) >>> RGB_to_YCoCg(np.array([0.75, 0.5, 0.5])) array([ 0.5625, 0.125 , -0.0625]) """ return vector_dot(MATRIX_RGB_TO_YCOCG, RGB)
def YCoCg_to_RGB(YCoCg: ArrayLike) -> NDArray: """ Convert an array of *YCoCg* colour encoding values to the corresponding *R'G'B'* values array. Parameters ---------- YCoCg *YCoCg* colour encoding array. Returns ------- :class:`numpy.ndarray` Output *R'G'B'* array. References ---------- :cite:`Malvar2003` Examples -------- >>> YCoCg_to_RGB(np.array([1.0, 0.0, 0.0])) array([ 1., 1., 1.]) >>> YCoCg_to_RGB(np.array([0.5625, 0.125, -0.0625])) array([ 0.75, 0.5 , 0.5 ]) """ return vector_dot(MATRIX_YCOCG_TO_RGB, YCoCg)
def chromatic_adaptation( RGB: ArrayLike, RGB_0: ArrayLike, RGB_0r: ArrayLike, Y: FloatingOrArrayLike, D: FloatingOrArrayLike = 1, ) -> NDArray: """ Apply chromatic adaptation to given *RGB* normalised cone responses array. Parameters ---------- RGB *RGB* normalised cone responses array of test sample / stimulus. RGB_0 *RGB* normalised cone responses array of reference white. RGB_0r *RGB* normalised cone responses array of reference illuminant *CIE Standard Illuminant D Series* *D65*. Y Tristimulus values :math:`Y` of the stimulus. D *Discounting-the-Illuminant* factor normalised to domain [0, 1]. Returns ------- :class:`numpy.ndarray` Adapted *CIE XYZ* tristimulus values. Examples -------- >>> RGB = np.array([0.94142795, 1.04040120, 1.08970885]) >>> RGB_0 = np.array([0.94146023, 1.04039386, 1.08950293]) >>> RGB_0r = np.array([0.94146023, 1.04039386, 1.08950293]) >>> Y = 20.0 >>> chromatic_adaptation(RGB, RGB_0, RGB_0r, Y) # doctest: +ELLIPSIS array([ 19.01, 20. , 21.78]) """ R, G, B = tsplit(RGB) R_0, G_0, B_0 = tsplit(RGB_0) R_0r, G_0r, B_0r = tsplit(RGB_0r) Y = as_float_array(Y) D = as_float_array(D) beta = spow(B_0 / B_0r, 0.0834) R_r = (D * (R_0r / R_0) + 1 - D) * R G_r = (D * (G_0r / G_0) + 1 - D) * G B_r = (D * (B_0r / spow(B_0, beta)) + 1 - D) * spow(B, beta) RGB_r = tstack([R_r, G_r, B_r]) Y = tstack([Y, Y, Y]) XYZ_r = vector_dot(MATRIX_RGB_TO_XYZ_LLAB, RGB_r * Y) return XYZ_r
def RGB_to_IHLS(RGB: ArrayLike) -> NDArray: """ Convert from *RGB* colourspace to *IHLS* (Improved HLS) colourspace. Parameters ---------- RGB *RGB* colourspace array. Returns ------- :class:`numpy.ndarray` *HYS* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HYS`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Hanbury2003` Examples -------- >>> RGB = np.array([0.45595571, 0.03039702, 0.04087245]) >>> RGB_to_IHLS(RGB) # doctest: +ELLIPSIS array([ 6.2616051..., 0.1216271..., 0.4255586...]) """ RGB = to_domain_1(RGB) R, G, B = tsplit(RGB) Y, C_1, C_2 = tsplit(vector_dot(MATRIX_RGB_TO_YC_1_C_2, RGB)) C = np.sqrt(C_1**2 + C_2**2) acos_C_1_C_2 = zeros(C.shape) acos_C_1_C_2[C != 0] = np.arccos(C_1[C != 0] / C[C != 0]) H = np.where(C_2 <= 0, acos_C_1_C_2, (np.pi * 2) - acos_C_1_C_2) S = np.maximum(np.maximum(R, G), B) - np.minimum(np.minimum(R, G), B) HYS = tstack([H, Y, S]) return from_range_1(HYS)
def IHLS_to_RGB(HYS: ArrayLike) -> NDArray: """ Convert from *IHLS* (Improved HLS) colourspace to *RGB* colourspace. Parameters ---------- HYS *IHLS* colourspace array. Returns ------- :class:`numpy.ndarray` *RGB* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HYS`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Hanbury2003` Examples -------- >>> HYS = np.array([6.26160518, 0.12162712, 0.42555869]) >>> IHLS_to_RGB(HYS) # doctest: +ELLIPSIS array([ 0.4559557..., 0.0303970..., 0.0408724...]) """ H, Y, S = tsplit(to_domain_1(HYS)) pi_3 = np.pi / 3 k = np.floor(H / (pi_3)) H_s = H - k * (pi_3) C = (np.sqrt(3) * S) / (2 * np.sin((2 * pi_3) - H_s)) C_1 = C * np.cos(H) C_2 = -C * np.sin(H) RGB = vector_dot(MATRIX_YC_1_C_2_TO_RGB, tstack([Y, C_1, C_2])) return from_range_1(RGB)
def apply(self, RGB: ArrayLike, *args: Any, **kwargs: Any) -> NDArray: """ Apply the *LUT* operator to given *RGB* array. Parameters ---------- RGB *RGB* array to apply the *LUT* operator transform to. Other Parameters ---------------- apply_offset_first Whether to apply the offset first and then the matrix. Returns ------- :class:`numpy.ndarray` Transformed *RGB* array. Examples -------- >>> matrix = np.array([[ 1.45143932, -0.23651075, -0.21492857], ... [-0.07655377, 1.1762297 , -0.09967593], ... [ 0.00831615, -0.00603245, 0.9977163 ]]) >>> M = LUTOperatorMatrix(matrix) >>> RGB = np.array([0.3, 0.4, 0.5]) >>> M.apply(RGB) # doctest: +ELLIPSIS array([ 0.2333632..., 0.3976877..., 0.4989400...]) """ RGB = as_float_array(RGB) apply_offset_first = kwargs.get("apply_offset_first", False) has_alpha_channel = RGB.shape[-1] == 4 M = self._matrix offset = self._offset if not has_alpha_channel: M = M[:3, :3] offset = offset[:3] if apply_offset_first: RGB += offset RGB = vector_dot(M, RGB) if not apply_offset_first: RGB += offset return RGB
def RGB_10_degree_cmfs_to_LMS_10_degree_cmfs( wavelength: FloatingOrArrayLike, ) -> NDArray: """ Convert *Stiles & Burch 1959 10 Degree RGB CMFs* colour matching functions into the *Stockman & Sharpe 10 Degree Cone Fundamentals* spectral sensitivity functions. Parameters ---------- wavelength Wavelength :math:`\\lambda` in nm. Returns ------- :class:`numpy.ndarray` *Stockman & Sharpe 10 Degree Cone Fundamentals* spectral tristimulus values. Notes ----- - Data for the *Stockman & Sharpe 10 Degree Cone Fundamentals* already exists, this definition is intended for educational purpose. References ---------- :cite:`CIETC1-362006a` Examples -------- >>> from colour.utilities import numpy_print_options >>> with numpy_print_options(suppress=True): ... RGB_10_degree_cmfs_to_LMS_10_degree_cmfs(700) # doctest: +ELLIPSIS array([ 0.0052860..., 0.0003252..., 0. ]) """ cmfs = MSDS_CMFS_RGB["Stiles & Burch 1959 10 Degree RGB CMFs"] rgb_bar = cmfs[wavelength] M = np.array([ [0.1923252690, 0.749548882, 0.0675726702], [0.0192290085, 0.940908496, 0.113830196], [0.0000000000, 0.0105107859, 0.991427669], ]) lms_bar = vector_dot(M, rgb_bar) lms_bar[..., -1][np.asarray(np.asarray(wavelength) > 505)] = 0 return lms_bar
def LMS_2_degree_cmfs_to_XYZ_2_degree_cmfs( wavelength: FloatingOrArrayLike, ) -> NDArray: """ Convert *Stockman & Sharpe 2 Degree Cone Fundamentals* colour matching functions into the *CIE 2012 2 Degree Standard Observer* colour matching functions. Parameters ---------- wavelength Wavelength :math:`\\lambda` in nm. Returns ------- :class:`numpy.ndarray` *CIE 2012 2 Degree Standard Observer* spectral tristimulus values. Notes ----- - Data for the *CIE 2012 2 Degree Standard Observer* already exists, this definition is intended for educational purpose. References ---------- :cite:`CVRLv` Examples -------- >>> from colour.utilities import numpy_print_options >>> with numpy_print_options(suppress=True): ... LMS_2_degree_cmfs_to_XYZ_2_degree_cmfs(700) # doctest: +ELLIPSIS array([ 0.0109677..., 0.0041959..., 0. ]) """ cmfs = MSDS_CMFS_LMS["Stockman & Sharpe 2 Degree Cone Fundamentals"] lms_bar = cmfs[wavelength] M = np.array([ [1.94735469, -1.41445123, 0.36476327], [0.68990272, 0.34832189, 0.00000000], [0.00000000, 0.00000000, 1.93485343], ]) xyz_bar = vector_dot(M, lms_bar) return xyz_bar
def RGB_10_degree_cmfs_to_XYZ_10_degree_cmfs( wavelength: FloatingOrArrayLike, ) -> NDArray: """ Convert *Stiles & Burch 1959 10 Degree RGB CMFs* colour matching functions into the *CIE 1964 10 Degree Standard Observer* colour matching functions. Parameters ---------- wavelength Wavelength :math:`\\lambda` in nm. Returns ------- :class:`numpy.ndarray` *CIE 1964 10 Degree Standard Observer* spectral tristimulus values. Notes ----- - Data for the *CIE 1964 10 Degree Standard Observer* already exists, this definition is intended for educational purpose. References ---------- :cite:`Wyszecki2000be` Examples -------- >>> from colour.utilities import numpy_print_options >>> with numpy_print_options(suppress=True): ... RGB_10_degree_cmfs_to_XYZ_10_degree_cmfs(700) # doctest: +ELLIPSIS array([ 0.0096432..., 0.0037526..., -0.0000041...]) """ cmfs = MSDS_CMFS_RGB["Stiles & Burch 1959 10 Degree RGB CMFs"] rgb_bar = cmfs[wavelength] M = np.array([ [0.341080, 0.189145, 0.387529], [0.139058, 0.837460, 0.073316], [0.000000, 0.039553, 2.026200], ]) xyz_bar = vector_dot(M, rgb_bar) return xyz_bar
def LMS_10_degree_cmfs_to_XYZ_10_degree_cmfs( wavelength: FloatingOrArrayLike, ) -> NDArray: """ Convert *Stockman & Sharpe 10 Degree Cone Fundamentals* colour matching functions into the *CIE 2012 10 Degree Standard Observer* colour matching functions. Parameters ---------- wavelength Wavelength :math:`\\lambda` in nm. Returns ------- :class:`numpy.ndarray` *CIE 2012 10 Degree Standard Observer* spectral tristimulus values. Notes ----- - Data for the *CIE 2012 10 Degree Standard Observer* already exists, this definition is intended for educational purpose. References ---------- :cite:`CVRLp` Examples -------- >>> from colour.utilities import numpy_print_options >>> with numpy_print_options(suppress=True): ... LMS_10_degree_cmfs_to_XYZ_10_degree_cmfs(700) # doctest: +ELLIPSIS array([ 0.0098162..., 0.0037761..., 0. ]) """ cmfs = MSDS_CMFS_LMS["Stockman & Sharpe 10 Degree Cone Fundamentals"] lms_bar = cmfs[wavelength] M = np.array([ [1.93986443, -1.34664359, 0.43044935], [0.69283932, 0.34967567, 0.00000000], [0.00000000, 0.00000000, 2.14687945], ]) xyz_bar = vector_dot(M, lms_bar) return xyz_bar
def matrix_post_adaptation_non_linear_response_compression( P_2: FloatingOrArrayLike, a: FloatingOrArrayLike, b: FloatingOrArrayLike) -> NDArray: """ Apply the post-adaptation non-linear-response compression matrix. Parameters ---------- P_2 Point :math:`P_2`. a Opponent colour dimension :math:`a`. b Opponent colour dimension :math:`b`. Returns ------- :class:`numpy.ndarray` Points :math:`P`. Examples -------- >>> P_2 = 24.2372054671 >>> a = -0.000624112068243 >>> b = -0.000506270106773 >>> matrix_post_adaptation_non_linear_response_compression(P_2, a, b) ... # doctest: +ELLIPSIS array([ 7.9463202..., 7.9471152..., 7.9489959...]) """ P_2 = as_float_array(P_2) a = as_float_array(a) b = as_float_array(b) RGB_a = (vector_dot( [ [460, 451, 288], [460, -891, -261], [460, -220, -6300], ], tstack([P_2, a, b]), ) / 1403) return RGB_a
def RGB_to_XYZ_Fairchild1990(RGB: ArrayLike) -> NDArray: """ Convert from cone responses to *CIE XYZ* tristimulus values. Parameters ---------- RGB Cone responses. Returns ------- :class:`numpy.ndarray` *CIE XYZ* tristimulus values. Examples -------- >>> RGB = np.array([22.12319350, 23.60542240, 22.92795340]) >>> RGB_to_XYZ_Fairchild1990(RGB) # doctest: +ELLIPSIS array([ 19.53, 23.07, 24.97]) """ return vector_dot(MATRIX_RGB_TO_XYZ_FAIRCHILD1990, RGB)
def XYZ_to_RGB_CIE1994(XYZ: ArrayLike) -> NDArray: """ Convert from *CIE XYZ* tristimulus values to cone responses. Parameters ---------- XYZ *CIE XYZ* tristimulus values. Returns ------- :class:`numpy.ndarray` Cone responses. Examples -------- >>> XYZ = np.array([28.00, 21.26, 5.27]) >>> XYZ_to_RGB_CIE1994(XYZ) # doctest: +ELLIPSIS array([ 25.8244273..., 18.6791422..., 4.8390194...]) """ return vector_dot(MATRIX_XYZ_TO_RGB_CIE1994, XYZ)
def XYZ_to_RGB_Fairchild1990(XYZ: ArrayLike) -> NDArray: """ Convert from *CIE XYZ* tristimulus values to cone responses. Parameters ---------- XYZ *CIE XYZ* tristimulus values. Returns ------- :class:`numpy.ndarray` Cone responses. Examples -------- >>> XYZ = np.array([19.53, 23.07, 24.97]) >>> XYZ_to_RGB_Fairchild1990(XYZ) # doctest: +ELLIPSIS array([ 22.1231935..., 23.6054224..., 22.9279534...]) """ return vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ)
def XYZ_to_RGB_Nayatani95(XYZ: ArrayLike) -> NDArray: """ Convert from *CIE XYZ* tristimulus values to cone responses. Parameters ---------- XYZ *CIE XYZ* tristimulus values. Returns ------- :class:`numpy.ndarray` Cone responses. Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_to_RGB_Nayatani95(XYZ) # doctest: +ELLIPSIS array([ 20.0005206..., 19.999783 ..., 19.9988316...]) """ return vector_dot(MATRIX_XYZ_TO_RGB_NAYATANI95, XYZ)
def RGB_to_XYZ_CIE1994(RGB: ArrayLike) -> NDArray: """ Convert from cone responses to *CIE XYZ* tristimulus values. Parameters ---------- RGB Cone responses. Returns ------- :class:`numpy.ndarray` *CIE XYZ* tristimulus values. Examples -------- >>> RGB = np.array([25.82442730, 18.67914220, 4.83901940]) >>> RGB_to_XYZ_CIE1994(RGB) # doctest: +ELLIPSIS array([ 28. , 21.26, 5.27]) """ return vector_dot(MATRIX_RGB_TO_XYZ_CIE1994, RGB)
def XYZ_to_rgb(XYZ: ArrayLike) -> NDArray: """ Convert from *CIE XYZ* tristimulus values to *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace. Parameters ---------- XYZ *CIE XYZ* tristimulus values. Returns ------- :class:`numpy.ndarray` *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace. Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_to_rgb(XYZ) # doctest: +ELLIPSIS array([ 19.4743367..., 20.3101217..., 21.78 ]) """ return vector_dot(MATRIX_XYZ_TO_HPE, XYZ)
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 CIECAM02_to_XYZ( specification: CAM_Specification_CIECAM02, XYZ_w: ArrayLike, L_A: FloatingOrArrayLike, Y_b: FloatingOrArrayLike, surround: InductionFactors_CIECAM02 = VIEWING_CONDITIONS_CIECAM02[ "Average"], discount_illuminant: Boolean = False, ) -> NDArray: """ Convert from *CIECAM02* specification to *CIE XYZ* tristimulus values. Parameters ---------- 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 *CIE XYZ* tristimulus values of reference white. L_A Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken to be 20% of the luminance of a white object in the scene). Y_b Luminous factor of background :math:`Y_b` such as :math:`Y_b = 100 x L_b / L_w` where :math:`L_w` is the luminance of the light source and :math:`L_b` is the luminance of the background. For viewing images, :math:`Y_b` can be the average :math:`Y` value for the pixels in the entire image, or frequently, a :math:`Y` value of 20, approximate an :math:`L^*` of 50 is used. surround Surround viewing conditions. discount_illuminant Discount the illuminant. Returns ------- :class:`numpy.ndarray` *CIE XYZ* tristimulus values. Raises ------ ValueError If neither *C* or *M* correlates have been defined in the ``CAM_Specification_CIECAM02`` argument. Notes ----- +----------------------------------+-----------------------\ +---------------+ | **Domain** | **Scale - Reference** \ | **Scale - 1** | +==================================+=======================\ +===============+ | ``CAM_Specification_CIECAM02.J`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.C`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.h`` | [0, 360] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.s`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.Q`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.M`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.H`` | [0, 360] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``XYZ_w`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ +-----------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +===========+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +-----------+-----------------------+---------------+ References ---------- :cite:`Fairchild2004c`, :cite:`Luo2013`, :cite:`Moroneya`, :cite:`Wikipedia2007a` Examples -------- >>> specification = CAM_Specification_CIECAM02(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 = astuple(specification) J = to_domain_100(J) C = to_domain_100(C) h = to_domain_degrees(h) M = to_domain_100(M) L_A = as_float_array(L_A) XYZ_w = to_domain_100(XYZ_w) _X_w, Y_w, _Z_w = tsplit(XYZ_w) n, F_L, N_bb, N_cb, z = viewing_condition_dependent_parameters( Y_b, Y_w, L_A) if has_only_nan(C) and not has_only_nan(M): C = M / spow(F_L, 0.25) elif has_only_nan(C): raise ValueError('Either "C" or "M" correlate must be defined in ' 'the "CAM_Specification_CIECAM02" argument!') # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform # sharpened *RGB* values. RGB_w = vector_dot(CAT_CAT02, XYZ_w) # Computing degree of adaptation :math:`D`. D = (degree_of_adaptation(surround.F, L_A) if not discount_illuminant else 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_inverse(C, J, n) # Computing eccentricity factor *e_t*. e_t = eccentricity_factor(h) # Computing achromatic response :math:`A` for the stimulus. A = achromatic_response_inverse(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_inverse(P_n, h)) # Applying post-adaptation non-linear response compression matrix. RGB_a = matrix_post_adaptation_non_linear_response_compression(P_2, a, b) # Applying inverse post-adaptation non-linear response compression. RGB_p = post_adaptation_non_linear_response_compression_inverse(RGB_a, F_L) # Converting to *Hunt-Pointer-Estevez* colourspace. RGB_c = rgb_to_RGB(RGB_p) # Applying inverse full chromatic adaptation. RGB = full_chromatic_adaptation_inverse(RGB_c, RGB_w, Y_w, D) # Converting *CMCCAT2000* transform sharpened *RGB* values to *CIE XYZ* # tristimulus values. XYZ = vector_dot(CAT_INVERSE_CAT02, RGB) return from_range_100(XYZ)
def XYZ_to_OSA_UCS(XYZ: ArrayLike) -> NDArray: """ Convert 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 *CIE XYZ* tristimulus values under the *CIE 1964 10 Degree Standard Observer*. Returns ------- :class:`numpy.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 = vector_dot(MATRIX_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 degrees_of_adaptation( LMS: ArrayLike, Y_n: FloatingOrArrayLike, v: FloatingOrArrayLike = 1 / 3, discount_illuminant: Boolean = False, ) -> NDArray: """ Compute the degrees of adaptation :math:`p_L`, :math:`p_M` and :math:`p_S`. Parameters ---------- LMS Cone responses. Y_n Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`. v Exponent :math:`v`. discount_illuminant Truth value indicating if the illuminant should be discounted. Returns ------- :class:`numpy.ndarray` Degrees of adaptation :math:`p_L`, :math:`p_M` and :math:`p_S`. Examples -------- >>> LMS = np.array([20.00052060, 19.99978300, 19.99883160]) >>> Y_n = 31.83 >>> degrees_of_adaptation(LMS, Y_n) # doctest: +ELLIPSIS array([ 0.9799324..., 0.9960035..., 1.0233041...]) >>> degrees_of_adaptation(LMS, Y_n, 1 / 3, True) array([ 1., 1., 1.]) """ LMS = as_float_array(LMS) if discount_illuminant: return ones(LMS.shape) Y_n = as_float_array(Y_n) v = as_float_array(v) L, M, S = tsplit(LMS) # E illuminant. LMS_E = vector_dot(CAT_VON_KRIES, ones(LMS.shape)) L_E, M_E, S_E = tsplit(LMS_E) Ye_n = spow(Y_n, v) def m_E(x: NDArray, y: NDArray) -> NDArray: """Compute the :math:`m_E` term.""" return (3 * (x / y)) / (L / L_E + M / M_E + S / S_E) def P_c(x: NDArray) -> NDArray: """Compute the :math:`P_L`, :math:`P_M` or :math:`P_S` terms.""" return (1 + Ye_n + x) / (1 + Ye_n + 1 / x) p_L = P_c(m_E(L, L_E)) p_M = P_c(m_E(M, M_E)) p_S = P_c(m_E(S, S_E)) p_LMS = tstack([p_L, p_M, p_S]) return p_LMS
def matrix_RGB_to_WSYBRG(cmfs: LMS_ConeFundamentals, primaries: RGB_DisplayPrimaries) -> NDArray: """ Compute the matrix transforming from *RGB* colourspace to opponent-colour space using *Machado et al. (2009)* method. Parameters ---------- cmfs *LMS* cone fundamentals colour matching functions. primaries *RGB* display primaries tri-spectral distributions. Returns ------- :class:`numpy.ndarray` Matrix transforming from *RGB* colourspace to opponent-colour space. Examples -------- >>> from colour.characterisation import MSDS_DISPLAY_PRIMARIES >>> from colour.colorimetry import MSDS_CMFS_LMS >>> cmfs = MSDS_CMFS_LMS['Stockman & Sharpe 2 Degree Cone Fundamentals'] >>> d_LMS = np.array([15, 0, 0]) >>> primaries = MSDS_DISPLAY_PRIMARIES['Apple Studio Display'] >>> matrix_RGB_to_WSYBRG( # doctest: +ELLIPSIS ... cmfs, primaries) array([[ 0.2126535..., 0.6704626..., 0.1168838...], [ 4.7095295..., 12.4862869..., -16.1958165...], [-11.1518474..., 15.2534789..., -3.1016315...]]) """ wavelengths = cmfs.wavelengths WSYBRG = vector_dot(MATRIX_LMS_TO_WSYBRG, cmfs.values) WS, YB, RG = tsplit(WSYBRG) # pylint: disable=E1102 primaries = reshape_msds( primaries, # type: ignore[assignment] cmfs.shape, extrapolator_kwargs={ "method": "Constant", "left": 0, "right": 0 }, ) R, G, B = tsplit(primaries.values) WS_R = np.trapz(R * WS, wavelengths) WS_G = np.trapz(G * WS, wavelengths) WS_B = np.trapz(B * WS, wavelengths) YB_R = np.trapz(R * YB, wavelengths) YB_G = np.trapz(G * YB, wavelengths) YB_B = np.trapz(B * YB, wavelengths) RG_R = np.trapz(R * RG, wavelengths) RG_G = np.trapz(G * RG, wavelengths) RG_B = np.trapz(B * RG, wavelengths) M_G = np.array([ [WS_R, WS_G, WS_B], [YB_R, YB_G, YB_B], [RG_R, RG_G, RG_B], ]) PWS = 1 / (WS_R + WS_G + WS_B) PYB = 1 / (YB_R + YB_G + YB_B) PRG = 1 / (RG_R + RG_G + RG_B) M_G *= np.array([PWS, PYB, PRG])[:, np.newaxis] return M_G
def XYZ_to_RLAB( XYZ: ArrayLike, XYZ_n: ArrayLike, Y_n: FloatingOrArrayLike, sigma: FloatingOrArrayLike = VIEWING_CONDITIONS_RLAB["Average"], D: FloatingOrArrayLike = D_FACTOR_RLAB["Hard Copy Images"], ) -> CAM_Specification_RLAB: """ Compute the *RLAB* model color appearance correlates. Parameters ---------- XYZ *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_n *CIE XYZ* tristimulus values of reference white. Y_n Absolute adapting luminance in :math:`cd/m^2`. sigma Relative luminance of the surround, see :attr:`colour.VIEWING_CONDITIONS_RLAB` for reference. D *Discounting-the-Illuminant* factor normalised to domain [0, 1]. Returns ------- CAM_Specification_RLAB *RLAB* 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** | +==============================+=======================\ +===============+ | ``CAM_Specification_RLAB.h`` | [0, 360] \ | [0, 1] | +------------------------------+-----------------------\ +---------------+ References ---------- :cite:`Fairchild1996a`, :cite:`Fairchild2013w` Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_n = np.array([109.85, 100, 35.58]) >>> Y_n = 31.83 >>> sigma = VIEWING_CONDITIONS_RLAB['Average'] >>> D = D_FACTOR_RLAB['Hard Copy Images'] >>> XYZ_to_RLAB(XYZ, XYZ_n, Y_n, sigma, D) # doctest: +ELLIPSIS CAM_Specification_RLAB(J=49.8347069..., C=54.8700585..., \ h=286.4860208..., s=1.1010410..., HC=None, a=15.5711021..., \ b=-52.6142956...) """ XYZ = to_domain_100(XYZ) XYZ_n = to_domain_100(XYZ_n) Y_n = as_float_array(Y_n) D = as_float_array(D) sigma = as_float_array(sigma) # Converting to cone responses. LMS_n = XYZ_to_rgb(XYZ_n) # Computing the :math:`A` matrix. LMS_l_E = (3 * LMS_n) / np.sum(LMS_n, axis=-1)[..., np.newaxis] LMS_p_L = (1 + spow(Y_n[..., np.newaxis], 1 / 3) + LMS_l_E) / (1 + spow(Y_n[..., np.newaxis], 1 / 3) + (1 / LMS_l_E)) LMS_a_L = (LMS_p_L + D[..., np.newaxis] * (1 - LMS_p_L)) / LMS_n M = matrix_dot(matrix_dot(MATRIX_R, row_as_diagonal(LMS_a_L)), MATRIX_XYZ_TO_HPE) XYZ_ref = vector_dot(M, XYZ) X_ref, Y_ref, Z_ref = tsplit(XYZ_ref) # Computing the correlate of *Lightness* :math:`L^R`. LR = 100 * spow(Y_ref, sigma) # Computing opponent colour dimensions :math:`a^R` and :math:`b^R`. aR = as_float(430 * (spow(X_ref, sigma) - spow(Y_ref, sigma))) bR = as_float(170 * (spow(Y_ref, sigma) - spow(Z_ref, sigma))) # Computing the *hue* angle :math:`h^R`. hR = np.degrees(np.arctan2(bR, aR)) % 360 # TODO: Implement hue composition computation. # Computing the correlate of *chroma* :math:`C^R`. CR = np.hypot(aR, bR) # Computing the correlate of *saturation* :math:`s^R`. sR = CR / LR return CAM_Specification_RLAB( LR, CR, as_float(from_range_degrees(hR)), sR, None, aR, bR, )
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_CIECAM02( XYZ: ArrayLike, XYZ_w: ArrayLike, L_A: FloatingOrArrayLike, Y_b: FloatingOrArrayLike, surround: InductionFactors_CIECAM02 = VIEWING_CONDITIONS_CIECAM02[ "Average"], discount_illuminant: Boolean = False, ) -> CAM_Specification_CIECAM02: """ Compute the *CIECAM02* colour appearance model correlates from given *CIE XYZ* tristimulus values. Parameters ---------- XYZ *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_w *CIE XYZ* tristimulus values of reference white. L_A Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`, (often taken to be 20% of the luminance of a white object in the scene). Y_b Luminous factor of background :math:`Y_b` such as :math:`Y_b = 100 x L_b / L_w` where :math:`L_w` is the luminance of the light source and :math:`L_b` is the luminance of the background. For viewing images, :math:`Y_b` can be the average :math:`Y` value for the pixels in the entire image, or frequently, a :math:`Y` value of 20, approximate an :math:`L^*` of 50 is used. surround Surround viewing conditions induction factors. discount_illuminant Truth value indicating if the illuminant should be discounted. Returns ------- :class:`colour.CAM_Specification_CIECAM02` *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** | +==================================+=======================\ +===============+ | ``CAM_Specification_CIECAM02.J`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.C`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.h`` | [0, 360] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.s`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.Q`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.M`` | [0, 100] \ | [0, 1] | +----------------------------------+-----------------------\ +---------------+ | ``CAM_Specification_CIECAM02.H`` | [0, 400] \ | [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 = VIEWING_CONDITIONS_CIECAM02['Average'] >>> XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround) # doctest: +ELLIPSIS CAM_Specification_CIECAM02(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 = viewing_condition_dependent_parameters( Y_b, Y_w, L_A) # Converting *CIE XYZ* tristimulus values to *CMCCAT2000* transform # sharpened *RGB* values. RGB = vector_dot(CAT_CAT02, XYZ) RGB_w = vector_dot(CAT_CAT02, XYZ_w) # Computing degree of adaptation :math:`D`. D = (degree_of_adaptation(surround.F, L_A) if not discount_illuminant else 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 CAM_Specification_CIECAM02( as_float(from_range_100(J)), as_float(from_range_100(C)), as_float(from_range_degrees(h)), as_float(from_range_100(s)), as_float(from_range_100(Q)), as_float(from_range_100(M)), as_float(from_range_degrees(H, 400)), None, )