def delta_E_Luo2006(Jpapbp_1, Jpapbp_2, coefficients): """ Returns the difference :math:`\\Delta E'` between two given *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces :math:`J'a'b'` arrays. Parameters ---------- Jpapbp_1 : array_like Standard / reference *Luo et al. (2006)* *CAM02-LCD*, *CAM02-SCD*, or *CAM02-UCS* colourspaces :math:`J'a'b'` array. Jpapbp_2 : array_like Sample / test *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 ------- numeric or ndarray Colour difference :math:`\\Delta E'`. Notes ----- +--------------+------------------------+--------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +==============+========================+====================+ | ``Jpapbp_1`` | ``Jp_1`` : [0, 100] | ``Jp_1`` : [0, 1] | | | | | | | ``ap_1`` : [-100, 100] | ``ap_1`` : [-1, 1] | | | | | | | ``bp_1`` : [-100, 100] | ``bp_1`` : [-1, 1] | +--------------+------------------------+--------------------+ | ``Jpapbp_2`` | ``Jp_2`` : [0, 100] | ``Jp_2`` : [0, 1] | | | | | | | ``ap_2`` : [-100, 100] | ``ap_2`` : [-1, 1] | | | | | | | ``bp_2`` : [-100, 100] | ``bp_2`` : [-1, 1] | +--------------+------------------------+--------------------+ Examples -------- >>> Jpapbp_1 = np.array([54.90433134, -0.08450395, -0.06854831]) >>> Jpapbp_2 = np.array([54.80352754, -3.96940084, -13.57591013]) >>> delta_E_Luo2006(Jpapbp_1, Jpapbp_2, ... COEFFICIENTS_UCS_LUO2006['CAM02-LCD']) ... # doctest: +ELLIPSIS 0.0001034... """ J_p_1, a_p_1, b_p_1 = tsplit(Jpapbp_1) J_p_2, a_p_2, b_p_2 = tsplit(Jpapbp_2) K_L, _c_1, _c_2 = tsplit(coefficients) d_E = np.sqrt(((J_p_1 - J_p_2) / K_L) ** 2 + (a_p_1 - a_p_2) ** 2 + (b_p_1 - b_p_2) ** 2) return d_E
def XYZ_to_Hunter_Rdab( XYZ, XYZ_n=HUNTERLAB_ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50').XYZ_n, K_ab=HUNTERLAB_ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50').K_ab): """ Converts from *CIE XYZ* tristimulus values to *Hunter Rd,a,b* colour scale. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. XYZ_n : array_like, optional Reference *illuminant* tristimulus values. K_ab : array_like, optional Reference *illuminant* chromaticity coefficients, if `K_ab` is set to `None` it will be computed using :func:`XYZ_to_K_ab_HunterLab1966`. Returns ------- ndarray *Hunter Rd,a,b* colour scale array. Notes ----- - Input *CIE XYZ* and reference *illuminant* tristimulus values are in domain [0, 100]. Examples -------- >>> import numpy as np >>> XYZ = np.array([0.07049534, 0.10080000, 0.09558313]) * 100 >>> D50 = HUNTERLAB_ILLUMINANTS.get( ... 'CIE 1931 2 Degree Standard Observer').get('D50') >>> XYZ_to_Hunter_Rdab( ... XYZ, ... D50.XYZ_n, ... D50.K_ab) # doctest: +ELLIPSIS array([ 10.08 , -18.6765376..., -3.4432992...]) """ X, Y, Z = tsplit(XYZ) X_n, Y_n, Z_n = tsplit(XYZ_n) K_a, K_b = (tsplit(XYZ_to_K_ab_HunterLab1966(XYZ_n)) if K_ab is None else tsplit(K_ab)) f = 0.51 * ((21 + 0.2 * Y) / (1 + 0.2 * Y)) Y_Yn = Y / Y_n R_d = Y a_Rd = K_a * f * (X / X_n - Y_Yn) b_Rd = K_b * f * (Y_Yn - Z / Z_n) R_d_ab = tstack((R_d, a_Rd, b_Rd)) return R_d_ab
def LCHab_to_Lab(LCHab): """ Converts from *CIE L\\*C\\*Hab* colourspace to *CIE L\\*a\\*b\\** colourspace. Parameters ---------- LCHab : array_like *CIE L\\*C\\*Hab* colourspace array. Returns ------- ndarray *CIE L\\*a\\*b\\** colourspace array. Notes ----- +-------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +=============+=======================+=================+ | ``LCHab`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``C`` : [0, 100] | ``C`` : [0, 1] | | | | | | | ``ab`` : [0, 360] | ``ab`` : [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 -------- >>> LCHab = np.array([41.52787529, 59.12425901, 27.08848784]) >>> LCHab_to_Lab(LCHab) # doctest: +ELLIPSIS array([ 41.5278752..., 52.6385830..., 26.9231792...]) """ L, C, H = tsplit(LCHab) a, b = tsplit( polar_to_cartesian(tstack([C, np.radians(to_domain_degrees(H))]))) Lab = tstack([L, a, b]) return Lab
def LCHuv_to_Luv(LCHuv): """ Converts from *CIE L\\*C\\*Huv* colourspace to *CIE L\\*u\\*v\\** colourspace. Parameters ---------- LCHuv : array_like *CIE L\\*C\\*Huv* colourspace array. Returns ------- ndarray *CIE L\\*u\\*v\\** colourspace array. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``LCHuv`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``C`` : [0, 100] | ``C`` : [0, 1] | | | | | | | ``uv`` : [0, 360] | ``uv`` : [0, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``Luv`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``u`` : [-100, 100] | ``u`` : [-1, 1] | | | | | | | ``v`` : [-100, 100] | ``v`` : [-1, 1] | +------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m` Examples -------- >>> LCHuv = np.array([41.52787529, 98.44997950, 10.38816348]) >>> LCHuv_to_Luv(LCHuv) # doctest: +ELLIPSIS array([ 41.5278752..., 96.8362605..., 17.7521014...]) """ L, C, H = tsplit(LCHuv) u, v = tsplit( polar_to_cartesian(tstack([C, np.radians(to_domain_degrees(H))]))) Luv = tstack([L, u, v]) return Luv
def Luv_to_XYZ(Luv, illuminant=ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50')): """ Converts from *CIE Luv* colourspace to *CIE XYZ* tristimulus values. Parameters ---------- Luv : array_like *CIE Luv* colourspace array. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- - Input :math:`L^*` is in domain [0, 100]. - Input *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array are in domain [0, :math:`\infty`]. - Output *CIE XYZ* tristimulus values are in range [0, 1]. References ---------- .. [3] Lindbloom, B. (2003). Luv to XYZ. Retrieved February 24, 2014, from http://brucelindbloom.com/Eqn_Luv_to_XYZ.html Examples -------- >>> Luv = np.array([37.9856291 , -28.80219593, -1.35800706]) >>> Luv_to_XYZ(Luv) # doctest: +ELLIPSIS array([ 0.0704953..., 0.1008 , 0.0955831...]) """ L, u, v = tsplit(Luv) X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) Y = np.where(L > CIE_E * CIE_K, ((L + 16) / 116) ** 3, L / CIE_K) 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 XYZ
def Hunter_Lab_to_XYZ( Lab, XYZ_n=HUNTERLAB_ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50').XYZ_n, K_ab=HUNTERLAB_ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50').K_ab): """ Converts from *Hunter L,a,b* colour scale to *CIE XYZ* tristimulus values. Parameters ---------- Lab : array_like *Hunter L,a,b* colour scale array. XYZ_n : array_like, optional Reference *illuminant* tristimulus values. K_ab : array_like, optional Reference *illuminant* chromaticity coefficients, if `K_ab` is set to `None` it will be computed using :func:`XYZ_to_K_ab_HunterLab1966`. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- - Input *Lightness* :math:`L^*` is in domain [0, 100]. - Input *CIE XYZ* and reference *illuminant* tristimulus values are in domain [0, 100]. - Output *CIE XYZ* tristimulus values are in range [0, 100]. Examples -------- >>> Lab = np.array([31.74901573, -15.11462629, -2.78660758]) >>> D50 = HUNTERLAB_ILLUMINANTS.get( ... 'CIE 1931 2 Degree Standard Observer').get('D50') >>> Hunter_Lab_to_XYZ(Lab, D50.XYZ_n, D50.K_ab) # doctest: +ELLIPSIS array([ 7.049534, 10.08 , 9.558313]) """ L, a, b = tsplit(Lab) X_n, Y_n, Z_n = tsplit(XYZ_n) K_a, K_b = (tsplit(XYZ_to_K_ab_HunterLab1966(XYZ_n)) if K_ab is None else tsplit(K_ab)) L_100 = L / 100 L_100_2 = L_100 ** 2 Y = L_100_2 * Y_n X = ((a / K_a) * L_100 + L_100_2) * X_n Z = -((b / K_b) * L_100 - L_100_2) * Z_n XYZ = tstack((X, Y, Z)) return XYZ
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 achromatic_response(RGB, bRGB_o, xez, bL_or, eR, eG, n=1): """ Returns the achromatic response :math:`Q` from given stimulus cone responses. Parameters ---------- RGB: ndarray Stimulus cone responses. bRGB_o: ndarray Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`, :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`. xez: ndarray Intermediate values :math:`\\xi`, :math:`\eta`, :math:`\zeta`. bL_or: numeric or array_like Normalising chromatic adaptation exponential factor :math:`\\beta_1(B_or)`. eR: numeric or array_like Scaling coefficient :math:`e(R)`. eG: numeric or array_like Scaling coefficient :math:`e(G)`. n : numeric or array_like, optional Noise term used in the non linear chromatic adaptation model. Returns ------- numeric or ndarray Achromatic response :math:`Q`. Examples -------- >>> RGB = np.array([20.00052060, 19.99978300, 19.99883160]) >>> bRGB_o = np.array([4.61062223, 4.61058926, 4.65206986]) >>> xez = np.array([1.00004219, 0.99998001, 0.99975794]) >>> bL_or = 3.6810214956040888 >>> eR = 1.0 >>> eG = 1.758 >>> n = 1.0 >>> achromatic_response( # doctest: +ELLIPSIS ... RGB, bRGB_o, xez, bL_or, eR, eG, n) -0.0001169... """ R, G, _B = tsplit(RGB) bR_o, bG_o, _bB_o = tsplit(bRGB_o) xi, eta, _zeta = tsplit(xez) bL_or = np.asarray(bL_or) eR = np.asarray(eR) eG = np.asarray(eG) Q = (2 / 3) * bR_o * eR * np.log10((R + n) / (20 * xi + n)) Q += (1 / 3) * bG_o * eG * np.log10((G + n) / (20 * eta + n)) Q *= 41.69 / bL_or return Q
def K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, n=1): """ Computes the coefficient :math:`K` for correcting the difference between the test and references illuminances. Parameters ---------- xez_1: array_like Intermediate values :math:`\\xi_1`, :math:`\eta_1`, :math:`\zeta_1` for the test illuminant and background. xez_2: array_like Intermediate values :math:`\\xi_2`, :math:`\eta_2`, :math:`\zeta_2` for the reference illuminant and background. bRGB_o1: array_like Chromatic adaptation exponential factors :math:`\\beta_1(R_{o1})`, :math:`\\beta_1(G_{o1})` and :math:`\\beta_2(B_{o1})` of test sample. bRGB_o2: array_like Chromatic adaptation exponential factors :math:`\\beta_1(R_{o2})`, :math:`\\beta_1(G_{o2})` and :math:`\\beta_2(B_{o2})` of reference sample. Y_o : numeric or array_like Luminance factor :math:`Y_o` of achromatic background as percentage in domain [18, 100]. n : numeric or array_like, optional Noise component in fundamental primary system. Returns ------- numeric or array_like Coefficient :math:`K`. Examples -------- >>> xez_1 = np.array([1.11857195, 0.93295530, 0.32680879]) >>> xez_2 = np.array([1.00000372, 1.00000176, 0.99999461]) >>> bRGB_o1 = np.array([3.74852518, 3.63920879, 2.78924811]) >>> bRGB_o2 = np.array([3.68102374, 3.68102256, 3.56557351]) >>> Y_o = 20 >>> K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o) 1.0 """ xi_1, eta_1, _zeta_1 = tsplit(xez_1) xi_2, eta_2, _zeta_2 = tsplit(xez_2) bR_o1, bG_o1, _bB_o1 = tsplit(bRGB_o1) bR_o2, bG_o2, _bB_o2 = tsplit(bRGB_o2) Y_o = np.asarray(Y_o) K = (((Y_o * xi_1 + n) / (20 * xi_1 + n)) ** ((2 / 3) * bR_o1) / ((Y_o * xi_2 + n) / (20 * xi_2 + n)) ** ((2 / 3) * bR_o2)) K *= (((Y_o * eta_1 + n) / (20 * eta_1 + n)) ** ((1 / 3) * bG_o1) / ((Y_o * eta_2 + n) / (20 * eta_2 + n)) ** ((1 / 3) * bG_o2)) return K
def XYZ_to_Hunter_Lab( XYZ, XYZ_n=HUNTERLAB_ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50').XYZ_n, K_ab=HUNTERLAB_ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50').K_ab): """ Converts from *CIE XYZ* tristimulus values to *Hunter L,a,b* colour scale. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. XYZ_n : array_like, optional Reference *illuminant* tristimulus values. K_ab : array_like, optional Reference *illuminant* chromaticity coefficients, if `K_ab` is set to `None` it will be computed using :func:`XYZ_to_K_ab_HunterLab1966`. Returns ------- ndarray *Hunter L,a,b* colour scale array. Notes ----- - Input *CIE XYZ* and reference *illuminant* tristimulus values are in domain [0, 100]. - Output *Lightness* :math:`L^*` is in range [0, 100]. Examples -------- >>> XYZ = np.array([0.07049534, 0.10080000, 0.09558313]) * 100 >>> D50 = HUNTERLAB_ILLUMINANTS.get( ... 'CIE 1931 2 Degree Standard Observer').get('D50') >>> XYZ_to_Hunter_Lab(XYZ, D50.XYZ_n, D50.K_ab) # doctest: +ELLIPSIS array([ 31.7490157..., -15.1146262..., -2.7866075...]) """ X, Y, Z = tsplit(XYZ) X_n, Y_n, Z_n = tsplit(XYZ_n) K_a, K_b = (tsplit(XYZ_to_K_ab_HunterLab1966(XYZ_n)) if K_ab is None else tsplit(K_ab)) Y_Y_n = Y / Y_n sqrt_Y_Y_n = np.sqrt(Y_Y_n) L = 100 * sqrt_Y_Y_n a = K_a * ((X / X_n - Y_Y_n) / sqrt_Y_Y_n) b = K_b * ((Y_Y_n - Z / Z_n) / sqrt_Y_Y_n) Lab = tstack((L, a, b)) return Lab
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 = np.asarray(LMS) if discount_illuminant: return np.ones(LMS.shape) Y_n = np.asarray(Y_n) v = np.asarray(v) L, M, S = tsplit(LMS) LMS_E = dot_vector(VON_KRIES_CAT, np.ones(LMS.shape)) # E illuminant. L_E, M_E, S_E = tsplit(LMS_E) Ye_n = Y_n ** v f_E = lambda x, y: (3 * (x / y)) / (L / L_E + M / M_E + S / S_E) f_P = lambda x: (1 + Ye_n + x) / (1 + Ye_n + 1 / x) p_L = f_P(f_E(L, L_E)) p_M = f_P(f_E(M, M_E)) p_S = f_P(f_E(S, S_E)) p_LMS = tstack((p_L, p_M, p_S)) return p_LMS
def XYZ_to_UVW(XYZ, illuminant=ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50')): """ 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. Notes ----- - Input *CIE XYZ* tristimulus values are in domain [0, 100]. - Input *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array are in domain [0, :math:`\infty`]. - Output *CIE UVW* colourspace array is in range [0, 100]. Warning ------- The input / output domains of that definition are non standard! Examples -------- >>> import numpy as np >>> XYZ = np.array([0.07049534, 0.10080000, 0.09558313]) * 100 >>> XYZ_to_UVW(XYZ) # doctest: +ELLIPSIS array([-28.0579733..., -0.8819449..., 37.0041149...]) """ xyY = XYZ_to_xyY(XYZ, xyY_to_xy(illuminant)) _x, _y, Y = tsplit(xyY) u, v = tsplit(UCS_to_uv(XYZ_to_UCS(XYZ))) u_0, v_0 = tsplit( UCS_to_uv(XYZ_to_UCS(xyY_to_XYZ(xy_to_xyY(illuminant))))) W = 25 * Y ** (1 / 3) - 17 U = 13 * W * (u - u_0) V = 13 * W * (v - v_0) UVW = tstack((U, V, W)) return UVW
def XYZ_to_Luv(XYZ, illuminant=ILLUMINANTS.get( 'CIE 1931 2 Degree Standard Observer').get('D50')): """ Converts from *CIE XYZ* tristimulus values to *CIE Luv* 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 Luv* colourspace array. Notes ----- - Input *CIE XYZ* tristimulus values are in domain [0, 1]. - Input *illuminant* *xy* chromaticity coordinates or *CIE xyY* colourspace array are in domain [0, :math:`\infty`]. - Output :math:`L^*` is in range [0, 100]. References ---------- .. [2] Lindbloom, B. (2003). XYZ to Luv. Retrieved February 24, 2014, from http://brucelindbloom.com/Eqn_XYZ_to_Luv.html Examples -------- >>> XYZ = np.array([0.07049534, 0.10080000, 0.09558313]) >>> XYZ_to_Luv(XYZ) # doctest: +ELLIPSIS array([ 37.9856291..., -28.8021959..., -1.3580070...]) """ X, Y, Z = tsplit(XYZ) X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) y_r = Y / Y_r L = np.where(y_r > CIE_E, 116 * y_r ** (1 / 3) - 16, CIE_K * y_r) u = (13 * L * ((4 * X / (X + 15 * Y + 3 * Z)) - (4 * X_r / (X_r + 15 * Y_r + 3 * Z_r)))) v = (13 * L * ((9 * Y / (X + 15 * Y + 3 * Z)) - (9 * Y_r / (X_r + 15 * Y_r + 3 * Z_r)))) Luv = tstack((L, u, v)) return Luv
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 in 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 = np.asarray(Y) beta = (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 / (B_0 ** beta)) + 1 - D) * (abs(B) ** beta) RGB_r = tstack((R_r, G_r, B_r)) Y = tstack((Y, Y, Y)) XYZ_r = dot_vector(LLAB_RGB_TO_XYZ_MATRIX, RGB_r * Y) return XYZ_r
def gamut_area(Lab): """ Returns the gamut area :math:`G` covered by given *CIE L\\*a\\*b\\** matrices. Parameters ---------- Lab : array_like *CIE L\\*a\\*b\\** colourspace matrices. Returns ------- numeric Gamut area :math:`G`. Examples -------- >>> Lab = [ ... np.array([39.94996006, 34.59018231, -19.86046321]), ... np.array([38.88395498, 21.44348519, -34.87805301]), ... np.array([36.60576301, 7.06742454, -43.21461177]), ... np.array([46.60142558, -15.90481586, -34.64616865]), ... np.array([56.50196523, -29.54655550, -20.50177194]), ... np.array([55.73912101, -43.39520959, -5.08956953]), ... np.array([56.20776870, -53.68997662, 20.21134410]), ... np.array([66.16683122, -38.64600327, 42.77396631]), ... np.array([76.72952110, -23.92148210, 61.04740432]), ... np.array([82.85370708, -3.98679065, 75.43320144]), ... np.array([69.26458861, 13.11066359, 68.83858372]), ... np.array([69.63154351, 28.24532497, 59.45609803]), ... np.array([61.26281449, 40.87950839, 44.97606172]), ... np.array([41.62567821, 57.34129516, 27.46718170]), ... np.array([40.52565174, 48.87449192, 3.45121680]) ... ] >>> gamut_area(Lab) # doctest: +ELLIPSIS 8335.9482018... """ Lab = as_float_array(Lab) Lab_s = np.roll(np.copy(Lab), -3) _L, a, b = tsplit(Lab) _L_s, a_s, b_s = tsplit(Lab_s) A = np.linalg.norm(Lab[..., 1:3], axis=-1) B = np.linalg.norm(Lab_s[..., 1:3], axis=-1) C = np.linalg.norm(np.dstack([a_s - a, b_s - b]), axis=-1) t = (A + B + C) / 2 S = np.sqrt(t * (t - A) * (t - B) * (t - C)) return np.sum(S)
def extend_line_segment(a, b, distance=1): """ Extends the line segment defined by point arrays :math:`a` and :math:`b` by given distance and return the new end point. Parameters ---------- a : array_like Point array :math:`a`. b : array_like Point array :math:`b`. distance : numeric, optional Distance to extend the line segment. Returns ------- ndarray New end point. References ---------- .. [1] Saeedn. (n.d.). Extend a line segment a specific distance. Retrieved January 16, 2016, from http://stackoverflow.com/\ questions/7740507/extend-a-line-segment-a-specific-distance Notes ----- - Input line segment points coordinates are 2d coordinates. Examples -------- >>> a = np.array([0.95694934, 0.13720932]) >>> b = np.array([0.28382835, 0.60608318]) >>> extend_line_segment(a, b) # doctest: +ELLIPSIS array([-0.5367248..., 1.1776534...]) """ x_a, y_a = tsplit(a) x_b, y_b = tsplit(b) d = euclidean_distance(a, b) x_c = x_b + (x_b - x_a) / d * distance y_c = y_b + (y_b - y_a) / d * distance xy_c = tstack((x_c, y_c)) return xy_c
def xy_to_CCT_McCamy1992(xy): """ Returns the correlated colour temperature :math:`T_{cp}` from given *CIE XYZ* tristimulus values *xy* chromaticity coordinates using McCamy (1992) method. Parameters ---------- xy : array_like *xy* chromaticity coordinates. Returns ------- numeric or ndarray Correlated colour temperature :math:`T_{cp}`. References ---------- .. [9] Wikipedia. (n.d.). Approximation. Retrieved June 28, 2014, from http://en.wikipedia.org/wiki/Color_temperature#Approximation Examples -------- >>> xy = np.array([0.31271, 0.32902]) >>> xy_to_CCT_McCamy1992(xy) # doctest: +ELLIPSIS 6504.3893830... """ x, y = tsplit(xy) n = (x - 0.3320) / (y - 0.1858) CCT = -449 * n ** 3 + 3525 * n ** 2 - 6823.3 * n + 5520.33 return CCT
def opponent_colour_dimensions_forward(RGB): """ Returns opponent colour dimensions from given compressed CMCCAT2000 transform sharpened *RGB* array for forward CIECAM02 implementation Parameters ---------- RGB : array_like Compressed CMCCAT2000 transform sharpened *RGB* array. Returns ------- ndarray Opponent colour dimensions. Examples -------- >>> RGB = np.array([7.94632020, 7.94711528, 7.94899595]) >>> opponent_colour_dimensions_forward(RGB) # doctest: +ELLIPSIS array([-0.0006241..., -0.0005062...]) """ R, G, B = tsplit(RGB) a = R - 12 * G / 11 + B / 11 b = (R + G - 2 * B) / 9 ab = tstack((a, b)) return ab
def UCS_uv_to_xy(uv): """ Returns the *xy* chromaticity coordinates from given *CIE 1960 UCS* colourspace *uv* chromaticity coordinates. Parameters ---------- uv : array_like *CIE UCS uv* chromaticity coordinates. Returns ------- ndarray *xy* chromaticity coordinates. References ---------- :cite:`Wikipedia2008c` Examples -------- >>> uv = np.array([0.37720213, 0.33413508]) >>> UCS_uv_to_xy(uv) # doctest: +ELLIPSIS array([ 0.5436955..., 0.3210794...]) """ u, v = tsplit(uv) d = 2 * u - 8 * v + 4 xy = tstack([3 * u / d, 2 * v / d]) return xy
def achromatic_response_forward(RGB, N_bb): """ Returns the achromatic response :math:`A` from given compressed CMCCAT2000 transform sharpened *RGB* array and :math:`N_{bb}` chromatic induction factor for forward CIECAM02 implementation. Parameters ---------- RGB : array_like Compressed CMCCAT2000 transform sharpened *RGB* array. N_bb : numeric or array_like Chromatic induction factor :math:`N_{bb}`. Returns ------- numeric or ndarray Achromatic response :math:`A`. Examples -------- >>> RGB = np.array([7.94632020, 7.94711528, 7.94899595]) >>> N_bb = 1.000304004559381 >>> achromatic_response_forward(RGB, N_bb) # doctest: +ELLIPSIS 23.9394809... """ R, G, B = tsplit(RGB) A = (2 * R + G + (1 / 20) * B - 0.305) * N_bb return A
def UCS_to_XYZ(UVW): """ Converts from *CIE UCS* colourspace to *CIE XYZ* tristimulus values. Parameters ---------- UVW : array_like *CIE UCS* colourspace array. Returns ------- ndarray *CIE XYZ* tristimulus values. Notes ----- - Input *CIE UCS* colourspace array is in domain [0, 1]. - Output *CIE XYZ* tristimulus values are in domain [0, 1]. Examples -------- >>> UVW = np.array([0.04699689, 0.10080000, 0.16374390]) >>> UCS_to_XYZ(UVW) # doctest: +ELLIPSIS array([ 0.0704953..., 0.1008 , 0.0955831...]) """ U, V, W = tsplit(UVW) XYZ = tstack((3 / 2 * U, V, 3 / 2 * U - (3 * V) + (2 * W))) return XYZ
def viewing_condition_dependent_parameters(Y_b, Y_w, L_A): """ Returns the viewing condition dependent parameters. Parameters ---------- Y_b : numeric or array_like Adapting field *Y* tristimulus value :math:`Y_b`. Y_w : numeric or array_like Whitepoint *Y* tristimulus value :math:`Y_w`. L_A : numeric or array_like Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`. Returns ------- ndarray Viewing condition dependent parameters. Examples -------- >>> viewing_condition_dependent_parameters( # doctest: +ELLIPSIS ... 20.0, 100.0, 318.31) array([ 0.2..., 1.1675444..., 1.000304..., 1.000304..., 1.9272136...]) """ Y_b = np.asarray(Y_b) Y_w = np.asarray(Y_w) n = Y_b / Y_w F_L = luminance_level_adaptation_factor(L_A) N_bb, N_cb = tsplit(chromatic_induction_factors(n)) z = base_exponential_non_linearity(n) return tstack((n, F_L, N_bb, N_cb, z))
def UCS_uv_to_xy(uv): """ Returns the *xy* chromaticity coordinates from given *CIE UCS* colourspace *uv* chromaticity coordinates. Parameters ---------- uv : array_like *CIE UCS uv* chromaticity coordinates. Returns ------- ndarray *xy* chromaticity coordinates. Notes ----- - Input *uv* chromaticity coordinates are in domain [0, 1]. - Output *xy* chromaticity coordinates are in domain [0, 1]. Examples -------- >>> uv = np.array([0.15085308732766581, 0.3235531372954405]) >>> UCS_uv_to_xy(uv) # doctest: +ELLIPSIS array([ 0.2641477..., 0.3777000...]) """ u, v = tsplit(uv) xy = tstack((3 * u / (2 * u - 8 * v + 4), 2 * v / (2 * u - 8 * v + 4))) return xy
def XYZ_to_UCS(XYZ): """ Converts from *CIE XYZ* tristimulus values to *CIE UCS* colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Returns ------- ndarray *CIE UCS* colourspace array. Notes ----- - Input *CIE XYZ* tristimulus values are in domain [0, 1]. - Output *CIE UCS* colourspace array is in domain [0, 1]. Examples -------- >>> XYZ = np.array([0.07049534, 0.10080000, 0.09558313]) >>> XYZ_to_UCS(XYZ) # doctest: +ELLIPSIS array([ 0.0469968..., 0.1008 , 0.1637439...]) """ X, Y, Z = tsplit(XYZ) UVW = tstack((2 / 3 * X, Y, 1 / 2 * (-X + 3 * Y + Z))) return UVW
def xy_to_z(xy): """ Returns the *z* coordinate using given :math:`xy` chromaticity coordinates. Parameters ---------- xy : array_like :math:`xy` chromaticity coordinates. Returns ------- numeric *z* coordinate. Examples -------- >>> xy_to_z(np.array([0.25, 0.25])) 0.5 """ x, y = tsplit(xy) z = 1 - x - y return z
def UCS_to_uv(UVW): """ Returns the *uv* chromaticity coordinates from given *CIE UCS* colourspace array. Parameters ---------- UVW : array_like *CIE UCS* colourspace array. Returns ------- ndarray *uv* chromaticity coordinates. Notes ----- - Input *CIE UCS* colourspace array is in domain [0, 1]. - Output *uv* chromaticity coordinates are in domain [0, 1]. Examples -------- >>> UCS = np.array([0.04699689, 0.10080000, 0.16374390]) >>> UCS_to_uv(UCS) # doctest: +ELLIPSIS array([ 0.1508530..., 0.3235531...]) """ U, V, W = tsplit(UVW) uv = tstack((U / (U + V + W), V / (U + V + W))) return uv
def hue_angle(C): """ Returns the *hue* angle :math:`h` from given colour difference signals :math:`C`. Parameters ---------- C : array_like Colour difference signals :math:`C`. Returns ------- numeric or ndarray *Hue* angle :math:`h`. Examples -------- >>> C = np.array([ ... -5.365865581996587e-05, ... -0.000571699383647, ... 0.000625358039467]) >>> hue_angle(C) # doctest: +ELLIPSIS 269.2737594... """ C_1, C_2, C_3 = tsplit(C) hue = (180 * np.arctan2(0.5 * (C_2 - C_3) / 4.5, C_1 - (C_2 / 11)) / np.pi) % 360 return hue
def IPT_hue_angle(IPT): """ Computes the hue angle from *IPT* colourspace. Parameters ---------- IPT : array_like *IPT* colourspace array. Returns ------- numeric or ndarray Hue angle. Examples -------- >>> IPT = np.array([0.96907232, 1, 1.12179215]) >>> IPT_hue_angle(IPT) # doctest: +ELLIPSIS 0.8427358... """ _I, P, T = tsplit(IPT) hue = np.arctan2(T, P) return hue
def xy_to_UCS_uv(xy): """ Returns the *CIE 1960 UCS* colourspace *uv* chromaticity coordinates from given *xy* chromaticity coordinates. Parameters ---------- xy : array_like *xy* chromaticity coordinates. Returns ------- ndarray *CIE UCS uv* chromaticity coordinates. References ---------- :cite:`Wikipedia2008c` Examples -------- >>> xy = np.array([0.54369555, 0.32107941]) >>> xy_to_UCS_uv(xy) # doctest: +ELLIPSIS array([ 0.3772021..., 0.3341350...]) """ x, y = tsplit(xy) d = 12 * y - 2 * x + 3 uv = tstack([4 * x / d, 6 * y / d]) return uv
def colour_difference_signals(rgb): """ Returns the colour difference signals :math:`C_1`, :math:`C_2` and :math:`C_3` from given *Hunt-Pointer-Estevez* :math:`\\rho\gamma\\beta` colourspace array. Parameters ---------- rgb : array_like *Hunt-Pointer-Estevez* :math:`\\rho\gamma\\beta` colourspace array. Returns ------- ndarray Colour difference signals :math:`C_1`, :math:`C_2` and :math:`C_3`. Examples -------- >>> rgb = np.array([6.89594549, 6.89599915, 6.89657085]) >>> colour_difference_signals(rgb) # doctest: +ELLIPSIS array([ -5.3660000...e-05, -5.7170000...e-04, 6.2536000...e-04]) """ r, g, b = tsplit(rgb) C_1 = r - g C_2 = g - b C_3 = b - r C = tstack((C_1, C_2, C_3)) return C
def delta_E_CIE1994(Lab_1, Lab_2, textiles=False): """ Returns the difference :math:`\Delta E_{ab}` between two given *CIE Lab* colourspace arrays using *CIE 1994* recommendation. Parameters ---------- Lab_1 : array_like *CIE Lab* colourspace array 1. Lab_2 : array_like *CIE Lab* 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_{ab}`. Notes ----- - *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 ---------- .. [3] Lindbloom, B. (2011). Delta E (CIE 1994). Retrieved February 24, 2014, from http://brucelindbloom.com/Eqn_DeltaE_CIE94.html 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... """ 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 L_1, a_1, b_1 = tsplit(Lab_1) L_2, a_2, b_2 = tsplit(Lab_2) 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 XYZ_to_Luv( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE XYZ* tristimulus values to *CIE L\\*u\\*v\\** 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\\*u\\*v\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Luv`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``u`` : [-100, 100] | ``u`` : [-1, 1] | | | | | | | ``v`` : [-100, 100] | ``v`` : [-1, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wikipedia2007b` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_Luv(XYZ) # doctest: +ELLIPSIS array([ 41.5278752..., 96.8362605..., 17.7521014...]) """ X, Y, Z = tsplit(to_domain_1(XYZ)) X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) with domain_range_scale('100'): L = lightness_CIE1976(Y, Y_r) u = (13 * L * ((4 * X / (X + 15 * Y + 3 * Z)) - (4 * X_r / (X_r + 15 * Y_r + 3 * Z_r)))) v = (13 * L * ((9 * Y / (X + 15 * Y + 3 * Z)) - (9 * Y_r / (X_r + 15 * Y_r + 3 * Z_r)))) Luv = tstack([L, u, v]) return from_range_100(Luv)
def HSL_to_RGB(HSL): """ Converts from *HSL* colourspace to *RGB* colourspace. Parameters ---------- HSL : array_like *HSL* colourspace array. Returns ------- ndarray *RGB* colourspace array. Notes ----- - Input *HSL* colourspace array is in domain [0, 1]. - Output *RGB* colourspace array is in range [0, 1]. References ---------- .. [6] EasyRGB. (n.d.). HSL —> RGB. Retrieved May 18, 2014, from http://www.easyrgb.com/index.php?X=MATH&H=19#text19 Examples -------- >>> HSL = np.array([0.27867384, 0.94897959, 0.61568627]) >>> HSL_to_RGB(HSL) # doctest: +ELLIPSIS array([ 0.4901960..., 0.9803921..., 0.2509803...]) """ H, S, L = tsplit(HSL) def H_to_RGB(vi, vj, vH): """ Converts *hue* value to *RGB* colourspace. """ vH = np.asarray(vH) vH[np.asarray(vH < 0)] += 1 vH[np.asarray(vH > 1)] -= 1 v = np.full(vi.shape, np.nan) v = np.where(np.logical_and(6 * vH < 1, np.isnan(v)), vi + (vj - vi) * 6 * vH, v) v = np.where(np.logical_and(2 * vH < 1, np.isnan(v)), vj, v) v = np.where(np.logical_and(3 * vH < 2, np.isnan(v)), vi + (vj - vi) * ((2 / 3) - vH) * 6, v) v = np.where(np.isnan(v), vi, v) return v j = np.where(L < 0.5, L * (1 + S), (L + S) - (S * L)) i = 2 * L - j R = H_to_RGB(i, j, H + (1 / 3)) G = H_to_RGB(i, j, H) B = H_to_RGB(i, j, H - (1 / 3)) R = np.where(S == 1, L, R) G = np.where(S == 1, L, G) B = np.where(S == 1, L, B) RGB = tstack((R, G, B)) return RGB
def XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround=VIEWING_CONDITIONS_CIECAM02['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 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 : InductionFactors_CIECAM02, optional Surround viewing conditions induction factors. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- 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 = tsplit( 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( from_range_100(J), from_range_100(C), from_range_degrees(h), from_range_100(s), from_range_100(Q), from_range_100(M), from_range_degrees(H, 400), None)
def CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround=VIEWING_CONDITIONS_CIECAM02['Average'], discount_illuminant=False): """ Converts from *CIECAM02* specification to *CIE XYZ* tristimulus values. This is the *inverse* implementation. Parameters ---------- specification : CAM_Specification_CIECAM02 *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 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 : InductionFactors_CIECAM02, 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 ``CAM_Specification_CIECAM02`` argument. Warnings -------- The output range of that definition is non standard! 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] | +------------------------------+-----------------------+---------------+ - ``CAM_Specification_CIECAM02`` 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 = 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 = as_namedtuple(specification, CAM_Specification_CIECAM02) J = to_domain_100(J) C = to_domain_100(C) if C is not None else C h = to_domain_degrees(h) M = to_domain_100(M) if M is not None else 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 = 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 "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)) # Computing 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(CAT02_INVERSE_CAT, RGB) return from_range_100(XYZ)
def XYZ_to_Luv( XYZ: ArrayLike, illuminant: ArrayLike = CCS_ILLUMINANTS[ "CIE 1931 2 Degree Standard Observer"]["D65"], ) -> NDArray: """ Convert from *CIE XYZ* tristimulus values to *CIE L\\*u\\*v\\** colourspace. Parameters ---------- XYZ *CIE XYZ* tristimulus values. illuminant Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- :class:`numpy.ndarray` *CIE L\\*u\\*v\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Luv`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``u`` : [-100, 100] | ``u`` : [-1, 1] | | | | | | | ``v`` : [-100, 100] | ``v`` : [-1, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wikipedia2007b` Examples -------- >>> import numpy as np >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_Luv(XYZ) # doctest: +ELLIPSIS array([ 41.5278752..., 96.8362605..., 17.7521014...]) """ X, Y, Z = tsplit(to_domain_1(XYZ)) X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) with domain_range_scale("100"): L = lightness_CIE1976(Y, Y_r) X_Y_Z = X + 15 * Y + 3 * Z X_r_Y_r_Z_r = X_r + 15 * Y_r + 3 * Z_r u = 13 * L * ((4 * X / X_Y_Z) - (4 * X_r / X_r_Y_r_Z_r)) v = 13 * L * ((9 * Y / X_Y_Z) - (9 * Y_r / X_r_Y_r_Z_r)) Luv = tstack([L, u, v]) return from_range_100(Luv)
def Luv_to_XYZ( Luv: ArrayLike, illuminant: ArrayLike = CCS_ILLUMINANTS[ "CIE 1931 2 Degree Standard Observer"]["D65"], ) -> NDArray: """ Convert from *CIE L\\*u\\*v\\** colourspace to *CIE XYZ* tristimulus values. Parameters ---------- Luv *CIE L\\*u\\*v\\** colourspace array. illuminant Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- :class:`numpy.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 -------- >>> import numpy as np >>> 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 CAM16_to_XYZ(CAM16_specification, XYZ_w, L_A, Y_b, surround=CAM16_VIEWING_CONDITIONS['Average'], discount_illuminant=False): """ Converts from *CAM16* specification to *CIE XYZ* tristimulus values. This is the *inverse* implementation. Parameters ---------- CAM16_specification : CAM16_Specification *CAM16* 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 : CAM16_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 ``CAM16_specification`` argument. Notes ----- +---------------------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +===========================+=======================+===============+ | ``CAM16_specification.J`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.C`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.h`` | [0, 360] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.s`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.Q`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.M`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.H`` | [0, 360] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ +---------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +===========================+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ - ``CAM16_specification`` can also be passed as a compatible argument to :func:`colour.utilities.as_namedtuple` definition. References ---------- :cite:`Li2017` Examples -------- >>> specification = CAM16_Specification(J=41.731207905126638, ... C=0.103355738709070, ... h=217.067959767393010) >>> XYZ_w = np.array([95.05, 100.00, 108.88]) >>> L_A = 318.31 >>> Y_b = 20.0 >>> CAM16_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(CAM16_specification, CAM16_Specification) J = to_domain_100(J) C = to_domain_100(C) if C is not None else C h = to_domain_degrees(h) M = to_domain_100(M) if M is not None else M L_A = as_float_array(L_A) XYZ_w = to_domain_100(XYZ_w) _X_w, Y_w, _Z_w = tsplit(XYZ_w) # Step 0 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values. RGB_w = dot_vector(M_16, XYZ_w) # Computing degree of adaptation :math:`D`. D = (np.clip(degree_of_adaptation(surround.F, L_A), 0, 1) if not discount_illuminant else np.ones(L_A.shape)) n, F_L, N_bb, N_cb, z = tsplit( viewing_condition_dependent_parameters(Y_b, Y_w, L_A)) D_RGB = (D[..., np.newaxis] * Y_w[..., np.newaxis] / RGB_w + 1 - D[..., np.newaxis]) RGB_wc = D_RGB * RGB_w # Applying forward post-adaptation non linear response compression. RGB_aw = post_adaptation_non_linear_response_compression_forward( RGB_wc, F_L) # Computing achromatic responses for the whitepoint. A_w = achromatic_response_forward(RGB_aw, N_bb) # Step 1 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 "CAM16_specification" argument!') # Step 2 # 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) # Step 3 # Computing opponent colour dimensions :math:`a` and :math:`b`. a, b = tsplit(opponent_colour_dimensions_inverse(P_n, h)) # Step 4 # Computing post-adaptation non linear response compression matrix. RGB_a = post_adaptation_non_linear_response_compression_matrix(P_2, a, b) # Step 5 # Applying inverse post-adaptation non linear response compression. RGB_c = post_adaptation_non_linear_response_compression_inverse(RGB_a, F_L) # Step 6 RGB = RGB_c / D_RGB # Step 7 XYZ = dot_vector(M_16_INVERSE, RGB) return from_range_100(XYZ)
def Lab_to_XYZ( Lab, illuminant=CCS_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* *CIE 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 -------- >>> import numpy as np >>> 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 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround=CAM16_VIEWING_CONDITIONS['Average'], discount_illuminant=False): """ Computes the *CAM16* 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 : CAM16_InductionFactors, optional Surround viewing conditions induction factors. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- CAM16_Specification *CAM16* 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** | +===========================+=======================+===============+ | ``CAM16_specification.J`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.C`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.h`` | [0, 360] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.s`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.Q`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.M`` | [0, 100] | [0, 1] | +---------------------------+-----------------------+---------------+ | ``CAM16_specification.H`` | [0, 360] | [0, 1] | +---------------------------+-----------------------+---------------+ References ---------- :cite:`Li2017` 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 = CAM16_VIEWING_CONDITIONS['Average'] >>> XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround) # doctest: +ELLIPSIS CAM16_Specification(J=41.7312079..., C=0.1033557..., h=217.0679597..., \ s=2.3450150..., Q=195.3717089..., M=0.1074367..., H=275.5949861..., 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) # Step 0 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values. RGB_w = dot_vector(M_16, XYZ_w) # Computing degree of adaptation :math:`D`. D = (np.clip(degree_of_adaptation(surround.F, L_A), 0, 1) if not discount_illuminant else np.ones(L_A.shape)) n, F_L, N_bb, N_cb, z = tsplit( viewing_condition_dependent_parameters(Y_b, Y_w, L_A)) D_RGB = (D[..., np.newaxis] * Y_w[..., np.newaxis] / RGB_w + 1 - D[..., np.newaxis]) RGB_wc = D_RGB * RGB_w # Applying forward post-adaptation non linear response compression. RGB_aw = post_adaptation_non_linear_response_compression_forward( RGB_wc, F_L) # Computing achromatic responses for the whitepoint. A_w = achromatic_response_forward(RGB_aw, N_bb) # Step 1 # Converting *CIE XYZ* tristimulus values to sharpened *RGB* values. RGB = dot_vector(M_16, XYZ) # Step 2 RGB_c = D_RGB * RGB # Step 3 # Applying forward post-adaptation non linear response compression. RGB_a = post_adaptation_non_linear_response_compression_forward(RGB_c, F_L) # Step 4 # 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) # Step 5 # Computing eccentricity factor *e_t*. e_t = eccentricity_factor(h) # Computing hue :math:`h` quadrature :math:`H`. H = hue_quadrature(h) # TODO: Compute hue composition. # Step 6 # Computing achromatic responses for the stimulus. A = achromatic_response_forward(RGB_a, N_bb) # Step 7 # Computing the correlate of *Lightness* :math:`J`. J = lightness_correlate(A, A_w, surround.c, z) # Step 8 # Computing the correlate of *brightness* :math:`Q`. Q = brightness_correlate(surround.c, J, A_w, F_L) # Step 9 # 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 CAM16_Specification(from_range_100(J), from_range_100(C), from_range_degrees(h), from_range_100(s), from_range_100(Q), from_range_100(M), from_range_degrees(H), None)
class TestCIECAM02ColourAppearanceModelReverse(ColourAppearanceModelTest): """ Defines :mod:`colour.appearance.ciecam02` module units tests methods for *CIECAM02* colour appearance model reverse implementation. """ FIXTURE_BASENAME = 'ciecam02.csv' OUTPUT_ATTRIBUTES = {'X': 0, 'Y': 1, 'Z': 2} def output_specification_from_data(self, data): """ Returns the colour appearance model output specification from given fixture data. Parameters ---------- data : list Tested colour appearance model fixture data. Notes ----- - This method is a dummy object. """ pass def _XYZ_from_data(self, data, correlates): """ Returns the *CIE XYZ* tristimulus values from given *CIECAM02* colour appearance model input data. Parameters ---------- data : list Fixture data. correlates : array_like Correlates used to build the input *CIECAM02* colour appearance model specification. Returns ------- array_like *CIE XYZ* tristimulus values """ XYZ_w = tstack([data['X_w'], data['Y_w'], data['Z_w']]) i, j, k = correlates CIECAM02_specification = as_namedtuple( { i: data[i], j: data[j], k: data[k] }, CIECAM02_Specification) XYZ = CIECAM02_to_XYZ( CIECAM02_specification, XYZ_w, data['L_A'], data['Y_b'], CIECAM02_InductionFactors(data['F'], data['c'], data['N_c'])) return XYZ def check_specification_attribute(self, case, data, attribute, expected): """ Tests *CIE XYZ* tristimulus values output from *CIECAM02* colour appearance model input data. Parameters ---------- case : int Fixture case number. data : dict. Fixture case data. attribute : unicode. Tested attribute name. expected : float. Expected attribute value. Warning ------- The method name does not reflect the underlying implementation. """ for correlates in (('J', 'C', 'h'), ('J', 'M', 'h')): XYZ = self._XYZ_from_data(data, correlates) value = tsplit(XYZ)[attribute] error_message = ('Parameter "{0}" in test case "{1}" ' 'does not match target value.\n' 'Expected: "{2}" \n' 'Received "{3}"').format(attribute, case, expected, value) np.testing.assert_allclose(value, expected, err_msg=error_message, rtol=0.01, atol=0.01, verbose=False) np.testing.assert_almost_equal(value, expected, decimal=1, err_msg=error_message)
def plot_ellipses_MacAdam1942_in_chromaticity_diagram( chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', chromaticity_diagram_clipping=False, ellipse_parameters=None, **kwargs): """ Plots *MacAdam (1942) Ellipses (Observer PGN)* in the *Chromaticity Diagram* according to given method. Parameters ---------- chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. chromaticity_diagram_clipping : bool, optional, Whether to clip the *Chromaticity Diagram* colours with the ellipses. ellipse_parameters : dict or array_like, optional Parameters for the :class:`Ellipse` class, ``ellipse_parameters`` can be either a single dictionary applied to all the ellipses with same settings or a sequence of dictionaries with different settings for each ellipse. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram() # doctest: +SKIP .. image:: ../_static/\ Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png :align: center :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) settings = dict(kwargs) settings.update({'axes': axes, 'standalone': False}) ellipses_coefficients = ellipses_MacAdam1942(method=method) if chromaticity_diagram_clipping: diagram_clipping_path_x = [] diagram_clipping_path_y = [] for coefficients in ellipses_coefficients: coefficients = np.copy(coefficients) coefficients[2:4] /= 2 x, y = tsplit( point_at_angle_on_ellipse( np.linspace(0, 360, 36), coefficients, )) diagram_clipping_path_x.append(x) diagram_clipping_path_y.append(y) diagram_clipping_path = np.rollaxis( np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3) diagram_clipping_path = Path.make_compound_path_from_polys( diagram_clipping_path).vertices settings.update({'diagram_clipping_path': diagram_clipping_path}) chromaticity_diagram_callable(**settings) ellipse_settings_collection = [{ 'color': COLOUR_STYLE_CONSTANTS.colour.cycle[4], 'alpha': 0.4, 'edgecolor': COLOUR_STYLE_CONSTANTS.colour.cycle[1], 'linewidth': colour_style()['lines.linewidth'] } for _ in range(len(ellipses_coefficients))] if ellipse_parameters is not None: if not isinstance(ellipse_parameters, dict): assert len(ellipse_parameters) == len(ellipses_coefficients), ( 'Multiple ellipse parameters defined, but they do not match ' 'the ellipses count!') for i, ellipse_settings in enumerate(ellipse_settings_collection): if isinstance(ellipse_parameters, dict): ellipse_settings.update(ellipse_parameters) else: ellipse_settings.update(ellipse_parameters[i]) for i, coefficients in enumerate(ellipses_coefficients): x_c, y_c, a_a, a_b, theta_e = coefficients ellipse = Ellipse((x_c, y_c), a_a, a_b, theta_e, **ellipse_settings_collection[i]) axes.add_artist(ellipse) settings.update({'standalone': True}) settings.update(kwargs) return render(**settings)
def delta_E_CMC(Lab_1, Lab_2, l=2, c=1): """ Returns the difference :math:`\Delta E_{ab}` between two given *CIE Lab* 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 Lab* colourspace array 1. Lab_2 : array_like *CIE Lab* colourspace array 2. l : numeric, optional Lightness weighting factor. c : numeric, optional Chroma weighting factor. Returns ------- numeric or ndarray Colour difference :math:`\Delta E_{ab}`. References ---------- .. [5] Lindbloom, B. (2009). Delta E (CMC). Retrieved February 24, 2014, from http://brucelindbloom.com/Eqn_DeltaE_CMC.html 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(Lab_1) L_2, a_2, b_2 = tsplit(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 ellipse_fitting_Halir1998(a): """ Returns the coefficients of the implicit second-order polynomial/quadratic curve that fits given point array :math:`a` using *Halir and Flusser (1998)* method. The implicit second-order polynomial is expressed as follows:: :math:`F\\left(x, y\\right)` = ax^2 + bxy + cy^2 + dx + ey + f = 0` with an ellipse-specific constraint such as :math:`b^2 -4ac < 0` and where :math:`a, b, c, d, e, f` are coefficients of the ellipse and :math:`F\\left(x, y\\right)` are coordinates of points lying on it. Parameters ---------- a : array_like Point array :math:`a` to be fitted. Returns ------- ndarray Coefficients of the implicit second-order polynomial/quadratic curve that fits given point array :math:`a`. References ---------- :cite:`Halir1998` Examples -------- >>> a = np.array([[2, 0], [0, 1], [-2, 0], [0, -1]]) >>> ellipse_fitting_Halir1998(a) # doctest: +ELLIPSIS array([ 0.2425356..., 0. , 0.9701425..., 0. , 0. , -0.9701425...]) >>> ellipse_coefficients_canonical_form(ellipse_fitting_Halir1998(a)) array([-0., -0., 2., 1., 0.]) """ x, y = tsplit(a) # Quadratic part of the design matrix. D1 = tstack([x**2, x * y, y**2]) # Linear part of the design matrix. D2 = tstack([x, y, np.ones(x.shape)]) D1_T = np.transpose(D1) D2_T = np.transpose(D2) # Quadratic part of the scatter matrix. S1 = np.dot(D1_T, D1) # Combined part of the scatter matrix. S2 = np.dot(D1_T, D2) # Linear part of the scatter matrix. S3 = np.dot(D2_T, D2) T = -np.dot(np.linalg.inv(S3), np.transpose(S2)) # Reduced scatter matrix. M = S1 + np.dot(S2, T) M = np.array([M[2, :] / 2, -M[1, :], M[0, :] / 2]) w, v = np.linalg.eig(M) A1 = v[:, np.nonzero(4 * v[0, :] * v[2, :] - v[1, :]**2 > 0)[0]] A2 = np.dot(T, A1) A = np.ravel([A1, A2]) return A
def delta_E_CIE2000(Lab_1, Lab_2, textiles=False): """ Returns the difference :math:`\Delta E_{ab}` between two given *CIE Lab* colourspace arrays using *CIE 2000* recommendation. Parameters ---------- Lab_1 : array_like *CIE Lab* colourspace array 1. Lab_2 : array_like *CIE Lab* 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_{ab}`. Notes ----- - *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*: [5]_ - 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_{ab}` - Sample structure: Homogeneous (without texture) References ---------- .. [4] Lindbloom, B. (2009). Delta E (CIE 2000). Retrieved February 24, 2014, from http://brucelindbloom.com/Eqn_DeltaE_CIE2000.html .. [5] Melgosa, M. (2013). CIE / ISO new standard: CIEDE2000, 2013(July). Retrieved from http://www.color.org/events/colorimetry/\ Melgosa_CIEDE2000_Workshop-July4.pdf 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... """ k_L = 2 if textiles else 1 k_C = 1 k_H = 1 L_1, a_1, b_1 = tsplit(Lab_1) L_2, a_2, b_2 = tsplit(Lab_2) 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 = np.power(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 sd_CIE_illuminant_D_series(xy, M1_M2_rounding=True): """ Returns the spectral distribution of given *CIE Illuminant D Series* using given *CIE xy* chromaticity coordinates. Parameters ---------- xy : array_like *CIE xy* chromaticity coordinates. M1_M2_rounding : bool, optional Whether to round :math:`M1` and :math:`M2` variables to 3 decimal places in order to yield the internationally agreed values. Returns ------- SpectralDistribution *CIE Illuminant D Series* spectral distribution. Notes ----- - The nominal *CIE xy* chromaticity coordinates which have been computed with :func:`colour.temperature.CCT_to_xy_CIE_D` must be given according to *CIE 015:2004* recommendation and thus multiplied by 1.4388 / 1.4380. - :math:`M1` and :math:`M2` variables are rounded to 3 decimal places according to *CIE 015:2004* recommendation. References ---------- :cite:`CIETC1-482004`, :cite:`Wyszecki2000z` Examples -------- >>> from colour.utilities import numpy_print_options >>> from colour.temperature import CCT_to_xy_CIE_D >>> CCT_D65 = 6500 * 1.4388 / 1.4380 >>> xy = CCT_to_xy_CIE_D(CCT_D65) >>> with numpy_print_options(suppress=True): ... sd_CIE_illuminant_D_series(xy) # doctest: +ELLIPSIS SpectralDistribution([[ 300. , 0.0341...], [ 305. , 1.6643...], [ 310. , 3.2945...], [ 315. , 11.7652...], [ 320. , 20.236 ...], [ 325. , 28.6447...], [ 330. , 37.0535...], [ 335. , 38.5011...], [ 340. , 39.9488...], [ 345. , 42.4302...], [ 350. , 44.9117...], [ 355. , 45.775 ...], [ 360. , 46.6383...], [ 365. , 49.3637...], [ 370. , 52.0891...], [ 375. , 51.0323...], [ 380. , 49.9755...], [ 385. , 52.3118...], [ 390. , 54.6482...], [ 395. , 68.7015...], [ 400. , 82.7549...], [ 405. , 87.1204...], [ 410. , 91.486 ...], [ 415. , 92.4589...], [ 420. , 93.4318...], [ 425. , 90.0570...], [ 430. , 86.6823...], [ 435. , 95.7736...], [ 440. , 104.8649...], [ 445. , 110.9362...], [ 450. , 117.0076...], [ 455. , 117.4099...], [ 460. , 117.8122...], [ 465. , 116.3365...], [ 470. , 114.8609...], [ 475. , 115.3919...], [ 480. , 115.9229...], [ 485. , 112.3668...], [ 490. , 108.8107...], [ 495. , 109.0826...], [ 500. , 109.3545...], [ 505. , 108.5781...], [ 510. , 107.8017...], [ 515. , 106.2957...], [ 520. , 104.7898...], [ 525. , 106.2396...], [ 530. , 107.6895...], [ 535. , 106.0475...], [ 540. , 104.4055...], [ 545. , 104.2258...], [ 550. , 104.0462...], [ 555. , 102.0231...], [ 560. , 100. ...], [ 565. , 98.1671...], [ 570. , 96.3342...], [ 575. , 96.0611...], [ 580. , 95.788 ...], [ 585. , 92.2368...], [ 590. , 88.6856...], [ 595. , 89.3459...], [ 600. , 90.0062...], [ 605. , 89.8026...], [ 610. , 89.5991...], [ 615. , 88.6489...], [ 620. , 87.6987...], [ 625. , 85.4936...], [ 630. , 83.2886...], [ 635. , 83.4939...], [ 640. , 83.6992...], [ 645. , 81.863 ...], [ 650. , 80.0268...], [ 655. , 80.1207...], [ 660. , 80.2146...], [ 665. , 81.2462...], [ 670. , 82.2778...], [ 675. , 80.281 ...], [ 680. , 78.2842...], [ 685. , 74.0027...], [ 690. , 69.7213...], [ 695. , 70.6652...], [ 700. , 71.6091...], [ 705. , 72.9790...], [ 710. , 74.349 ...], [ 715. , 67.9765...], [ 720. , 61.604 ...], [ 725. , 65.7448...], [ 730. , 69.8856...], [ 735. , 72.4863...], [ 740. , 75.087 ...], [ 745. , 69.3398...], [ 750. , 63.5927...], [ 755. , 55.0054...], [ 760. , 46.4182...], [ 765. , 56.6118...], [ 770. , 66.8054...], [ 775. , 65.0941...], [ 780. , 63.3828...], [ 785. , 63.8434...], [ 790. , 64.304 ...], [ 795. , 61.8779...], [ 800. , 59.4519...], [ 805. , 55.7054...], [ 810. , 51.959 ...], [ 815. , 54.6998...], [ 820. , 57.4406...], [ 825. , 58.8765...], [ 830. , 60.3125...]], interpolator=LinearInterpolator, interpolator_kwargs={}, extrapolator=Extrapolator, extrapolator_kwargs={...}) """ x, y = tsplit(xy) M = 0.0241 + 0.2562 * x - 0.7341 * y M1 = (-1.3515 - 1.7703 * x + 5.9114 * y) / M M2 = (0.0300 - 31.4424 * x + 30.0717 * y) / M if M1_M2_rounding: M1 = np.around(M1, 3) M2 = np.around(M2, 3) S0 = SDS_BASIS_FUNCTIONS_CIE_ILLUMINANT_D_SERIES['S0'] S1 = SDS_BASIS_FUNCTIONS_CIE_ILLUMINANT_D_SERIES['S1'] S2 = SDS_BASIS_FUNCTIONS_CIE_ILLUMINANT_D_SERIES['S2'] distribution = S0.values + M1 * S1.values + M2 * S2.values return SpectralDistribution( distribution, S0.wavelengths, name='CIE xy ({0}, {1}) - CIE Illuminant D Series'.format(*xy), interpolator=LinearInterpolator)
def planckian_table( uv: ArrayLike, cmfs: MultiSpectralDistributions, start: Floating, end: Floating, count: Integer, ) -> List[PlanckianTableRow]: """ Return a planckian table from given *CIE UCS* colourspace *uv* chromaticity coordinates, colour matching functions and temperature range using *Ohno (2013)* method. Parameters ---------- uv *uv* chromaticity coordinates. cmfs Standard observer colour matching functions. start Temperature range start in kelvin degrees. end Temperature range end in kelvin degrees. count Temperatures count in the planckian table. Returns ------- :class:`list` Planckian table. Examples -------- >>> from colour import MSDS_CMFS, SPECTRAL_SHAPE_DEFAULT >>> cmfs = ( ... MSDS_CMFS['CIE 1931 2 Degree Standard Observer']. ... copy().align(SPECTRAL_SHAPE_DEFAULT) ... ) >>> uv = np.array([0.1978, 0.3122]) >>> pprint(planckian_table(uv, cmfs, 1000, 1010, 10)) ... # doctest: +SKIP [PlanckianTableRow(Ti=1000.0, ui=0.4479628..., \ vi=0.3546296..., di=0.2537355...), PlanckianTableRow(Ti=1001.1111111..., ui=0.4477030..., \ vi=0.3546521..., di=0.2534831...), PlanckianTableRow(Ti=1002.2222222..., ui=0.4474434..., \ vi=0.3546746..., di=0.2532310...), PlanckianTableRow(Ti=1003.3333333..., ui=0.4471842..., \ vi=0.3546970..., di=0.2529792...), PlanckianTableRow(Ti=1004.4444444..., ui=0.4469252..., \ vi=0.3547194..., di=0.2527277...), PlanckianTableRow(Ti=1005.5555555..., ui=0.4466666..., \ vi=0.3547417..., di=0.2524765...), PlanckianTableRow(Ti=1006.6666666..., ui=0.4464083..., \ vi=0.3547640..., di=0.2522256...), PlanckianTableRow(Ti=1007.7777777..., ui=0.4461502..., \ vi=0.3547862..., di=0.2519751...), PlanckianTableRow(Ti=1008.8888888..., ui=0.4458925..., \ vi=0.3548084..., di=0.2517248...), PlanckianTableRow(Ti=1010.0, ui=0.4456351..., \ vi=0.3548306..., di=0.2514749...)] """ ux, vx = tsplit(uv) table = [] for Ti in np.linspace(start, end, count): sd = sd_blackbody(Ti, cmfs.shape) XYZ = sd_to_XYZ(sd, cmfs) XYZ /= np.max(XYZ) UVW = XYZ_to_UCS(XYZ) ui, vi = UCS_to_uv(UVW) di = np.hypot(ux - ui, vx - vi) table.append(PlanckianTableRow(Ti, ui, vi, di)) return table
def ellipse_coefficients_canonical_form(coefficients): """ Returns the canonical form ellipse coefficients from given general form ellipse coefficients. The general form ellipse coefficients are the coefficients of the implicit second-order polynomial/quadratic curve expressed as follows: :math:`F\\left(x, y\\right)` = ax^2 + bxy + cy^2 + dx + ey + f = 0` with an ellipse-specific constraint such as :math:`b^2 -4ac < 0` and where :math:`a, b, c, d, e, f` are coefficients of the ellipse and :math:`F\\left(x, y\\right)` are coordinates of points lying on it. Parameters ---------- coefficients : array_like General form ellipse coefficients. Returns ------- ndarray Canonical form ellipse coefficients. References ---------- :cite:`Wikipedia` Examples -------- >>> coefficients = np.array([ 2.5, -3.0, 2.5, -1.0, -1.0, -3.5]) >>> ellipse_coefficients_canonical_form(coefficients) array([ 0.5, 0.5, 2. , 1. , 45. ]) """ a, b, c, d, e, f = tsplit(coefficients) d_1 = b**2 - 4 * a * c n_p_1 = 2 * (a * e**2 + c * d**2 - b * d * e + d_1 * f) n_p_2 = np.sqrt((a - c)**2 + b**2) a_a = -np.sqrt(n_p_1 * (a + c + n_p_2)) / d_1 a_b = -np.sqrt(n_p_1 * (a + c - n_p_2)) / d_1 x_c = (2 * c * d - b * e) / d_1 y_c = (2 * a * e - b * d) / d_1 theta = np.select( [ np.logical_and(b == 0, a < c), np.logical_and(b == 0, a > c), b != 0, ], [ 0, 90, np.degrees(np.arctan((c - a - n_p_2) / b)), ], ) return np.array([x_c, y_c, a_a, a_b, theta])
def corresponding_colour( RGB_1: ArrayLike, xez_1: ArrayLike, xez_2: ArrayLike, bRGB_o1: ArrayLike, bRGB_o2: ArrayLike, Y_o: FloatingOrArrayLike, K: FloatingOrArrayLike, n: FloatingOrArrayLike = 1, ) -> NDArray: """ Compute the corresponding colour cone responses of given test sample cone responses :math:`RGB_1`. Parameters ---------- RGB_1 Test sample cone responses :math:`RGB_1`. xez_1 Intermediate values :math:`\\xi_1`, :math:`\\eta_1`, :math:`\\zeta_1` for the test illuminant and background. xez_2 Intermediate values :math:`\\xi_2`, :math:`\\eta_2`, :math:`\\zeta_2` for the reference illuminant and background. bRGB_o1 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o1})`, :math:`\\beta_1(G_{o1})` and :math:`\\beta_2(B_{o1})` of test sample. bRGB_o2 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o2})`, :math:`\\beta_1(G_{o2})` and :math:`\\beta_2(B_{o2})` of reference sample. Y_o Luminance factor :math:`Y_o` of achromatic background as percentage normalised to domain [18, 100] in **'Reference'** domain-range scale. K Coefficient :math:`K`. n Noise component in fundamental primary system. Returns ------- :class:`numpy.ndarray` Corresponding colour cone responses of given test sample cone responses. Examples -------- >>> RGB_1 = np.array([25.82442730, 18.67914220, 4.83901940]) >>> xez_1 = np.array([1.11857195, 0.93295530, 0.32680879]) >>> xez_2 = np.array([1.00000372, 1.00000176, 0.99999461]) >>> bRGB_o1 = np.array([3.74852518, 3.63920879, 2.78924811]) >>> bRGB_o2 = np.array([3.68102374, 3.68102256, 3.56557351]) >>> Y_o = 20 >>> K = 1 >>> corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K) ... # doctest: +ELLIPSIS array([ 23.1636901..., 20.0211948..., 16.2001664...]) """ R_1, G_1, B_1 = tsplit(RGB_1) xi_1, eta_1, zeta_1 = tsplit(xez_1) xi_2, eta_2, zeta_2 = tsplit(xez_2) bR_o1, bG_o1, bB_o1 = tsplit(bRGB_o1) bR_o2, bG_o2, bB_o2 = tsplit(bRGB_o2) Y_o = as_float_array(Y_o) K = as_float_array(K) n = as_float_array(n) def RGB_c( x_1: NDArray, x_2: NDArray, y_1: NDArray, y_2: NDArray, z: NDArray, n: NDArray, ) -> NDArray: """Compute the corresponding colour cone responses component.""" return (Y_o * x_2 + n) * spow(K, 1 / y_2) * spow( (z + n) / (Y_o * x_1 + n), y_1 / y_2) - n R_2 = RGB_c(xi_1, xi_2, bR_o1, bR_o2, R_1, n) G_2 = RGB_c(eta_1, eta_2, bG_o1, bG_o2, G_1, n) B_2 = RGB_c(zeta_1, zeta_2, bB_o1, bB_o2, B_1, n) RGB_2 = tstack([R_2, G_2, B_2]) return RGB_2
def _uv_to_CCT_Ohno2013( uv: ArrayLike, cmfs: Optional[MultiSpectralDistributions] = None, start: Floating = CCT_MINIMAL, end: Floating = CCT_MAXIMAL, count: Integer = CCT_SAMPLES, iterations: Integer = CCT_CALCULATION_ITERATIONS, ) -> NDArray: """ Return the correlated colour temperature :math:`T_{cp}` and :math:`\\Delta_{uv}` from given *CIE UCS* colourspace *uv* chromaticity coordinates, colour matching functions and temperature range using *Ohno (2013)* method. The ``iterations`` parameter defines the calculations' precision: The higher its value, the more planckian tables will be generated through cascade expansion in order to converge to the exact solution. Parameters ---------- uv *CIE UCS* colourspace *uv* chromaticity coordinates. cmfs Standard observer colour matching functions, default to the *CIE 1931 2 Degree Standard Observer*. start Temperature range start in kelvin degrees. end Temperature range end in kelvin degrees. count Temperatures count in the planckian tables. iterations Number of planckian tables to generate. Returns ------- :class:`numpy.ndarray` Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. """ cmfs, _illuminant = handle_spectral_arguments(cmfs) # Ensuring that we do at least one iteration to initialise the variables. iterations = max(int(iterations), 1) # Planckian table creation through cascade expansion. for _i in range(iterations): table = planckian_table(uv, cmfs, start, end, count) index = planckian_table_minimal_distance_index(table) if index == 0: runtime_warning( "Minimal distance index is on lowest planckian table bound, " "unpredictable results may occur!") index += 1 elif index == len(table) - 1: runtime_warning( "Minimal distance index is on highest planckian table bound, " "unpredictable results may occur!") index -= 1 start = table[index - 1].Ti end = table[index + 1].Ti _ux, vx = tsplit(uv) Tuvdip, Tuvdi, Tuvdin = (table[index - 1], table[index], table[index + 1]) Tip, uip, vip, dip = Tuvdip.Ti, Tuvdip.ui, Tuvdip.vi, Tuvdip.di Ti, di = Tuvdi.Ti, Tuvdi.di Tin, uin, vin, din = Tuvdin.Ti, Tuvdin.ui, Tuvdin.vi, Tuvdin.di # Triangular solution. l = np.hypot(uin - uip, vin - vip) # noqa x = (dip**2 - din**2 + l**2) / (2 * l) T = Tip + (Tin - Tip) * (x / l) vtx = vip + (vin - vip) * (x / l) sign = 1 if vx - vtx >= 0 else -1 D_uv = (dip**2 - x**2)**(1 / 2) * sign # Parabolic solution. if np.abs(D_uv) >= 0.002: X = (Tin - Ti) * (Tip - Tin) * (Ti - Tip) a = (Tip * (din - di) + Ti * (dip - din) + Tin * (di - dip)) * X**-1 b = (-(Tip**2 * (din - di) + Ti**2 * (dip - din) + Tin**2 * (di - dip)) * X**-1) c = (-(dip * (Tin - Ti) * Ti * Tin + di * (Tip - Tin) * Tip * Tin + din * (Ti - Tip) * Tip * Ti) * X**-1) T = -b / (2 * a) D_uv = sign * (a * T**2 + b * T + c) return np.array([T, D_uv])
def hdr_CIELab_to_XYZ( Lab_hdr, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65'], Y_s=0.2, Y_abs=100, method='Fairchild 2011'): """ Converts from *hdr-CIELAB* colourspace to *CIE XYZ* tristimulus values. Parameters ---------- Lab_hdr : array_like *hdr-CIELAB* colourspace array. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* 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** | +================+=========================+=====================+ | ``Lab_hdr`` | ``L_hdr`` : [0, 100] | ``L_hdr`` : [0, 1] | | | | | | | ``a_hdr`` : [-100, 100] | ``a_hdr`` : [-1, 1] | | | | | | | ``b_hdr`` : [-100, 100] | ``b_hdr`` : [-1, 1] | +----------------+-------------------------+---------------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ | ``Y_s`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ +----------------+-------------------------+---------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=========================+=====================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ References ---------- :cite:`Fairchild2010`, :cite:`Fairchild2011` Examples -------- >>> Lab_hdr = np.array([51.87002062, 60.4763385, 32.14551912]) >>> hdr_CIELab_to_XYZ(Lab_hdr) # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) >>> Lab_hdr = np.array([31.99621114, 128.00763036, 48.76952309]) >>> hdr_CIELab_to_XYZ(Lab_hdr, method='Fairchild 2010') ... # doctest: +ELLIPSIS array([ 0.2065400..., 0.1219722..., 0.0513695...]) """ L_hdr, a_hdr, b_hdr = tsplit(to_domain_100(Lab_hdr)) X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) method_l = method.lower() assert method.lower() in [ m.lower() for m in HDR_CIELAB_METHODS ], ('"{0}" method is invalid, must be one of {1}!'.format( method, HDR_CIELAB_METHODS)) if method_l == 'fairchild 2010': luminance_callable = luminance_Fairchild2010 else: luminance_callable = luminance_Fairchild2011 e = exponent_hdr_CIELab(Y_s, Y_abs, method) # Domain and range scaling has already be handled. with domain_range_scale('ignore'): Y = luminance_callable(L_hdr, e) * Y_n X = luminance_callable((a_hdr + 5 * L_hdr) / 5, e) * X_n Z = luminance_callable((-b_hdr + 2 * L_hdr) / 2, e) * Z_n XYZ = tstack([X, Y, Z]) return from_range_1(XYZ)
def K_coefficient( xez_1: ArrayLike, xez_2: ArrayLike, bRGB_o1: ArrayLike, bRGB_o2: ArrayLike, Y_o: FloatingOrArrayLike, n: FloatingOrArrayLike = 1, ) -> FloatingOrNDArray: """ Compute the coefficient :math:`K` for correcting the difference between the test and references illuminances. Parameters ---------- xez_1 Intermediate values :math:`\\xi_1`, :math:`\\eta_1`, :math:`\\zeta_1` for the test illuminant and background. xez_2 Intermediate values :math:`\\xi_2`, :math:`\\eta_2`, :math:`\\zeta_2` for the reference illuminant and background. bRGB_o1 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o1})`, :math:`\\beta_1(G_{o1})` and :math:`\\beta_2(B_{o1})` of test sample. bRGB_o2 Chromatic adaptation exponential factors :math:`\\beta_1(R_{o2})`, :math:`\\beta_1(G_{o2})` and :math:`\\beta_2(B_{o2})` of reference sample. Y_o Luminance factor :math:`Y_o` of achromatic background as percentage normalised to domain [18, 100] in **'Reference'** domain-range scale. n Noise component in fundamental primary system. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Coefficient :math:`K`. Examples -------- >>> xez_1 = np.array([1.11857195, 0.93295530, 0.32680879]) >>> xez_2 = np.array([1.00000372, 1.00000176, 0.99999461]) >>> bRGB_o1 = np.array([3.74852518, 3.63920879, 2.78924811]) >>> bRGB_o2 = np.array([3.68102374, 3.68102256, 3.56557351]) >>> Y_o = 20 >>> K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o) 1.0 """ xi_1, eta_1, _zeta_1 = tsplit(xez_1) xi_2, eta_2, _zeta_2 = tsplit(xez_2) bR_o1, bG_o1, _bB_o1 = tsplit(bRGB_o1) bR_o2, bG_o2, _bB_o2 = tsplit(bRGB_o2) Y_o = as_float_array(Y_o) n = as_float_array(n) K = spow((Y_o * xi_1 + n) / (20 * xi_1 + n), (2 / 3) * bR_o1) / spow( (Y_o * xi_2 + n) / (20 * xi_2 + n), (2 / 3) * bR_o2) K *= spow((Y_o * eta_1 + n) / (20 * eta_1 + n), (1 / 3) * bG_o1) / spow( (Y_o * eta_2 + n) / (20 * eta_2 + n), (1 / 3) * bG_o2) return K
def ootf_reverse_BT2100_HLG(F_D, L_B=0, L_W=1000, gamma=None): """ Defines *Recommendation ITU-R BT.2100* *Reference HLG* reverse opto-optical transfer function (OOTF / OOCF). Parameters ---------- F_D : numeric or array_like :math:`F_D` is the luminance of a displayed linear component :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`. L_B : numeric, optional :math:`L_B` is the display luminance for black in :math:`cd/m^2`. L_W : numeric, optional :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2` for achromatic pixels. gamma : numeric, optional System gamma value, 1.2 at the nominal display peak luminance of :math:`1000 cd/m^2`. Returns ------- numeric or ndarray :math:`E` is the signal for each colour component :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled by camera exposure, normalized to the range [0, 1]. References ---------- - :cite:`Borer2017a` - :cite:`InternationalTelecommunicationUnion2016a` Examples -------- >>> ootf_reverse_BT2100_HLG(63.095734448019336) # doctest: +ELLIPSIS 0.1000000... """ F_D = np.atleast_1d(F_D) if F_D.shape[-1] != 3: warning( '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses ' 'RGB Luminance in computations and expects a vector input, thus ' 'the given input array will be stacked to compose a vector for ' 'internal computations but a single component will be output.') R_D = G_D = B_D = F_D else: R_D, G_D, B_D = tsplit(F_D) Y_D = np.sum(BT2100_HLG_WEIGHTS * tstack((R_D, G_D, B_D)), axis=-1) alpha = L_W - L_B beta = L_B if gamma is None: gamma = function_gamma_BT2100_HLG(L_W) R_S = np.where(Y_D == beta, 0.0, (np.abs( (Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (R_D - beta) / alpha) G_S = np.where(Y_D == beta, 0.0, (np.abs( (Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (G_D - beta) / alpha) B_S = np.where(Y_D == beta, 0.0, (np.abs( (Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (B_D - beta) / alpha) if F_D.shape[-1] != 3: return as_numeric(R_S) else: return tstack((R_S, G_S, B_S))
def XYZ_to_hdr_CIELab( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65'], Y_s=0.2, Y_abs=100, method='Fairchild 2011'): """ Converts from *CIE XYZ* tristimulus values to *hdr-CIELAB* colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like, optional Reference *illuminant* *xy* chromaticity coordinates or *CIE xyY* 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 *hdr-CIELAB* colourspace array. Notes ----- +----------------+-------------------------+---------------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=========================+=====================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ | ``Y_s`` | [0, 1] | [0, 1] | +----------------+-------------------------+---------------------+ +----------------+-------------------------+---------------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=========================+=====================+ | ``Lab_hdr`` | ``L_hdr`` : [0, 100] | ``L_hdr`` : [0, 1] | | | | | | | ``a_hdr`` : [-100, 100] | ``a_hdr`` : [-1, 1] | | | | | | | ``b_hdr`` : [-100, 100] | ``b_hdr`` : [-1, 1] | +----------------+-------------------------+---------------------+ - Conversion to polar coordinates to compute the *chroma* :math:`C_{hdr}` and *hue* :math:`h_{hdr}` correlates can be safely performed with :func:`colour.Lab_to_LCHab` definition. - Conversion to cartesian coordinates from the *Lightness* :math:`L_{hdr}`, *chroma* :math:`C_{hdr}` and *hue* :math:`h_{hdr}` correlates can be safely performed with :func:`colour.LCHab_to_Lab` definition. References ---------- :cite:`Fairchild2010`, :cite:`Fairchild2011` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_to_hdr_CIELab(XYZ) # doctest: +ELLIPSIS array([ 51.8700206..., 60.4763385..., 32.1455191...]) >>> XYZ_to_hdr_CIELab(XYZ, method='Fairchild 2010') # doctest: +ELLIPSIS array([ 31.9962111..., 128.0076303..., 48.7695230...]) """ X, Y, Z = tsplit(to_domain_1(XYZ)) X_n, Y_n, Z_n = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) method_l = method.lower() assert method.lower() in [ m.lower() for m in HDR_CIELAB_METHODS ], ('"{0}" method is invalid, must be one of {1}!'.format( method, HDR_CIELAB_METHODS)) if method_l == 'fairchild 2010': lightness_callable = lightness_Fairchild2010 else: lightness_callable = lightness_Fairchild2011 e = exponent_hdr_CIELab(Y_s, Y_abs, method) # Domain and range scaling has already be handled. with domain_range_scale('ignore'): L_hdr = lightness_callable(Y / Y_n, e) a_hdr = 5 * (lightness_callable(X / X_n, e) - L_hdr) b_hdr = 2 * (L_hdr - lightness_callable(Z / Z_n, e)) Lab_hdr = tstack([L_hdr, a_hdr, b_hdr]) return from_range_100(Lab_hdr)
def XYZ_to_Hunter_Lab(XYZ, XYZ_n=HUNTERLAB_ILLUMINANTS[ 'CIE 1931 2 Degree Standard Observer']['D65'].XYZ_n, K_ab=HUNTERLAB_ILLUMINANTS[ 'CIE 1931 2 Degree Standard Observer']['D65'].K_ab): """ Converts from *CIE XYZ* tristimulus values to *Hunter L,a,b* colour scale. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. XYZ_n : array_like, optional Reference *illuminant* tristimulus values. K_ab : array_like, optional Reference *illuminant* chromaticity coefficients, if ``K_ab`` is set to *None* it will be computed using :func:`colour.XYZ_to_K_ab_HunterLab1966`. Returns ------- ndarray *Hunter L,a,b* colour scale array. Notes ----- +------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+-----------------+ | ``XYZ_n`` | [0, 100] | [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:`HunterLab2008b` Examples -------- >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) * 100 >>> D65 = HUNTERLAB_ILLUMINANTS[ ... 'CIE 1931 2 Degree Standard Observer']['D65'] >>> XYZ_to_Hunter_Lab(XYZ, D65.XYZ_n, D65.K_ab) # doctest: +ELLIPSIS array([ 34.9245257..., 47.0618985..., 14.3861510...]) """ X, Y, Z = tsplit(to_domain_100(XYZ)) X_n, Y_n, Z_n = tsplit(to_domain_100(XYZ_n)) K_a, K_b = (tsplit(XYZ_to_K_ab_HunterLab1966(XYZ_n)) if K_ab is None else tsplit(K_ab)) Y_Y_n = Y / Y_n sqrt_Y_Y_n = np.sqrt(Y_Y_n) L = 100 * sqrt_Y_Y_n a = K_a * ((X / X_n - Y_Y_n) / sqrt_Y_Y_n) b = K_b * ((Y_Y_n - Z / Z_n) / sqrt_Y_Y_n) Lab = tstack([L, a, b]) return from_range_100(Lab)
def ootf_BT2100_HLG(E, L_B=0, L_W=1000, gamma=None): """ Defines *Recommendation ITU-R BT.2100* *Reference HLG* opto-optical transfer function (OOTF / OOCF). The OOTF maps relative scene linear light to display linear light. Parameters ---------- E : numeric or array_like :math:`E` is the signal for each colour component :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled by camera exposure, normalized to the range [0, 1]. L_B : numeric, optional :math:`L_B` is the display luminance for black in :math:`cd/m^2`. L_W : numeric, optional :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2` for achromatic pixels. gamma : numeric, optional System gamma value, 1.2 at the nominal display peak luminance of :math:`1000 cd/m^2`. Returns ------- numeric or ndarray :math:`F_D` is the luminance of a displayed linear component :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`. References ---------- - :cite:`Borer2017a` - :cite:`InternationalTelecommunicationUnion2016a` Examples -------- >>> ootf_BT2100_HLG(0.1) # doctest: +ELLIPSIS 63.0957344... """ E = np.atleast_1d(E) if E.shape[-1] != 3: warning( '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses ' 'RGB Luminance in computations and expects a vector input, thus ' 'the given input array will be stacked to compose a vector for ' 'internal computations but a single component will be output.') R_S = G_S = B_S = E else: R_S, G_S, B_S = tsplit(E) alpha = L_W - L_B beta = L_B Y_S = np.sum(BT2100_HLG_WEIGHTS * tstack((R_S, G_S, B_S)), axis=-1) if gamma is None: gamma = function_gamma_BT2100_HLG(L_W) R_D = alpha * R_S * np.abs(Y_S)**(gamma - 1) + beta G_D = alpha * G_S * np.abs(Y_S)**(gamma - 1) + beta B_D = alpha * B_S * np.abs(Y_S)**(gamma - 1) + beta if E.shape[-1] != 3: return as_numeric(R_D) else: return tstack((R_D, G_D, B_D))
def Hunter_Lab_to_XYZ(Lab, XYZ_n=HUNTERLAB_ILLUMINANTS[ 'CIE 1931 2 Degree Standard Observer']['D65'].XYZ_n, K_ab=HUNTERLAB_ILLUMINANTS[ 'CIE 1931 2 Degree Standard Observer']['D65'].K_ab): """ Converts from *Hunter L,a,b* colour scale to *CIE XYZ* tristimulus values. Parameters ---------- Lab : array_like *Hunter L,a,b* colour scale array. XYZ_n : array_like, optional Reference *illuminant* tristimulus values. K_ab : array_like, optional Reference *illuminant* chromaticity coefficients, if ``K_ab`` is set to *None* it will be computed using :func:`colour.XYZ_to_K_ab_HunterLab1966`. 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] | +------------+-----------------------+-----------------+ | ``XYZ_n`` | [0, 100] | [0, 1] | +------------+-----------------------+-----------------+ +------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+=================+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+-----------------+ References ---------- :cite:`HunterLab2008b` Examples -------- >>> Lab = np.array([34.92452577, 47.06189858, 14.38615107]) >>> D65 = HUNTERLAB_ILLUMINANTS[ ... 'CIE 1931 2 Degree Standard Observer']['D65'] >>> Hunter_Lab_to_XYZ(Lab, D65.XYZ_n, D65.K_ab) array([ 20.654008, 12.197225, 5.136952]) """ L, a, b = tsplit(to_domain_100(Lab)) X_n, Y_n, Z_n = tsplit(to_domain_100(XYZ_n)) K_a, K_b = (tsplit(XYZ_to_K_ab_HunterLab1966(XYZ_n)) if K_ab is None else tsplit(K_ab)) L_100 = L / 100 L_100_2 = L_100 ** 2 Y = L_100_2 * Y_n X = ((a / K_a) * L_100 + L_100_2) * X_n Z = -((b / K_b) * L_100 - L_100_2) * Z_n XYZ = tstack([X, Y, Z]) return from_range_100(XYZ)
def opponent_colour_dimensions_inverse(P_n, h): """ Returns opponent colour dimensions from given points :math:`P_n` and hue :math:`h` in degrees for inverse *CIECAM02* implementation. Parameters ---------- P_n : array_like Points :math:`P_n`. h : numeric or array_like Hue :math:`h` in degrees. Returns ------- ndarray Opponent colour dimensions. Notes ----- - This definition implements negative values handling as per :cite:`Luo2013`. Examples -------- >>> P_n = np.array([30162.89081534, 24.23720547, 1.05000000]) >>> h = -140.95156734 >>> opponent_colour_dimensions_inverse(P_n, h) # doctest: +ELLIPSIS array([-0.0006241..., -0.0005062...]) """ P_1, P_2, P_3 = tsplit(P_n) hr = np.radians(h) sin_hr = np.sin(hr) cos_hr = np.cos(hr) P_4 = P_1 / sin_hr P_5 = P_1 / cos_hr n = P_2 * (2 + P_3) * (460 / 1403) a = zeros(hr.shape) b = zeros(hr.shape) b = np.where( np.isfinite(P_1) * np.abs(sin_hr) >= np.abs(cos_hr), (n / (P_4 + (2 + P_3) * (220 / 1403) * (cos_hr / sin_hr) - (27 / 1403) + P_3 * (6300 / 1403))), b, ) a = np.where( np.isfinite(P_1) * np.abs(sin_hr) >= np.abs(cos_hr), b * (cos_hr / sin_hr), a, ) a = np.where( np.isfinite(P_1) * np.abs(sin_hr) < np.abs(cos_hr), (n / (P_5 + (2 + P_3) * (220 / 1403) - ( (27 / 1403) - P_3 * (6300 / 1403)) * (sin_hr / cos_hr))), a, ) b = np.where( np.isfinite(P_1) * np.abs(sin_hr) < np.abs(cos_hr), a * (sin_hr / cos_hr), b, ) ab = tstack([a, b]) return ab
def ellipses_MacAdam1942(method='CIE 1931'): """ Returns *MacAdam (1942) Ellipses (Observer PGN)* coefficients according to given method. Parameters ---------- method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, Computation method. Returns ------- tuple Current figure and axes. Examples -------- >>> ellipses_MacAdam1942()[0] # doctest: +SKIP array([ 1.60000000e-01, 5.70000000e-02, 5.00000023e-03, 1.56666660e-02, -2.77000015e+01]) """ method = method.upper() if method == 'CIE 1931': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy elif method == 'CIE 1960 UCS': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy_to_UCS_uv(xy) elif method == 'CIE 1976 UCS': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy_to_Luv_uv(xy) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format( method)) x, y, _a, _b, _theta, a, b, theta = tsplit(MACADAM_1942_ELLIPSES_DATA) ellipses_coefficients = [] for i in range(len(theta)): xy = point_at_angle_on_ellipse( np.linspace(0, 360, 36), [x[i], y[i], a[i] / 60, b[i] / 60, theta[i]], ) ij = xy_to_ij(xy) ellipses_coefficients.append( ellipse_coefficients_canonical_form(ellipse_fitting(ij))) return ellipses_coefficients
def uv_to_Luv( uv: ArrayLike, illuminant: ArrayLike = CCS_ILLUMINANTS[ "CIE 1931 2 Degree Standard Observer"]["D65"], Y: Floating = 1, ) -> NDArray: """ Return the *CIE L\\*u\\*v\\** colourspace array from given :math:`uv^p` chromaticity coordinates by extending the array last dimension with given :math:`L` *Lightness*. Parameters ---------- uv :math:`uv^p` chromaticity coordinates. illuminant Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Y Optional :math:`Y` *luminance* value used to construct the intermediate *CIE XYZ* colourspace array, the default :math:`Y` *luminance* value is 1. Returns ------- :class:`numpy.ndarray` *CIE L\\*u\\*v\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Luv`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``u`` : [-100, 100] | ``u`` : [-1, 1] | | | | | | | ``v`` : [-100, 100] | ``v`` : [-1, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004j` Examples -------- >>> import numpy as np >>> uv = np.array([0.37720213, 0.50120264]) >>> uv_to_Luv(uv) # doctest: +ELLIPSIS array([ 100. , 233.1837603..., 42.7474385...]) """ u, v = tsplit(uv) Y = as_float_scalar(to_domain_1(Y)) X = 9 * u / (4 * v) Z = (-5 * Y * v - 3 * u / 4 + 3) / v XYZ = tstack([X, full(u.shape, Y), Z]) return XYZ_to_Luv(from_range_1(XYZ), illuminant)