def XYZ_to_IGPGTG(XYZ): """ Converts from *CIE XYZ* tristimulus values to :math:`I_GP_GT_G` colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- ndarray :math:`I_GP_GT_G` colourspace array. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``IGPGTG`` | ``IG`` : [0, 1] | ``IG`` : [0, 1] | | | | | | | ``PG`` : [-1, 1] | ``PG`` : [-1, 1]| | | | | | | ``TG`` : [-1, 1] | ``TG`` : [-1, 1]| +------------+-----------------------+-----------------+ - Input *CIE XYZ* tristimulus values must be adapted to *CIE Standard Illuminant D Series* *D65*. References ---------- :cite:`Hellwig2020` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_IGPGTG(XYZ) # doctest: +ELLIPSIS array([ 0.4242125..., 0.1863249..., 0.1068922...]) """ XYZ = to_domain_1(XYZ) LMS = vector_dot(MATRIX_IGPGTG_XYZ_TO_LMS, XYZ) LMS_prime = spow(LMS / np.array([18.36, 21.46, 19435]), 0.427) IGPGTG = vector_dot(MATRIX_IGPGTG_LMS_TO_IGPGTG, LMS_prime) return from_range_1(IGPGTG)
def XYZ_to_IPT(XYZ): """ Converts from *CIE XYZ* tristimulus values to *IPT* colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- ndarray *IPT* colourspace array. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``IPT`` | ``I`` : [0, 1] | ``I`` : [0, 1] | | | | | | | ``P`` : [-1, 1] | ``P`` : [-1, 1] | | | | | | | ``T`` : [-1, 1] | ``T`` : [-1, 1] | +------------+-----------------------+-----------------+ - Input *CIE XYZ* tristimulus values must be adapted to *CIE Standard Illuminant D Series* *D65*. References ---------- :cite:`Fairchild2013y` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_IPT(XYZ) # doctest: +ELLIPSIS array([ 0.3842619..., 0.3848730..., 0.1888683...]) """ XYZ = to_domain_1(XYZ) LMS = vector_dot(MATRIX_IPT_XYZ_TO_LMS, XYZ) LMS_prime = spow(LMS, 0.43) IPT = vector_dot(MATRIX_IPT_LMS_TO_IPT, LMS_prime) return from_range_1(IPT)
def objective_function(M, RGB, Jab): """ :math:`J_zA_zB_z` colourspace based objective function. """ M = np.reshape(M, [3, 3]) XYZ_t = vector_dot(RGB_COLOURSPACE_ACES2065_1.matrix_RGB_to_XYZ, vector_dot(M, RGB)) Jab_t = XYZ_to_JzAzBz(XYZ_t) return np.sum(euclidean_distance(Jab, Jab_t))
def objective_function(M, RGB, Lab): """ Objective function according to *RAW to ACES* v1. """ M = np.reshape(M, [3, 3]) XYZ_t = vector_dot(RGB_COLOURSPACE_ACES2065_1.matrix_RGB_to_XYZ, vector_dot(M, RGB)) Lab_t = XYZ_to_Lab(XYZ_t, RGB_COLOURSPACE_ACES2065_1.whitepoint) return np.linalg.norm(Lab_t - Lab)
def IGPGTG_to_XYZ(IGPGTG): """ Converts from :math:`I_GP_GT_G` colourspace to *CIE XYZ* tristimulus values. Parameters ---------- IGPGTG : array_like :math:`I_GP_GT_G` colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``IGPGTG`` | ``IG`` : [0, 1] | ``IG`` : [0, 1] | | | | | | | ``PG`` : [-1, 1] | ``PG`` : [-1, 1]| | | | | | | ``TG`` : [-1, 1] | ``TG`` : [-1, 1]| +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+-----------------+ References ---------- :cite:`Hellwig2020` Examples -------- >>> IGPGTG = np.array([0.42421258, 0.18632491, 0.10689223]) >>> IGPGTG_to_XYZ(IGPGTG) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ IGPGTG = to_domain_1(IGPGTG) LMS = vector_dot(MATRIX_IGPGTG_IGPGTG_TO_LMS, IGPGTG) LMS_prime = spow(LMS, 1 / 0.427) * np.array([18.36, 21.46, 19435]) XYZ = vector_dot(MATRIX_IGPGTG_LMS_TO_XYZ, LMS_prime) return from_range_1(XYZ)
def IPT_to_XYZ(IPT): """ Converts from *IPT* colourspace to *CIE XYZ* tristimulus values. Parameters ---------- IPT : array_like *IPT* colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``IPT`` | ``I`` : [0, 1] | ``I`` : [0, 1] | | | | | | | ``P`` : [-1, 1] | ``P`` : [-1, 1] | | | | | | | ``T`` : [-1, 1] | ``T`` : [-1, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+-----------------+ References ---------- :cite:`Fairchild2013y` Examples -------- >>> IPT = np.array([0.38426191, 0.38487306, 0.18886838]) >>> IPT_to_XYZ(IPT) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ IPT = to_domain_1(IPT) LMS = vector_dot(MATRIX_IPT_IPT_TO_LMS, IPT) LMS_prime = spow(LMS, 1 / 0.43) XYZ = vector_dot(MATRIX_IPT_LMS_TO_XYZ, LMS_prime) return from_range_1(XYZ)
def rgb_to_RGB(rgb): """ Converts given *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace array to *RGB* array. Parameters ---------- rgb : array_like *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace array. Returns ------- 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_rgb(RGB): """ Converts given *RGB* array to *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace. Parameters ---------- RGB : array_like *RGB* array. Returns ------- 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, CAT02_INVERSE_CAT), RGB) return rgb
def XYZ_to_RGB_LLAB(XYZ): """ Converts from *CIE XYZ* tristimulus values to normalised cone responses. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- 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 RGB_to_YCoCg(RGB): """ Converts an array of *R'G'B'* values to the corresponding *YCoCg* colour encoding values array. Parameters ---------- RGB : array_like Input *R'G'B'* array. Returns ------- 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 XYZ_to_LMS_ATD95(XYZ): """ Converts from *CIE XYZ* tristimulus values to *LMS* cone responses. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- 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 = spow(LMS, 0.7) LMS += np.array([0.024, 0.036, 0.31]) return LMS
def YCoCg_to_RGB(YCoCg): """ Converts an array of *YCoCg* colour encoding values to the corresponding *R'G'B'* values array. Parameters ---------- YCoCg : array_like *YCoCg* colour encoding array. Returns ------- 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 test_vector_dot(self): """ Tests :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 apply(self, RGB): """ Applies the *Matrix* transform to given *RGB* array. Parameters ---------- RGB : array_like *RGB* array to apply the *Matrix* transform to. Returns ------- ndarray Transformed *RGB* array. Examples -------- >>> array = np.array([[ 1.45143932, -0.23651075, -0.21492857], ... [-0.07655377, 1.1762297 , -0.09967593], ... [ 0.00831615, -0.00603245, 0.9977163 ]]) >>> M = Matrix(array=array) >>> RGB = [0.3, 0.4, 0.5] >>> M.apply(RGB) array([ 0.23336321, 0.39768778, 0.49894002]) """ RGB = np.asarray(RGB) if self.array.shape == (3, 4): R, G, B = tsplit(RGB) RGB = tstack([R, G, B, np.ones(R.shape)]) return vector_dot(self.array, RGB)
def chromatic_adaptation(RGB, RGB_0, RGB_0r, Y, D=1): """ Applies chromatic adaptation to given *RGB* normalised cone responses array. Parameters ---------- RGB : array_like *RGB* normalised cone responses array of test sample / stimulus. RGB_0 : array_like *RGB* normalised cone responses array of reference white. RGB_0r : array_like *RGB* normalised cone responses array of reference illuminant *CIE Standard Illuminant D Series* *D65*. Y : numeric or array_like Tristimulus values :math:`Y` of the stimulus. D : numeric or array_like, optional *Discounting-the-Illuminant* factor normalised to domain [0, 1]. Returns ------- 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) 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_10_degree_cmfs_to_LMS_10_degree_cmfs(wavelength): """ Converts *Stiles & Burch 1959 10 Degree RGB CMFs* colour matching functions into the *Stockman & Sharpe 10 Degree Cone Fundamentals* spectral sensitivity functions. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\\lambda` in nm. Returns ------- 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 RGB_10_degree_cmfs_to_XYZ_10_degree_cmfs(wavelength): """ Converts *Stiles & Burch 1959 10 Degree RGB CMFs* colour matching functions into the *CIE 1964 10 Degree Standard Observer* colour matching functions. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\\lambda` in nm. Returns ------- 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_2_degree_cmfs_to_XYZ_2_degree_cmfs(wavelength): """ Converts *Stockman & Sharpe 2 Degree Cone Fundamentals* colour matching functions into the *CIE 2012 2 Degree Standard Observer* colour matching functions. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\\lambda` in nm. Returns ------- 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 LMS_10_degree_cmfs_to_XYZ_10_degree_cmfs(wavelength): """ Converts *Stockman & Sharpe 10 Degree Cone Fundamentals* colour matching functions into the *CIE 2012 10 Degree Standard Observer* colour matching functions. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\\lambda` in nm. Returns ------- 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 XYZ_to_RGB_CIE1994(XYZ): """ Converts from *CIE XYZ* tristimulus values to cone responses. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- 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_Nayatani95(XYZ): """ Converts from *CIE XYZ* tristimulus values to cone responses. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- 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): """ Converts from cone responses to *CIE XYZ* tristimulus values. Parameters ---------- RGB : array_like Cone responses. Returns ------- 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_Fairchild1990(XYZ): """ Converts from *CIE XYZ* tristimulus values to cone responses. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- 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 RGB_to_XYZ_Fairchild1990(RGB): """ Converts from cone responses to *CIE XYZ* tristimulus values. Parameters ---------- RGB : array_like Cone responses. Returns ------- 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(XYZ): """ Converts from *CIE XYZ* tristimulus values to *Hunt-Pointer-Estevez* :math:`\\rho\\gamma\\beta` colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- 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 XYZ_to_RLAB(XYZ, XYZ_n, Y_n, sigma=VIEWING_CONDITIONS_RLAB['Average'], D=D_FACTOR_RLAB['Hard Copy Images']): """ Computes the *RLAB* model color appearance 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_n : numeric or array_like Absolute adapting luminance in :math:`cd/m^2`. sigma : numeric or array_like, optional Relative luminance of the surround, see :attr:`colour.VIEWING_CONDITIONS_RLAB` for reference. D : numeric or array_like, optional *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) / (LMS_n[0] + LMS_n[1] + LMS_n[2]) 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 aR = row_as_diagonal(LMS_a_L) M = matrix_dot(matrix_dot(MATRIX_R, aR), 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 = 430 * (spow(X_ref, sigma) - spow(Y_ref, sigma)) bR = 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, from_range_degrees(hR), sR, None, aR, bR)
def RGB_2_degree_cmfs_to_XYZ_2_degree_cmfs(wavelength): """ Converts *Wright & Guild 1931 2 Degree RGB CMFs* colour matching functions into the *CIE 1931 2 Degree Standard Observer* colour matching functions. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\\lambda` in nm. Returns ------- ndarray *CIE 1931 2 Degree Standard Observer* spectral tristimulus values. Notes ----- - Data for the *CIE 1931 2 Degree Standard Observer* already exists, this definition is intended for educational purpose. References ---------- :cite:`Wyszecki2000bg` Examples -------- >>> from colour.utilities import numpy_print_options >>> with numpy_print_options(suppress=True): ... RGB_2_degree_cmfs_to_XYZ_2_degree_cmfs(700) # doctest: +ELLIPSIS array([ 0.0113577..., 0.004102 , 0. ]) """ cmfs = MSDS_CMFS_RGB['Wright & Guild 1931 2 Degree RGB CMFs'] rgb_bar = cmfs[wavelength] rgb = rgb_bar / np.sum(rgb_bar) M1 = np.array([ [0.49000, 0.31000, 0.20000], [0.17697, 0.81240, 0.01063], [0.00000, 0.01000, 0.99000], ]) M2 = np.array([ [0.66697, 1.13240, 1.20063], [0.66697, 1.13240, 1.20063], [0.66697, 1.13240, 1.20063], ]) xyz = vector_dot(M1, rgb) xyz /= vector_dot(M2, rgb) x, y, z = xyz[..., 0], xyz[..., 1], xyz[..., 2] V = SDS_LEFS_PHOTOPIC['CIE 1924 Photopic Standard Observer'].copy() V.align(cmfs.shape) L = V[wavelength] x_bar = x / y * L y_bar = L z_bar = z / y * L xyz_bar = tstack([x_bar, y_bar, z_bar]) return xyz_bar
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 = 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 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 = 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 degrees_of_adaptation(LMS, Y_n, v=1 / 3, discount_illuminant=False): """ Computes the degrees of adaptation :math:`p_L`, :math:`p_M` and :math:`p_S`. Parameters ---------- LMS : array_like Cone responses. Y_n : numeric or array_like Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`. v : numeric or array_like, optional Exponent :math:`v`. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- 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, y): """ Computes the :math:`m_E` term. """ return (3 * (x / y)) / (L / L_E + M / M_E + S / S_E) def P_c(x): """ Computes 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