def test_n_dimensional_intermediate_lightness_function_CIE1976(self): """ Tests :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition n-dimensional arrays support. """ Y = 12.19722535 f_Y_Y_n = 0.495929964178047 np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(Y), f_Y_Y_n, decimal=7) Y = np.tile(Y, 6) f_Y_Y_n = np.tile(f_Y_Y_n, 6) np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(Y), f_Y_Y_n, decimal=7) Y = np.reshape(Y, (2, 3)) f_Y_Y_n = np.reshape(f_Y_Y_n, (2, 3)) np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(Y), f_Y_Y_n, decimal=7) Y = np.reshape(Y, (2, 3, 1)) f_Y_Y_n = np.reshape(f_Y_Y_n, (2, 3, 1)) np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(Y), f_Y_Y_n, decimal=7)
def test_nan_intermediate_lightness_function_CIE1976(self): """ Test :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition nan support. """ intermediate_lightness_function_CIE1976( np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]))
def test_nan_intermediate_lightness_function_CIE1976(self): """ Tests :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition nan support. """ intermediate_lightness_function_CIE1976( np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]))
def test_domain_range_scale_intermediate_lightness_function_CIE1976(self): """ Tests :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition domain and range scale support. """ f_Y_Y_n = intermediate_lightness_function_CIE1976(12.19722535, 100) for scale in ('reference', 1, 100): with domain_range_scale(scale): np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(12.19722535, 100), f_Y_Y_n, decimal=7)
def test_domain_range_scale_intermediate_lightness_function_CIE1976(self): """ Tests :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition domain and range scale support. """ f_Y_Y_n = intermediate_lightness_function_CIE1976(12.19722535, 100) for scale in ('reference', 1, 100): with domain_range_scale(scale): np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(12.19722535, 100), f_Y_Y_n, decimal=7)
def test_intermediate_lightness_function_CIE1976(self): """ Tests :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition. """ self.assertAlmostEqual( intermediate_lightness_function_CIE1976(12.19722535), 0.495929964178047, places=7) self.assertAlmostEqual( intermediate_lightness_function_CIE1976(23.04276781), 0.613072093530391, places=7) self.assertAlmostEqual( intermediate_lightness_function_CIE1976(6.15720079), 0.394876333449113, places=7)
def test_intermediate_lightness_function_CIE1976(self): """ Tests :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition. """ self.assertAlmostEqual( intermediate_lightness_function_CIE1976(12.19722535), 0.495929964178047, places=7) self.assertAlmostEqual( intermediate_lightness_function_CIE1976(23.04276781), 0.613072093530391, places=7) self.assertAlmostEqual( intermediate_lightness_function_CIE1976(6.15720079), 0.394876333449113, places=7)
def test_n_dimensional_intermediate_lightness_function_CIE1976(self): """ Tests :func:`colour.colorimetry.lightness.\ intermediate_lightness_function_CIE1976` definition n-dimensional arrays support. """ Y = 12.19722535 f_Y_Y_n = intermediate_lightness_function_CIE1976(Y) Y = np.tile(Y, 6) f_Y_Y_n = np.tile(f_Y_Y_n, 6) np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(Y), f_Y_Y_n, decimal=7) Y = np.reshape(Y, (2, 3)) f_Y_Y_n = np.reshape(f_Y_Y_n, (2, 3)) np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(Y), f_Y_Y_n, decimal=7) Y = np.reshape(Y, (2, 3, 1)) f_Y_Y_n = np.reshape(f_Y_Y_n, (2, 3, 1)) np.testing.assert_almost_equal( intermediate_lightness_function_CIE1976(Y), f_Y_Y_n, decimal=7)
def XYZ_to_Lab( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE XYZ* tristimulus values to *CIE L\\*a\\*b\\** colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like, optional Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- ndarray *CIE L\\*a\\*b\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Lab`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``a`` : [-100, 100] | ``a`` : [-1, 1] | | | | | | | ``b`` : [-100, 100] | ``b`` : [-1, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m` Examples -------- >>> import numpy as np >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_Lab(XYZ) # doctest: +ELLIPSIS array([ 41.5278752..., 52.6385830..., 26.9231792...]) """ X, Y, Z = tsplit(to_domain_1(XYZ)) X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) f_X_X_n = intermediate_lightness_function_CIE1976(X, X_n) f_Y_Y_n = intermediate_lightness_function_CIE1976(Y, Y_n) f_Z_Z_n = intermediate_lightness_function_CIE1976(Z, Z_n) L = 116 * f_Y_Y_n - 16 a = 500 * (f_X_X_n - f_Y_Y_n) b = 200 * (f_Y_Y_n - f_Z_Z_n) Lab = tstack([L, a, b]) return from_range_100(Lab)
def error_function( coefficients: ArrayLike, target: ArrayLike, cmfs: MultiSpectralDistributions, illuminant: SpectralDistribution, max_error: Optional[Floating] = None, additional_data: Boolean = False, ) -> Union[ Tuple[Floating, NDArray], Tuple[Floating, NDArray, NDArray, NDArray, NDArray], ]: """ Compute :math:`\\Delta E_{76}` between the target colour and the colour defined by given spectral model, along with its gradient. Parameters ---------- coefficients Dimensionless coefficients for *Jakob and Hanika (2019)* reflectance spectral model. target *CIE L\\*a\\*b\\** colourspace array of the target colour. cmfs Standard observer colour matching functions. illuminant Illuminant spectral distribution. max_error Raise ``StopMinimizationEarly`` if the error is smaller than this. The default is *None* and the function doesn't raise anything. additional_data If *True*, some intermediate calculations are returned, for use in correctness tests: R, XYZ and Lab. Returns ------- :class:`tuple` or :class:`tuple` Tuple of computed :math:`\\Delta E_{76}` error and gradient of error, i.e. the first derivatives of error with respect to the input coefficients or tuple of computed :math:`\\Delta E_{76}` error, gradient of error, computed spectral reflectance, *CIE XYZ* tristimulus values corresponding to ``R`` and *CIE L\\*a\\*b\\** colourspace array corresponding to ``XYZ``. Raises ------ StopMinimizationEarly Raised when the error is below ``max_error``. """ target = as_float_array(target) c_0, c_1, c_2 = as_float_array(coefficients) wv = np.linspace(0, 1, len(cmfs.shape)) U = c_0 * wv**2 + c_1 * wv + c_2 t1 = np.sqrt(1 + U**2) R = 1 / 2 + U / (2 * t1) t2 = 1 / (2 * t1) - U**2 / (2 * t1**3) dR = np.array([wv**2 * t2, wv * t2, t2]) XYZ = sd_to_XYZ_integration(R, cmfs, illuminant, shape=cmfs.shape) / 100 dXYZ = np.transpose( sd_to_XYZ_integration(dR, cmfs, illuminant, shape=cmfs.shape) / 100 ) XYZ_n = sd_to_XYZ_integration(illuminant, cmfs) XYZ_n /= XYZ_n[1] XYZ_XYZ_n = XYZ / XYZ_n XYZ_f = intermediate_lightness_function_CIE1976(XYZ, XYZ_n) dXYZ_f = np.where( XYZ_XYZ_n[..., np.newaxis] > (24 / 116) ** 3, 1 / ( 3 * spow(XYZ_n[..., np.newaxis], 1 / 3) * spow(XYZ[..., np.newaxis], 2 / 3) ) * dXYZ, (841 / 108) * dXYZ / XYZ_n[..., np.newaxis], ) def intermediate_XYZ_to_Lab( XYZ_i: NDArray, offset: Optional[Floating] = 16 ) -> NDArray: """ Return the final intermediate value for the *CIE Lab* to *CIE XYZ* conversion. """ return np.array( [ 116 * XYZ_i[1] - offset, 500 * (XYZ_i[0] - XYZ_i[1]), 200 * (XYZ_i[1] - XYZ_i[2]), ] ) Lab_i = intermediate_XYZ_to_Lab(as_float_array(XYZ_f)) dLab_i = intermediate_XYZ_to_Lab(as_float_array(dXYZ_f), 0) error = np.sqrt(np.sum((Lab_i - target) ** 2)) if max_error is not None and error <= max_error: raise StopMinimizationEarly(coefficients, error) derror = ( np.sum( dLab_i * (Lab_i[..., np.newaxis] - target[..., np.newaxis]), axis=0 ) / error ) if additional_data: return error, derror, R, XYZ, Lab_i else: return error, derror
def error_function(coefficients, target, cmfs, illuminant, max_error=None, additional_data=False): """ Computes :math:`\\Delta E_{76}` between the target colour and the colour defined by given spectral model, along with its gradient. Parameters ---------- coefficients : array_like Dimensionless coefficients for *Jakob and Hanika (2019)* reflectance spectral model. target : array_like, (3,) *CIE L\\*a\\*b\\** colourspace array of the target colour. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. illuminant : SpectralDistribution Illuminant spectral distribution. max_error : float, optional Raise ``StopMinimizationEarly`` if the error is smaller than this. The default is *None* and the function doesn't raise anything. additional_data : bool, optional If *True*, some intermediate calculations are returned, for use in correctness tests: R, XYZ and Lab. Returns ------- error : float The computed :math:`\\Delta E_{76}` error. derror : ndarray, (3,) The gradient of error, i.e. the first derivatives of error with respect to the input coefficients. R : ndarray Computed spectral reflectance. XYZ : ndarray, (3,) *CIE XYZ* tristimulus values corresponding to ``R``. Lab : ndarray, (3,) *CIE L\\*a\\*b\\** colourspace array corresponding to ``XYZ``. Raises ------ StopMinimizationEarly Raised when the error is below ``max_error``. """ c_0, c_1, c_2 = as_float_array(coefficients) wv = np.linspace(0, 1, len(cmfs.shape)) U = c_0 * wv ** 2 + c_1 * wv + c_2 t1 = np.sqrt(1 + U ** 2) R = 1 / 2 + U / (2 * t1) t2 = 1 / (2 * t1) - U ** 2 / (2 * t1 ** 3) dR = np.array([wv ** 2 * t2, wv * t2, t2]) E = illuminant.values * R dE = illuminant.values * dR dw = cmfs.wavelengths[1] - cmfs.wavelengths[0] k = 1 / (np.sum(cmfs.values[:, 1] * illuminant.values) * dw) XYZ = k * np.dot(E, cmfs.values) * dw dXYZ = np.transpose(k * np.dot(dE, cmfs.values) * dw) XYZ_n = sd_to_XYZ(illuminant, cmfs) XYZ_n /= XYZ_n[1] XYZ_XYZ_n = XYZ / XYZ_n XYZ_f = intermediate_lightness_function_CIE1976(XYZ, XYZ_n) dXYZ_f = np.where( XYZ_XYZ_n[..., np.newaxis] > (24 / 116) ** 3, 1 / (3 * spow(XYZ_n[..., np.newaxis], 1 / 3) * spow( XYZ[..., np.newaxis], 2 / 3)) * dXYZ, (841 / 108) * dXYZ / XYZ_n[..., np.newaxis], ) def intermediate_XYZ_to_Lab(XYZ_i, offset=16): """ Returns the final intermediate value for the *CIE Lab* to *CIE XYZ* conversion. """ return np.array([ 116 * XYZ_i[1] - offset, 500 * (XYZ_i[0] - XYZ_i[1]), 200 * (XYZ_i[1] - XYZ_i[2]) ]) Lab_i = intermediate_XYZ_to_Lab(XYZ_f) dLab_i = intermediate_XYZ_to_Lab(dXYZ_f, 0) error = np.sqrt(np.sum((Lab_i - target) ** 2)) if max_error is not None and error <= max_error: raise StopMinimizationEarly(coefficients, error) derror = np.sum( dLab_i * (Lab_i[..., np.newaxis] - target[..., np.newaxis]), axis=0) / error if additional_data: return error, derror, R, XYZ, Lab_i else: return error, derror
def XYZ_to_Lab( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE XYZ* tristimulus values to *CIE L\\*a\\*b\\** colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- ndarray *CIE L\\*a\\*b\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Lab`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``a`` : [-100, 100] | ``a`` : [-1, 1] | | | | | | | ``b`` : [-100, 100] | ``b`` : [-1, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_Lab(XYZ) # doctest: +ELLIPSIS array([ 41.5278752..., 52.6385830..., 26.9231792...]) """ X, Y, Z = tsplit(to_domain_1(XYZ)) X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) f_X_X_n = intermediate_lightness_function_CIE1976(X, X_n) f_Y_Y_n = intermediate_lightness_function_CIE1976(Y, Y_n) f_Z_Z_n = intermediate_lightness_function_CIE1976(Z, Z_n) L = 116 * f_Y_Y_n - 16 a = 500 * (f_X_X_n - f_Y_Y_n) b = 200 * (f_Y_Y_n - f_Z_Z_n) Lab = tstack([L, a, b]) return from_range_100(Lab)