def normalised_achromatic_lightness_correlate(B_r, B_rw): """ Returns the *normalised achromatic Lightness* correlate :math:`L_n^\\star`. Parameters ---------- B_r : numeric or array_like *Brightness* correlate :math:`B_r`. B_rw : numeric or array_like Ideal white *brightness* correlate :math:`B_{rw}`. Returns ------- numeric or ndarray *Normalised achromatic Lightness* correlate :math:`L_n^\\star`. Examples -------- >>> B_r = 62.626673467230766 >>> B_rw = 125.24353925846037 >>> normalised_achromatic_lightness_correlate(B_r, B_rw) ... # doctest: +ELLIPSIS 50.0039154... """ B_r = as_float_array(B_r) B_rw = as_float_array(B_rw) return 100 * (B_r / B_rw)
def scaling_coefficient(x, y): """ Returns the scaling coefficient :math:`e(R)` or :math:`e(G)`. Parameters ---------- x: numeric or array_like Cone response. y: numeric or array_like Intermediate value. Returns ------- numeric or ndarray Scaling coefficient :math:`e(R)` or :math:`e(G)`. Examples -------- >>> x = 20.000520600000002 >>> y = 1.000042192 >>> scaling_coefficient(x, y) array(1.0) """ x = as_float_array(x) y = as_float_array(y) return np.where(x >= (20 * y), 1.758, 1)
def brightness_correlate(bRGB_o, bL_or, Q): """ Returns the *brightness* correlate :math:`B_r`. Parameters ---------- bRGB_o: ndarray Chromatic adaptation exponential factors :math:`\\beta_1(R_o)`, :math:`\\beta_1(G_o)` and :math:`\\beta_2(B_o)`. bL_or: numeric or array_like Normalising chromatic adaptation exponential factor :math:`\\beta_1(B_or)`. Q : numeric or array_like Achromatic response :math:`Q`. Returns ------- numeric or ndarray *Brightness* correlate :math:`B_r`. Examples -------- >>> bRGB_o = np.array([4.61062223, 4.61058926, 4.65206986]) >>> bL_or = 3.681021495604089 >>> Q = -0.000117024294955 >>> brightness_correlate(bRGB_o, bL_or, Q) # doctest: +ELLIPSIS 62.6266734... """ bR_o, bG_o, _bB_o = tsplit(bRGB_o) bL_or = as_float_array(bL_or) Q = as_float_array(Q) B_r = (50 / bL_or) * ((2 / 3) * bR_o + (1 / 3) * bG_o) + Q return B_r
def luminance_to_retinal_illuminance(XYZ, Y_c): """ Converts from luminance in :math:`cd/m^2` to retinal illuminance in trolands. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. Y_c : numeric or array_like Absolute adapting field luminance in :math:`cd/m^2`. Returns ------- ndarray Converted *CIE XYZ* tristimulus values in trolands. Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> Y_0 = 318.31 >>> luminance_to_retinal_illuminance(XYZ, Y_0) # doctest: +ELLIPSIS array([ 479.4445924..., 499.3174313..., 534.5631673...]) """ XYZ = as_float_array(XYZ) Y_c = as_float_array(Y_c) return 18 * spow(Y_c[..., np.newaxis] * XYZ / 100, 0.8)
def CCT_to_uv_Robertson1968(CCT_D_uv): """ Returns the *CIE UCS* colourspace *uv* chromaticity coordinates from given correlated colour temperature :math:`T_{cp}` and :math:`\\Delta_{uv}` using *Roberston (1968)* method. Parameters ---------- CCT_D_uv : ndarray Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. Returns ------- ndarray *CIE UCS* colourspace *uv* chromaticity coordinates. References ---------- :cite:`AdobeSystems2013a`, :cite:`Wyszecki2000y` Examples -------- >>> CCT_D_uv = np.array([6500.0081378199056, 0.008333331244225]) >>> CCT_to_uv_Robertson1968(CCT_D_uv) # doctest: +ELLIPSIS array([ 0.1937413..., 0.3152210...]) """ CCT_D_uv = as_float_array(CCT_D_uv) uv = [_CCT_to_uv_Robertson1968(a) for a in np.reshape(CCT_D_uv, (-1, 2))] return as_float_array(uv).reshape(CCT_D_uv.shape)
def chroma_components(L_star_P, S_RG, S_YB): """ Returns the *chroma* components :math:`C_{RG}` and :math:`C_{YB}`. Parameters ---------- L_star_P : numeric or array_like *Achromatic Lightness* correlate :math:`L_p^\\star`. S_RG : numeric or array_like *Saturation* component :math:`S_{RG}`. S_YB : numeric or array_like *Saturation* component :math:`S_{YB}`. Returns ------- ndarray *Chroma* components :math:`C_{RG}` and :math:`C_{YB}`. Examples -------- >>> L_star_P = 49.99988297570504 >>> S_RG = -0.002885271638197 >>> S_YB = -0.013039632941332 >>> chroma_components(L_star_P, S_RG, S_YB) # doctest: +ELLIPSIS array([-0.00288527, -0.01303961]) """ L_star_P = as_float_array(L_star_P) S_RG = as_float_array(S_RG) S_YB = as_float_array(S_YB) C_RG = spow(L_star_P / 50, 0.7) * S_RG C_YB = spow(L_star_P / 50, 0.7) * S_YB return tstack([C_RG, C_YB])
def colourfulness_components(C_RG, C_YB, B_rw): """ Returns the *colourfulness* components :math:`M_{RG}` and :math:`M_{YB}`. Parameters ---------- C_RG : numeric or array_like *Chroma* component :math:`C_{RG}`. C_YB : numeric or array_like *Chroma* component :math:`C_{YB}`. B_rw : numeric or array_like Ideal white *brightness* correlate :math:`B_{rw}`. Returns ------- numeric or ndarray *Colourfulness* components :math:`M_{RG}` and :math:`M_{YB}`. Examples -------- >>> C_RG = -0.002885271638197 >>> C_YB = -0.013039632941332 >>> B_rw = 125.24353925846037 >>> colourfulness_components(C_RG, C_YB, B_rw) # doctest: +ELLIPSIS (-0.0036136..., -0.0163312...) """ C_RG = as_float_array(C_RG) C_YB = as_float_array(C_YB) B_rw = as_float_array(B_rw) M_RG = C_RG * B_rw / 100 M_YB = C_YB * B_rw / 100 return M_RG, M_YB
def saturation_correlate(M, Q): """ Returns the *saturation* correlate :math:`s`. Parameters ---------- M : numeric or array_like *Colourfulness* correlate :math:`M`. Q : numeric or array_like *Brightness* correlate :math:`C`. Returns ------- numeric or ndarray *Saturation* correlate :math:`s`. Examples -------- >>> M = 0.108842175669 >>> Q = 195.371325966 >>> saturation_correlate(M, Q) # doctest: +ELLIPSIS 2.3603053... """ M = as_float_array(M) Q = as_float_array(Q) s = 100 * spow(M / Q, 0.5) return s
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(20.0, 100.0, 318.31) ... # doctest: +ELLIPSIS array([ 0.2..., 1.1675444..., 1.000304 , 1.000304 , 1.9272136...]) """ Y_b = as_float_array(Y_b) Y_w = as_float_array(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 saturation_correlate(Ch_L, L_L): """ Returns the correlate of *saturation* :math:`S_L`. Parameters ---------- Ch_L : numeric or array_like Correlate of *chroma* :math:`Ch_L`. L_L : numeric or array_like Correlate of *Lightness* :math:`L_L`. Returns ------- numeric or ndarray Correlate of *saturation* :math:`S_L`. Examples -------- >>> Ch_L = 0.008650662051714 >>> L_L = 37.368047493928195 >>> saturation_correlate(Ch_L, L_L) # doctest: +ELLIPSIS 0.0002314... """ Ch_L = as_float_array(Ch_L) L_L = as_float_array(L_L) S_L = Ch_L / L_L return S_L
def colourfulness_correlate(C, F_L): """ Returns the *colourfulness* correlate :math:`M`. Parameters ---------- C : numeric or array_like *Chroma* correlate :math:`C`. F_L : numeric or array_like *Luminance* level adaptation factor :math:`F_L`. Returns ------- numeric or ndarray *Colourfulness* correlate :math:`M`. Examples -------- >>> C = 0.104707757171 >>> F_L = 1.16754446415 >>> colourfulness_correlate(C, F_L) # doctest: +ELLIPSIS 0.1088421... """ C = as_float_array(C) F_L = as_float_array(F_L) M = C * spow(F_L, 0.25) return M
def chroma_correlate(a, b): """ Returns the correlate of *chroma* :math:`Ch_L`. Parameters ---------- a : numeric or array_like Opponent colour dimension :math:`a`. b : numeric or array_like Opponent colour dimension :math:`b`. Returns ------- numeric or ndarray Correlate of *chroma* :math:`Ch_L`. Examples -------- >>> a = -4.49864756e-03 >>> b = -5.26046353e-03 >>> chroma_correlate(a, b) # doctest: +ELLIPSIS 0.0086506... """ a = as_float_array(a) b = as_float_array(b) c = spow(a ** 2 + b ** 2, 0.5) Ch_L = 25 * np.log(1 + 0.05 * c) return Ch_L
def hue_angle(a, b): """ Returns the *hue* angle :math:`h_L` in degrees. Parameters ---------- a : numeric or array_like Opponent colour dimension :math:`a`. b : numeric or array_like Opponent colour dimension :math:`b`. Returns ------- numeric or ndarray *Hue* angle :math:`h_L` in degrees. Examples -------- >>> hue_angle(-4.49864756e-03, -5.26046353e-03) # doctest: +ELLIPSIS 229.4635727... """ a = as_float_array(a) b = as_float_array(b) h_L = np.degrees(np.arctan2(b, a)) % 360 return h_L
def f(x, F_S): """ Defines the nonlinear response function of the *:math:`LLAB(l:c)`* colour appearance model used to model the nonlinear behaviour of various visual responses. Parameters ---------- x : numeric or array_like or array_like Visual response variable :math:`x`. F_S : numeric or array_like Surround induction factor :math:`F_S`. Returns ------- numeric or array_like Modeled visual response variable :math:`x`. Examples -------- >>> x = np.array([0.23350512, 0.23351103, 0.23355179]) >>> f(0.200009186234000, 3) # doctest: +ELLIPSIS array(0.5848125...) """ x = as_float_array(x) F_S = as_float_array(F_S) x_m = np.where( x > 0.008856, spow(x, 1 / F_S), ((spow(0.008856, 1 / F_S) - (16 / 116)) / 0.008856) * x + (16 / 116), ) return x_m
def hue_angle(p, t): """ Returns the *hue* angle :math:`h` in degrees. Parameters ---------- p : numeric or array_like Protanopic response :math:`p`. t : numeric or array_like Tritanopic response :math:`t`. Returns ------- numeric or ndarray *Hue* angle :math:`h` in degrees. Examples -------- >>> p = -8.002142682085493e-05 >>> t = -0.000017703650669 >>> hue_angle(p, t) # doctest: +ELLIPSIS 257.5250300... """ p = as_float_array(p) t = as_float_array(t) h_L = np.degrees(np.arctan2(p, t)) % 360 return h_L
def degree_of_adaptation(F, L_A): """ Returns the degree of adaptation :math:`D` from given surround maximum degree of adaptation :math:`F` and Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`. Parameters ---------- F : numeric or array_like Surround maximum degree of adaptation :math:`F`. L_A : numeric or array_like Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`. Returns ------- numeric or ndarray Degree of adaptation :math:`D`. Examples -------- >>> degree_of_adaptation(1.0, 318.31) # doctest: +ELLIPSIS 0.9944687... """ F = as_float_array(F) L_A = as_float_array(L_A) D = F * (1 - (1 / 3.6) * np.exp((-L_A - 42) / 92)) return D
def saturation_correlate(S_RG, S_YB): """ Returns the correlate of *saturation* :math:`S`. Parameters ---------- S_RG : numeric or array_like *Saturation* component :math:`S_{RG}`. S_YB : numeric or array_like *Saturation* component :math:`S_{YB}`. Returns ------- numeric or ndarray Correlate of *saturation* :math:`S`. Examples -------- >>> S_RG = -0.002885271638197 >>> S_YB = -0.013039632941332 >>> saturation_correlate(S_RG, S_YB) # doctest: +ELLIPSIS 0.0133550... """ S_RG = as_float_array(S_RG) S_YB = as_float_array(S_YB) S = np.hypot(S_RG, S_YB) return S
def post_adaptation_non_linear_response_compression_reverse(RGB, F_L): """ Returns given *CMCCAT2000* transform sharpened *RGB* array without post adaptation non linear response compression. Parameters ---------- RGB : array_like *CMCCAT2000* transform sharpened *RGB* array. F_L : array_like *Luminance* level adaptation factor :math:`F_L`. Returns ------- ndarray Uncompressed *CMCCAT2000* transform sharpened *RGB* array. Examples -------- >>> RGB = np.array([7.94632020, 7.94711528, 7.94899595]) >>> F_L = 1.16754446415 >>> post_adaptation_non_linear_response_compression_reverse(RGB, F_L) ... # doctest: +ELLIPSIS array([ 19.9969397..., 20.0018612..., 20.0135052...]) """ RGB = as_float_array(RGB) F_L = as_float_array(F_L) RGB_p = ((np.sign(RGB - 0.1) * (100 / F_L[..., np.newaxis]) * spow( (27.13 * np.absolute(RGB - 0.1)) / (400 - np.absolute(RGB - 0.1)), 1 / 0.42))) return RGB_p
def chroma_correlate(L_star_P, S): """ Returns the correlate of *chroma* :math:`C`. Parameters ---------- L_star_P : numeric or array_like *Achromatic Lightness* correlate :math:`L_p^\\star`. S : numeric or array_like Correlate of *saturation* :math:`S`. Returns ------- numeric or ndarray Correlate of *chroma* :math:`C`. Examples -------- >>> L_star_P = 49.99988297570504 >>> S = 0.013355029751778 >>> chroma_correlate(L_star_P, S) # doctest: +ELLIPSIS 0.0133550... """ L_star_P = as_float_array(L_star_P) S = as_float_array(S) C = spow(L_star_P / 50, 0.7) * S return C
def hue_angle(a, b): """ Returns the *hue* angle :math:`h` in degrees. Parameters ---------- a : numeric or array_like Opponent colour dimension :math:`a`. b : numeric or array_like Opponent colour dimension :math:`b`. Returns ------- numeric or ndarray *Hue* angle :math:`h` in degrees. Examples -------- >>> a = -0.000624112068243 >>> b = -0.000506270106773 >>> hue_angle(a, b) # doctest: +ELLIPSIS 219.0484326... """ a = as_float_array(a) b = as_float_array(b) h = np.degrees(np.arctan2(b, a)) % 360 return h
def colourfulness_correlate(C, B_rw): """ Returns the correlate of *colourfulness* :math:`M`. Parameters ---------- C : numeric or array_like Correlate of *chroma* :math:`C`. B_rw : numeric or array_like Ideal white *brightness* correlate :math:`B_{rw}`. Returns ------- numeric or ndarray Correlate of *colourfulness* :math:`M`. Examples -------- >>> C = 0.013355007871689 >>> B_rw = 125.24353925846037 >>> colourfulness_correlate(C, B_rw) # doctest: +ELLIPSIS 0.0167262... """ C = as_float_array(C) B_rw = as_float_array(B_rw) M = C * B_rw / 100 return M
def illuminance_to_luminance(E, Y_f): """ Converts given *illuminance* :math:`E` value in lux to *luminance* in :math:`cd/m^2`. Parameters ---------- E : numeric or array_like *Illuminance* :math:`E` in lux. Y_f : numeric or array_like *Luminance* factor :math:`Y_f` in :math:`cd/m^2`. Returns ------- numeric or ndarray *Luminance* :math:`Y` in :math:`cd/m^2`. Examples -------- >>> illuminance_to_luminance(5000.0, 20.0) # doctest: +ELLIPSIS 318.3098861... """ E = as_float_array(E) Y_f = as_float_array(Y_f) return Y_f * E / (100 * np.pi)
def uv_to_CCT_Robertson1968(uv): """ Returns the correlated colour temperature :math:`T_{cp}` and :math:`\\Delta_{uv}` from given *CIE UCS* colourspace *uv* chromaticity coordinates using *Roberston (1968)* method. Parameters ---------- uv : array_like *CIE UCS* colourspace *uv* chromaticity coordinates. Returns ------- ndarray Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. References ---------- :cite:`AdobeSystems2013`, :cite:`Wyszecki2000y` Examples -------- >>> uv = np.array([0.193741375998230, 0.315221043940594]) >>> uv_to_CCT_Robertson1968(uv) # doctest: +ELLIPSIS array([ 6.5000162...e+03, 8.3333289...e-03]) """ uv = as_float_array(uv) CCT_D_uv = [_uv_to_CCT_Robertson1968(a) for a in np.reshape(uv, (-1, 2))] return as_float_array(CCT_D_uv).reshape(uv.shape)
def metric_mse(a, b): """ Computes the mean squared error (MSE) or mean squared deviation (MSD) between given *array_like* :math:`a` and :math:`b` variables. Parameters ---------- a : array_like :math:`a` variable. b : array_like :math:`b` variable. Returns ------- float Mean squared error (MSE). References ---------- :cite:`Wikipedia2003c` Examples -------- >>> a = np.array([0.48222001, 0.31654775, 0.22070353]) >>> b = a * 0.9 >>> metric_mse(a, b) # doctest: +ELLIPSIS 0.0012714... """ return np.mean((as_float_array(a) - as_float_array(b)) ** 2)
def optical_MTF_Barten1999(u, sigma=0.01): """ Returns the optical modulation transfer function (MTF) :math:`M_{opt}` of the eye using *Barten (1999)* method. Parameters ---------- u : numeric or array_like Spatial frequency :math:`u`, the cycles per degree. sigma : numeric or array_like, optional Standard deviation :math:`\\sigma` of the line-spread function resulting from the convolution of the different elements of the convolution process. Returns ------- numeric or array_like Optical modulation transfer function (MTF) :math:`M_{opt}` of the eye. References ---------- :cite:`Barten1999`, :cite:`Barten2003`, :cite:`Cowan2004`, :cite:`InternationalTelecommunicationUnion2015`, Examples -------- >>> optical_MTF_Barten1999(4, 0.01) # doctest: +ELLIPSIS 0.9689107... """ u = as_float_array(u) sigma = as_float_array(sigma) return as_float(np.exp(-2 * np.pi ** 2 * sigma ** 2 * u ** 2))
def pupil_diameter_Barten1999(L, X_0=60, Y_0=None): """ Returns the pupil diameter for given luminance and object or stimulus angular size using *Barten (1999)* method. Parameters ---------- L : numeric or array_like Average luminance :math:`L` in :math:`cd/m^2`. X_0 : numeric or array_like, optional Angular size of the object :math:`X_0` in degrees in the x direction. Y_0 : numeric or array_like, optional Angular size of the object :math:`X_0` in degrees in the y direction. References ---------- :cite:`Barten1999`, :cite:`Barten2003`, :cite:`Cowan2004`, :cite:`InternationalTelecommunicationUnion2015`, Examples -------- >>> pupil_diameter_Barten1999(100, 60, 60) # doctest: +ELLIPSIS 2.0777571... """ L = as_float_array(L) X_0 = as_float_array(X_0) Y_0 = X_0 if Y_0 is None else as_float_array(Y_0) return as_float(5 - 3 * np.tanh(0.4 * np.log(L * X_0 * Y_0 / 40 ** 2)))
def intermediate_lightness_function_CIE1976(Y, Y_n=100): """ Returns the intermediate value :math:`f(Y/Yn)` in the *Lightness* :math:`L^*` computation for given *luminance* :math:`Y` using given reference white *luminance* :math:`Y_n` as per *CIE 1976* recommendation. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. Y_n : numeric or array_like, optional White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like Intermediate value :math:`f(Y/Yn)`. Notes ----- +-------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``Y`` | [0, 100] | [0, 100] | +-------------+-----------------------+---------------+ +-------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``f_Y_Y_n`` | [0, 1] | [0, 1] | +-------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> intermediate_lightness_function_CIE1976(12.19722535) ... # doctest: +ELLIPSIS 0.4959299... >>> intermediate_lightness_function_CIE1976(12.19722535, 95) ... # doctest: +ELLIPSIS 0.5044821... """ Y = as_float_array(Y) Y_n = as_float_array(Y_n) Y_Y_n = Y / Y_n f_Y_Y_n = as_float( np.where( Y_Y_n > (24 / 116) ** 3, spow(Y_Y_n, 1 / 3), (841 / 108) * Y_Y_n + 16 / 116, )) return f_Y_Y_n
def air_refraction_index_Bodhaine1999( wavelength, CO2_concentration=STANDARD_CO2_CONCENTRATION): """ Returns the air refraction index :math:`n_s` from given wavelength :math:`\\lambda` in micrometers (:math:`\\mu m`) using *Bodhaine, Wood, Dutton and Slusser (1999)* method. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\\lambda` in micrometers (:math:`\\mu m`). CO2_concentration : numeric or array_like :math:`CO_2` concentration in parts per million (ppm). Returns ------- numeric or ndarray Air refraction index :math:`n_s`. Examples -------- >>> air_refraction_index_Bodhaine1999(0.555) # doctest: +ELLIPSIS 1.0002777... """ wl = as_float_array(wavelength) CO2_c = as_float_array(CO2_concentration) n = ((1 + 0.54 * ((CO2_c * 1e-6) - 300e-6)) * (air_refraction_index_Peck1972(wl) - 1) + 1) return n
def test_as_float_array(self): """ Tests :func:`colour.utilities.array.as_float_array` definition. """ np.testing.assert_equal(as_float_array([1, 2, 3]), np.array([1, 2, 3])) self.assertEqual(as_float_array([1, 2, 3]).dtype, DEFAULT_FLOAT_DTYPE)
def intermediate_luminance_function_CIE1976(f_Y_Y_n, Y_n=100): """ Returns the *luminance* :math:`Y` in the *luminance* :math:`Y` computation for given intermediate value :math:`f(Y/Yn)` using given reference white *luminance* :math:`Y_n` as per *CIE 1976* recommendation. Parameters ---------- f_Y_Y_n : numeric or array_like Intermediate value :math:`f(Y/Yn)`. Y_n : numeric or array_like White reference *luminance* :math:`Y_n`. Returns ------- numeric or array_like *luminance* :math:`Y`. Notes ----- +-------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``f_Y_Y_n`` | [0, 1] | [0, 1] | +-------------+-----------------------+---------------+ +-------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +=============+=======================+===============+ | ``Y`` | [0, 100] | [0, 100] | +-------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-482004m`, :cite:`Wyszecki2000bd` Examples -------- >>> intermediate_luminance_function_CIE1976(0.495929964178047) ... # doctest: +ELLIPSIS 12.1972253... >>> intermediate_luminance_function_CIE1976(0.504482161449319, 95) ... # doctest: +ELLIPSIS 12.1972253... """ f_Y_Y_n = as_float_array(f_Y_Y_n) Y_n = as_float_array(Y_n) Y = as_float( np.where( f_Y_Y_n > 24 / 116, Y_n * f_Y_Y_n ** 3, Y_n * (f_Y_Y_n - 16 / 116) * (108 / 841), )) return Y
def chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2, n=1): """ Adapts given stimulus *CIE XYZ_1* tristimulus values from test viewing conditions to reference viewing conditions using *CIE 1994* chromatic adaptation model. Parameters ---------- XYZ_1 : array_like *CIE XYZ* tristimulus values of test sample / stimulus. xy_o1 : array_like Chromaticity coordinates :math:`x_{o1}` and :math:`y_{o1}` of test illuminant and background. xy_o2 : array_like Chromaticity coordinates :math:`x_{o2}` and :math:`y_{o2}` of reference illuminant and background. Y_o : numeric Luminance factor :math:`Y_o` of achromatic background as percentage normalised to domain [18, 100] in **'Reference'** domain-range scale. E_o1 : numeric Test illuminance :math:`E_{o1}` in :math:`cd/m^2`. E_o2 : numeric Reference illuminance :math:`E_{o2}` in :math:`cd/m^2`. n : numeric, optional Noise component in fundamental primary system. Returns ------- ndarray Adapted *CIE XYZ_2* tristimulus values of test stimulus. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_1`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``Y_o`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_2`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-321994b` Examples -------- >>> XYZ_1 = np.array([28.00, 21.26, 5.27]) >>> xy_o1 = np.array([0.4476, 0.4074]) >>> xy_o2 = np.array([0.3127, 0.3290]) >>> Y_o = 20 >>> E_o1 = 1000 >>> E_o2 = 1000 >>> chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2) ... # doctest: +ELLIPSIS array([ 24.0337952..., 21.1562121..., 17.6430119...]) """ XYZ_1 = to_domain_100(XYZ_1) Y_o = to_domain_100(Y_o) E_o1 = as_float_array(E_o1) E_o2 = as_float_array(E_o2) if np.any(Y_o < 18) or np.any(Y_o > 100): usage_warning(('"Y_o" luminance factor must be in [18, 100] domain, ' 'unpredictable results may occur!')) RGB_1 = XYZ_to_RGB_CIE1994(XYZ_1) xez_1 = intermediate_values(xy_o1) xez_2 = intermediate_values(xy_o2) RGB_o1 = effective_adapting_responses(xez_1, Y_o, E_o1) RGB_o2 = effective_adapting_responses(xez_2, Y_o, E_o2) bRGB_o1 = exponential_factors(RGB_o1) bRGB_o2 = exponential_factors(RGB_o2) K = K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, n) RGB_2 = corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K, n) XYZ_2 = RGB_to_XYZ_CIE1994(RGB_2) return from_range_100(XYZ_2)
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 corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K, n=1): """ Computes the corresponding colour cone responses of given test sample cone responses :math:`RGB_1`. Parameters ---------- RGB_1: array_like Test sample cone responses :math:`RGB_1`. 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 normalised to domain [18, 100] in **'Reference'** domain-range scale. K : numeric or array_like Coefficient :math:`K`. n : numeric or array_like, optional Noise component in fundamental primary system. Returns ------- 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.0 >>> 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) def RGB_c(x_1, x_2, y_1, y_2, z): """ Computes 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) G_2 = RGB_c(eta_1, eta_2, bG_o1, bG_o2, G_1) B_2 = RGB_c(zeta_1, zeta_2, bB_o1, bB_o2, B_1) RGB_2 = tstack([R_2, G_2, B_2]) return RGB_2
def logarithmic_function_quasilog(x, style='linToLog', base=2, log_side_slope=1, lin_side_slope=1, log_side_offset=0, lin_side_offset=0): """ Defines the quasilog logarithmic function. Parameters ---------- x : numeric Linear/non-linear data to undergo encoding/decoding. style : unicode, optional **{'linToLog', 'logToLin'}**, Defines the behaviour for the logarithmic function to operate: - *linToLog*: Applies a logarithm to convert linear data to logarithmic data. - *logToLin*: Applies an anti-logarithm to convert logarithmic data to linear data. base : numeric, optional Logarithmic base used for the conversion. log_side_slope : numeric, optional Slope (or gain) applied to the log side of the logarithmic function. The default value is 1. lin_side_slope : numeric, optional Slope of the linear side of the logarithmic function. The default value is 1. log_side_offset : numeric, optional Offset applied to the log side of the logarithmic function. The default value is 0. lin_side_offset : numeric, optional Offset applied to the linear side of the logarithmic function. The default value is 0. Returns ------- numeric or ndarray Encoded/Decoded data. Raises ------ ValueError If the *style* is not defined. Examples -------- >>> logarithmic_function_quasilog( # doctest: +ELLIPSIS ... 0.18, 'linToLog') -2.4739311... >>> logarithmic_function_quasilog( # doctest: +ELLIPSIS ... -2.473931188332412, 'logToLin') 0.18000000... """ x = as_float_array(x) style = style.lower() if style == 'lintolog': return as_float( (log_side_slope * (np.log(np.maximum(lin_side_slope * x + lin_side_offset, FLT_MIN)) / np.log(base)) + log_side_offset)) elif style == 'logtolin': return as_float(((base**( (x - log_side_offset) / log_side_slope) - lin_side_offset) / lin_side_slope)) else: raise ValueError( 'Undefined style used: "{0}", must be one of the following: ' '"{1}".'.format(style, ', '.join(['linToLog', 'logToLin'])))
def logarithmic_function_camera( x: FloatingOrArrayLike, style: Union[Literal["cameraLinToLog", "cameraLogToLin"], str] = "cameraLinToLog", base: Integer = 2, log_side_slope: Floating = 1, lin_side_slope: Floating = 1, log_side_offset: Floating = 0, lin_side_offset: Floating = 0, lin_side_break: Floating = 0.005, linear_slope: Optional[Floating] = None, ) -> FloatingOrNDArray: """ Define the camera logarithmic function. Parameters ---------- x Linear/non-linear data to undergo encoding/decoding. style Defines the behaviour for the logarithmic function to operate: - *cameraLinToLog*: Applies a piece-wise function with logarithmic and linear segments on linear values, converting them to non-linear values. - *cameraLogToLin*: Applies a piece-wise function with logarithmic and linear segments on non-linear values, converting them to linear values. base Logarithmic base used for the conversion. log_side_slope Slope (or gain) applied to the log side of the logarithmic segment. The default value is 1. lin_side_slope Slope of the linear side of the logarithmic segment. The default value is 1. log_side_offset Offset applied to the log side of the logarithmic segment. The default value is 0. lin_side_offset Offset applied to the linear side of the logarithmic segment. The default value is 0. lin_side_break Break-point, defined in linear space, at which the piece-wise function transitions between the logarithmic and linear segments. linear_slope Slope of the linear portion of the curve. The default value is *None*. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Encoded/Decoded data. Examples -------- >>> logarithmic_function_camera( # doctest: +ELLIPSIS ... 0.18, 'cameraLinToLog') -2.4739311... >>> logarithmic_function_camera( # doctest: +ELLIPSIS ... -2.4739311883324122, 'cameraLogToLin') 0.1800000... """ x = as_float_array(x) style = validate_method( style, ["cameraLinToLog", "cameraLogToLin"], '"{0}" style is invalid, it must be one of {1}!', ) log_side_break = ( log_side_slope * (np.log(lin_side_slope * lin_side_break + lin_side_offset) / np.log(base)) + log_side_offset) linear_slope = cast( Floating, optional( linear_slope, (log_side_slope * (lin_side_slope / ((lin_side_slope * lin_side_break + lin_side_offset) * np.log(base)))), ), ) linear_offset = log_side_break - linear_slope * lin_side_break if style == "cameralintolog": return as_float( np.where( x <= lin_side_break, linear_slope * x + linear_offset, logarithmic_function_quasilog( x, "linToLog", base, log_side_slope, lin_side_slope, log_side_offset, lin_side_offset, ), )) else: # style == 'cameralogtolin' return as_float( np.where( x <= log_side_break, (x - linear_offset) / linear_slope, logarithmic_function_quasilog( x, "logToLin", base, log_side_slope, lin_side_slope, log_side_offset, lin_side_offset, ), ))
def degrees_of_adaptation(LMS, Y_n, v=1 / 3, discount_illuminant=False): """ Computes the degrees of adaptation :math:`p_L`, :math:`p_M` and :math:`p_S`. Parameters ---------- LMS : array_like Cone responses. Y_n : numeric or array_like Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`. v : numeric or array_like, optional Exponent :math:`v`. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- ndarray Degrees of adaptation :math:`p_L`, :math:`p_M` and :math:`p_S`. Examples -------- >>> LMS = np.array([20.00052060, 19.99978300, 19.99883160]) >>> Y_n = 31.83 >>> degrees_of_adaptation(LMS, Y_n) # doctest: +ELLIPSIS array([ 0.9799324..., 0.9960035..., 1.0233041...]) >>> degrees_of_adaptation(LMS, Y_n, 1 / 3, True) array([ 1., 1., 1.]) """ LMS = as_float_array(LMS) if discount_illuminant: return ones(LMS.shape) Y_n = as_float_array(Y_n) v = as_float_array(v) L, M, S = tsplit(LMS) # E illuminant. LMS_E = vector_dot(CAT_VON_KRIES, ones(LMS.shape)) L_E, M_E, S_E = tsplit(LMS_E) Ye_n = spow(Y_n, v) def m_E(x, y): """ Computes the :math:`m_E` term. """ return (3 * (x / y)) / (L / L_E + M / M_E + S / S_E) def P_c(x): """ Computes the :math:`P_L`, :math:`P_M` or :math:`P_S` terms. """ return (1 + Ye_n + x) / (1 + Ye_n + 1 / x) p_L = P_c(m_E(L, L_E)) p_M = P_c(m_E(M, M_E)) p_S = P_c(m_E(S, S_E)) p_LMS = tstack([p_L, p_M, p_S]) return p_LMS
def RGB_to_YCbCr(RGB, K=YCBCR_WEIGHTS['ITU-R BT.709'], in_bits=10, in_legal=False, in_int=False, out_bits=8, out_legal=True, out_int=False, **kwargs): """ Converts an array of *R'G'B'* values to the corresponding *Y'CbCr* colour encoding values array. Parameters ---------- RGB : array_like Input *R'G'B'* array of floats or integer values. K : array_like, optional Luma weighting coefficients of red and blue. See :attr:`colour.YCBCR_WEIGHTS` for presets. Default is *(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Default is *10*. in_legal : bool, optional Whether to treat the input values as legal range. Default is *False*. in_int : bool, optional Whether to treat the input values as ``in_bits`` integer code values. Default is *False*. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Ignored if ``out_legal`` and ``out_int`` are both *False*. Default is *8*. out_legal : bool, optional Whether to return legal range values. Default is *True*. out_int : bool, optional Whether to return values as ``out_bits`` integer code values. Default is *False*. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as *in_range = (RGB_min, RGB_max)*. If ``in_range`` is undefined, *RGB_min* and *RGB_max* will be computed using :func:`colour.CV_range` definition. out_range : array_like, optional Array overriding the computed range such as *out_range = (Y_min, Y_max, C_min, C_max)`. If ``out_range`` is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using :func:`colour.models.rgb.ycbcr.YCbCr_ranges` definition. Returns ------- ndarray *Y'CbCr* colour encoding array of integer or float values. Warning ------- For *Recommendation ITU-R BT.2020*, :func:`colour.RGB_to_YCbCr` definition is only applicable to the non-constant luminance implementation. :func:`colour.RGB_to_YcCbcCrc` definition should be used for the constant luminance case as per :cite:`InternationalTelecommunicationUnion2015h`. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``YCbCr`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ - \\* This definition has input and output integer switches, thus the domain-range scale information is only given for the floating point mode. - The default arguments, ``**{'in_bits': 10, 'in_legal': False, 'in_int': False, 'out_bits': 8, 'out_legal': True, 'out_int': False}`` transform a float *R'G'B'* input array normalised to domain [0, 1] (``in_bits`` is ignored) to a float *Y'CbCr* output array where *Y'* is normalised to range [16 / 255, 235 / 255] and *Cb* and *Cr* are normalised to range [16 / 255, 240./255]. The float values are calculated based on an [0, 255] integer range, but no 8-bit quantisation or clamping are performed. References ---------- :cite:`InternationalTelecommunicationUnion2011e`, :cite:`InternationalTelecommunicationUnion2015i`, :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`, :cite:`Wikipedia2004d` Examples -------- >>> RGB = np.array([1.0, 1.0, 1.0]) >>> RGB_to_YCbCr(RGB) # doctest: +ELLIPSIS array([ 0.9215686..., 0.5019607..., 0.5019607...]) Matching float output of The Foundry Nuke's Colorspace node set to YCbCr: >>> RGB_to_YCbCr(RGB, ... out_range=(16 / 255, 235 / 255, 15.5 / 255, 239.5 / 255)) ... # doctest: +ELLIPSIS array([ 0.9215686..., 0.5 , 0.5 ]) Matching float output of The Foundry Nuke's Colorspace node set to YPbPr: >>> RGB_to_YCbCr(RGB, out_legal=False, out_int=False) ... # doctest: +ELLIPSIS array([ 1., 0., 0.]) Creating integer code values as per standard 10-bit SDI: >>> RGB_to_YCbCr(RGB, out_legal=True, out_bits=10, out_int=True) array([940, 512, 512]) For JFIF JPEG conversion as per ITU-T T.871 :cite:`InternationalTelecommunicationUnion2011e`: >>> RGB = np.array([102, 0, 51]) >>> RGB_to_YCbCr(RGB, K=YCBCR_WEIGHTS['ITU-R BT.601'], in_range=(0, 255), ... out_range=(0, 255, 0, 256), out_int=True) array([ 36, 136, 175]) Note the use of 256 for the max *Cb / Cr* value, which is required so that the *Cb* and *Cr* output is centered about 128. Using 255 centres it about 127.5, meaning that there is no integer code value to represent achromatic colours. This does however create the possibility of output integer codes with value of 256, which cannot be stored in 8-bit integer representation. Recommendation ITU-T T.871 specifies these should be clamped to 255. These JFIF JPEG ranges are also obtained as follows: >>> RGB_to_YCbCr(RGB, K=YCBCR_WEIGHTS['ITU-R BT.601'], in_bits=8, ... in_int=True, out_legal=False, out_int=True) array([ 36, 136, 175]) """ if in_int: RGB = as_float_array(RGB) else: RGB = to_domain_1(RGB) Kr, Kb = K RGB_min, RGB_max = kwargs.get('in_range', CV_range(in_bits, in_legal, in_int)) Y_min, Y_max, C_min, C_max = kwargs.get( 'out_range', YCbCr_ranges(out_bits, out_legal, out_int)) RGB_float = RGB.astype(DEFAULT_FLOAT_DTYPE) - RGB_min RGB_float *= 1 / (RGB_max - RGB_min) R, G, B = tsplit(RGB_float) Y = Kr * R + (1 - Kr - Kb) * G + Kb * B Cb = 0.5 * (B - Y) / (1 - Kb) Cr = 0.5 * (R - Y) / (1 - Kr) Y *= Y_max - Y_min Y += Y_min Cb *= C_max - C_min Cr *= C_max - C_min Cb += (C_max + C_min) / 2 Cr += (C_max + C_min) / 2 YCbCr = tstack([Y, Cb, Cr]) YCbCr = np.round(YCbCr).astype( DEFAULT_INT_DTYPE) if out_int else from_range_1(YCbCr) return YCbCr
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}`. References ---------- :cite:`Ohno2014a` Examples -------- >>> from pprint import pprint >>> 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]) >>> uv_to_CCT_Ohno2013(uv, cmfs) # doctest: +ELLIPSIS array([ 6.50747...e+03, 3.22334...e-03]) """ uv = as_float_array(uv) CCT_D_uv = [ _uv_to_CCT_Ohno2013(a, cmfs, start, end, count, iterations) for a in np.reshape(uv, (-1, 2)) ] return np.reshape(as_float_array(CCT_D_uv), uv.shape)
def YcCbcCrc_to_RGB(YcCbcCrc, in_bits=10, in_legal=True, in_int=False, is_12_bits_system=False, **kwargs): """ Converts an array of *Yc'Cbc'Crc'* colour encoding values to the corresponding *RGB* array of linear values. Parameters ---------- YcCbcCrc : array_like Input *Yc'Cbc'Crc'* colour encoding array of linear float values. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Default is *10*. in_legal : bool, optional Whether to treat the input values as legal range. Default is *False*. in_int : bool, optional Whether to treat the input values as ``in_bits`` integer code values. Default is *False*. is_12_bits_system : bool, optional *Recommendation ITU-R BT.2020* EOTF (EOCF) adopts different parameters for 10 and 12 bit systems. Default is *False*. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as *in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range`` is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using :func:`colour.models.rgb.ycbcr.YCbCr_ranges` definition. Returns ------- ndarray *RGB* array of linear float values. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``YcCbcCrc`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ - \\* This definition has input and output integer switches, thus the domain-range scale information is only given for the floating point mode. Warning ------- This definition is specifically for usage with *Recommendation ITU-R BT.2020* when adopting the constant luminance implementation. References ---------- :cite:`InternationalTelecommunicationUnion2015h`, :cite:`Wikipedia2004d` Examples -------- >>> YcCbcCrc = np.array([1689, 2048, 2048]) >>> YcCbcCrc_to_RGB(YcCbcCrc, in_legal=True, in_bits=12, in_int=True, ... is_12_bits_system=True) ... # doctest: +ELLIPSIS array([ 0.1800903..., 0.1800903..., 0.1800903...]) """ if in_int: YcCbcCrc = as_float_array(YcCbcCrc) else: YcCbcCrc = to_domain_1(YcCbcCrc) Yc, Cbc, Crc = tsplit(YcCbcCrc.astype(DEFAULT_FLOAT_DTYPE)) Y_min, Y_max, C_min, C_max = kwargs.get( 'in_range', YCbCr_ranges(in_bits, in_legal, in_int)) Yc -= Y_min Cbc -= (C_max + C_min) / 2 Crc -= (C_max + C_min) / 2 Yc *= 1 / (Y_max - Y_min) Cbc *= 1 / (C_max - C_min) Crc *= 1 / (C_max - C_min) B = np.where(Cbc <= 0, Cbc * 1.9404 + Yc, Cbc * 1.5816 + Yc) R = np.where(Crc <= 0, Crc * 1.7184 + Yc, Crc * 0.9936 + Yc) with domain_range_scale('ignore'): Yc = eotf_BT2020(Yc, is_12_bits_system=is_12_bits_system) B = eotf_BT2020(B, is_12_bits_system=is_12_bits_system) R = eotf_BT2020(R, is_12_bits_system=is_12_bits_system) G = (Yc - 0.0593 * B - 0.2627 * R) / 0.6780 RGB = tstack([R, G, B]) return from_range_1(RGB)
def absolute_luminance_calibration_Lagarde2016(RGB, measured_illuminance, colourspace=RGB_COLOURSPACES[ 'sRGB']): """ Performs absolute *Luminance* calibration of given *RGB* panoramic image using *Lagarde (2016)* method. Parameters ---------- RGB : array_like *RGB* panoramic image to calibrate. measured_illuminance : numeric Measured illuminance :math:`E_v`. colourspace : `colour.RGB_Colourspace`, optional *RGB* colourspace used for internal *Luminance* computation. Returns ------- ndarray Absolute *Luminance* calibrated *RGB* panoramic image. Examples -------- >>> RGB = np.ones((4, 8, 3)) >>> absolute_luminance_calibration_Lagarde2016( # doctest: +ELLIPSIS ... RGB, 500) array([[[ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...]], <BLANKLINE> [[ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...]], <BLANKLINE> [[ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...]], <BLANKLINE> [[ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...], [ 233.9912506..., 233.9912506..., 233.9912506...]]]) """ RGB = as_float_array(RGB) E_v = upper_hemisphere_illuminance_Lagarde2016(RGB, colourspace) return RGB / E_v * measured_illuminance
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 contrast_sensitivity_function_Barten1999(u, sigma=sigma_Barten1999( 0.5 / 60, 0.08 / 60, 2.1), k=3.0, T=0.1, X_0=60, Y_0=None, X_max=12, Y_max=None, N_max=15, n=0.03, p=1.2274 * 10 ** 6, E=retinal_illuminance_Barten1999( 20, 2.1), phi_0=3 * 10 ** -8, u_0=7): """ Returns the contrast sensitivity :math:`S` of the human eye according to the contrast sensitivity function (CSF) described by *Barten (1999)*. Contrast sensitivity is defined as the inverse of the modulation threshold of a sinusoidal luminance pattern. The modulation threshold of this pattern is generally defined by 50% probability of detection. The contrast sensitivity function or CSF gives the contrast sensitivity as a function of spatial frequency. In the CSF, the spatial frequency is expressed in angular units with respect to the eye. It reaches a maximum between 1 and 10 cycles per degree with a fall off at higher and lower spatial frequencies. Parameters ---------- u : numeric Spatial frequency :math:`u`, the cycles per degree. sigma : numeric or array_like, optional Standard deviation :math:`\\sigma` of the line-spread function resulting from the convolution of the different elements of the convolution process. k : numeric or array_like, optional Signal-to-noise (SNR) ratio :math:`k`. T : numeric or array_like, optional Integration time :math:`T` in seconds of the eye. X_0 : numeric or array_like, optional Angular size :math:`X_0` in degrees of the object in the x direction. Y_0 : numeric or array_like, optional Angular size :math:`Y_0` in degrees of the object in the y direction. X_max : numeric or array_like, optional Maximum angular size :math:`X_{max}` in degrees of the integration area in the x direction. Y_max : numeric or array_like, optional Maximum angular size :math:`Y_{max}` in degrees of the integration area in the y direction. N_max : numeric or array_like, optional Maximum number of cycles :math:`N_{max}` over which the eye can integrate the information. n : numeric or array_like, optional Quantum efficiency of the eye :math:`n`. p : numeric or array_like, optional Photon conversion factor :math:`p` in :math:`photons\\div seconds\\div degrees^2\\div Trolands` that depends on the light source. E : numeric or array_like, optional Retinal illuminance :math:`E` in Trolands. phi_0 : numeric or array_like, optional Spectral density :math:`\\phi_0` in :math:`seconds degrees^2` of the neural noise. u_0 : numeric or array_like, optional Spatial frequency :math:`u_0` in :math:`cycles\\div degrees` above which the lateral inhibition ceases. Returns ------- ndarray Contrast sensitivity :math:`S`. Warnings -------- This definition expects :math:`\\sigma_{0}` and :math:`C_{ab}` used in the computation of :math:`\\sigma` to be given in degrees and :math:`degrees\\div mm` respectively. However, in the literature, the values for :math:`\\sigma_{0}` and :math:`C_{ab}` are usually given in :math:`arc min` and :math:`arc min\\div mm` respectively, thus they need to be divided by 60. Notes ----- - The formula holds for bilateral viewing and for equal dimensions of the object in x and y direction. For monocular vision, the contrast sensitivity is a factor :math:`\\sqrt{2}` smaller. - *Barten (1999)* CSF default values for the :math:`k`, :math:`\\sigma_{0}`, :math:`C_{ab}`, :math:`T`, :math:`X_{max}`, :math:`N_{max}`, :math:`n`, :math:`\\phi_{0}` and :math:`u_0` constants are valid for a standard observer with good vision and with an age between 20 and 30 years. - The other constants have been filled using reference data from *Figure 31* in :cite:`InternationalTelecommunicationUnion2015` but must be adapted to the current use case. - The product of :math:`u`, the cycles per degree, and :math:`X_0`, the number of degrees, gives the number of cycles :math:`P_c` in a pattern. Therefore, :math:`X_0` can be made a variable dependent on :math:`u` such as :math:`X_0 = P_c / u`. References ---------- :cite:`Barten1999`, :cite:`Barten2003`, :cite:`Cowan2004`, :cite:`InternationalTelecommunicationUnion2015`, Examples -------- >>> contrast_sensitivity_function_Barten1999(4) # doctest: +ELLIPSIS 360.8691122... Reproducing *Figure 31* in \ :cite:`InternationalTelecommunicationUnion2015` illustrating the minimum detectable contrast according to *Barten (1999)* model with the assumed conditions for UHDTV applications. The minimum detectable contrast :math:`MDC` is then defined as follows:: :math:`MDC = 1 / CSF * 2 * (1 / 1.27)` where :math:`2` is used for the conversion from modulation to contrast and :math:`1 / 1.27` is used for the conversion from sinusoidal to rectangular waves. >>> from scipy.optimize import fmin >>> settings_BT2246 = { ... 'k': 3.0, ... 'T': 0.1, ... 'X_max': 12, ... 'N_max': 15, ... 'n': 0.03, ... 'p': 1.2274 * 10 ** 6, ... 'phi_0': 3 * 10 ** -8, ... 'u_0': 7, ... } >>> >>> def maximise_spatial_frequency(L): ... maximised_spatial_frequency = [] ... for L_v in L: ... X_0 = 60 ... d = pupil_diameter_Barten1999(L_v, X_0) ... sigma = sigma_Barten1999(0.5 / 60, 0.08 / 60, d) ... E = retinal_illuminance_Barten1999(L_v, d, True) ... maximised_spatial_frequency.append( ... fmin(lambda x: ( ... -contrast_sensitivity_function_Barten1999( ... u=x, ... sigma=sigma, ... X_0=X_0, ... E=E, ... **settings_BT2246) ... ), 0, disp=False)[0]) ... return as_float(np.array(maximised_spatial_frequency)) >>> >>> L = np.logspace(np.log10(0.01), np.log10(100), 10) >>> X_0 = Y_0 = 60 >>> d = pupil_diameter_Barten1999(L, X_0, Y_0) >>> sigma = sigma_Barten1999(0.5 / 60, 0.08 / 60, d) >>> E = retinal_illuminance_Barten1999(L, d) >>> u = maximise_spatial_frequency(L) >>> (1 / contrast_sensitivity_function_Barten1999( ... u=u, sigma=sigma, E=E, X_0=X_0, Y_0=Y_0, **settings_BT2246) ... * 2 * (1/ 1.27)) ... # doctest: +ELLIPSIS array([ 0.0207396..., 0.0134885..., 0.0096063..., 0.0077299..., \ 0.0068983..., 0.0065057..., 0.0062712..., 0.0061198..., 0.0060365..., \ 0.0059984...]) """ u = as_float_array(u) k = as_float_array(k) T = as_float_array(T) X_0 = as_float_array(X_0) Y_0 = X_0 if Y_0 is None else as_float_array(Y_0) X_max = as_float_array(X_max) Y_max = X_max if Y_max is None else as_float_array(Y_max) N_max = as_float_array(N_max) n = as_float_array(n) p = as_float_array(p) E = as_float_array(E) phi_0 = as_float_array(phi_0) u_0 = as_float_array(u_0) M_opt = optical_MTF_Barten1999(u, sigma) M_as = 1 / (maximum_angular_size_Barten1999(u, X_0, X_max, N_max) * maximum_angular_size_Barten1999(u, Y_0, Y_max, N_max)) S = (M_opt / k) / np.sqrt(2 / T * M_as * (1 / (n * p * E) + phi_0 / (1 - np.exp(-(u / u_0) ** 2)))) return as_float(S)
def logarithmic_function_basic(x, style='log2', base=2): """ Defines the basic logarithmic function. Parameters ---------- x : numeric The data to undergo basic logarithmic conversion. style : unicode, optional **{'log10', 'antiLog10', 'log2', 'antiLog2', 'logB', 'antiLogB'}**, Defines the behaviour for the logarithmic function to operate: - *log10*: Applies a base 10 logarithm to the passed value. - *antiLog10*: Applies a base 10 anti-logarithm to the passed value. - *log2*: Applies a base 2 logarithm to the passed value. - *antiLog2*: Applies a base 2 anti-logarithm to the passed value. - *logB*: Applies an arbitrary base logarithm to the passed value. - *antiLogB*: Applies an arbitrary base anti-logarithm to the passed value. base : numeric, optional Logarithmic base used for the conversion. Returns ------- numeric or ndarray Logarithmically converted data. Raises ------ ValueError If the *style* is not defined. Examples -------- The basic logarithmic function *styles* operate as follows: >>> logarithmic_function_basic(0.18) # doctest: +ELLIPSIS -2.4739311... >>> logarithmic_function_basic(0.18, 'log10') # doctest: +ELLIPSIS -0.7447274... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... 0.18, 'logB', 3) -1.5608767... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -2.473931188332412, 'antiLog2') 0.18000000... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -0.7447274948966939, 'antiLog10') 0.18000000... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -1.5608767950073117, 'antiLogB', 3) 0.18000000... """ x = as_float_array(x) style = style.lower() if style == 'log10': return as_float(np.where(x >= FLT_MIN, np.log10(x), np.log10(FLT_MIN))) elif style == 'antilog10': return as_float(10**x) elif style == 'log2': return as_float(np.where(x >= FLT_MIN, np.log2(x), np.log2(FLT_MIN))) elif style == 'antilog2': return as_float(2**x) elif style == 'logb': return as_float(np.log(x) / np.log(base)) elif style == 'antilogb': return as_float(base**x) else: raise ValueError( 'Undefined style used: "{0}", must be one of the following: ' '"{1}".'.format( style, ', '.join([ 'log10', 'antiLog10', 'log2', 'antiLog2', 'logB', 'antiLogB' ])))
def logarithmic_function_camera(x, style='cameraLinToLog', base=2, log_side_slope=1, lin_side_slope=1, log_side_offset=0, lin_side_offset=0, lin_side_break=0.005, linear_slope=None): """ Defines the camera logarithmic function. Parameters ---------- x : numeric Linear/non-linear data to undergo encoding/decoding. style : unicode, optional **{'cameraLinToLog', 'cameraLogToLin'}**, Defines the behaviour for the logarithmic function to operate: - *cameraLinToLog*: Applies a piece-wise function with logarithmic and linear segments on linear values, converting them to non-linear values. - *cameraLogToLin*: Applies a piece-wise function with logarithmic and linear segments on non-linear values, converting them to linear values. base : numeric, optional Logarithmic base used for the conversion. log_side_slope : numeric, optional Slope (or gain) applied to the log side of the logarithmic segment. The default value is 1. lin_side_slope : numeric, optional Slope of the linear side of the logarithmic segment. The default value is 1. log_side_offset : numeric, optional Offset applied to the log side of the logarithmic segment. The default value is 0. lin_side_offset : numeric, optional Offset applied to the linear side of the logarithmic segment. The default value is 0. lin_side_break : numeric Break-point, defined in linear space, at which the piece-wise function transitions between the logarithmic and linear segments. linear_slope : numeric, optional Slope of the linear portion of the curve. The default value is *None*. Returns ------- numeric or ndarray Encoded/Decoded data. Raises ------ ValueError If the *style* is not defined. Examples -------- >>> logarithmic_function_camera( # doctest: +ELLIPSIS ... 0.18, 'cameraLinToLog') -2.4739311... >>> logarithmic_function_camera( # doctest: +ELLIPSIS ... -2.4739311883324122, 'cameraLogToLin') 0.1800000... """ x = as_float_array(x) log_side_break = ( log_side_slope * (np.log(lin_side_slope * lin_side_break + lin_side_offset) / np.log(base)) + log_side_offset) if linear_slope is None: linear_slope = (log_side_slope * (lin_side_slope / ((lin_side_slope * lin_side_break + lin_side_offset) * np.log(base)))) linear_offset = log_side_break - linear_slope * lin_side_break style = style.lower() if style == 'cameralintolog': return as_float( np.where( x <= lin_side_break, linear_slope * x + linear_offset, logarithmic_function_quasilog(x, 'linToLog', base, log_side_slope, lin_side_slope, log_side_offset, lin_side_offset))) elif style == 'cameralogtolin': return as_float( np.where( x <= log_side_break, (x - linear_offset) / linear_slope, logarithmic_function_quasilog(x, 'logToLin', base, log_side_slope, lin_side_slope, log_side_offset, lin_side_offset), )) else: raise ValueError( 'Undefined style used: "{0}", must be one of the following: ' '"{1}".'.format(style, ', '.join(['cameraLinToLog', 'cameraLogToLin'])))
def dominant_wavelength( xy: ArrayLike, xy_n: ArrayLike, cmfs: Optional[MultiSpectralDistributions] = None, inverse: bool = False, ) -> Tuple[NDArray, NDArray, NDArray]: """ Return the *dominant wavelength* :math:`\\lambda_d` for given colour stimulus :math:`xy` and the related :math:`xy_wl` first and :math:`xy_{cw}` second intersection coordinates with the spectral locus. In the eventuality where the :math:`xy_wl` first intersection coordinates are on the line of purples, the *complementary wavelength* will be computed in lieu. The *complementary wavelength* is indicated by a negative sign and the :math:`xy_{cw}` second intersection coordinates which are set by default to the same value than :math:`xy_wl` first intersection coordinates will be set to the *complementary dominant wavelength* intersection coordinates with the spectral locus. Parameters ---------- xy Colour stimulus *CIE xy* chromaticity coordinates. xy_n Achromatic stimulus *CIE xy* chromaticity coordinates. cmfs Standard observer colour matching functions, default to the *CIE 1931 2 Degree Standard Observer*. inverse Inverse the computation direction to retrieve the *complementary wavelength*. Returns ------- :class:`tuple` *Dominant wavelength*, first intersection point *CIE xy* chromaticity coordinates, second intersection point *CIE xy* chromaticity coordinates. References ---------- :cite:`CIETC1-482004o`, :cite:`Erdogana` Examples -------- *Dominant wavelength* computation: >>> from colour.colorimetry import MSDS_CMFS >>> from pprint import pprint >>> cmfs = MSDS_CMFS['CIE 1931 2 Degree Standard Observer'] >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_n = np.array([0.31270000, 0.32900000]) >>> pprint(dominant_wavelength(xy, xy_n, cmfs)) # doctest: +ELLIPSIS (array(616...), array([ 0.6835474..., 0.3162840...]), array([ 0.6835474..., 0.3162840...])) *Complementary dominant wavelength* is returned if the first intersection is located on the line of purples: >>> xy = np.array([0.37605506, 0.24452225]) >>> pprint(dominant_wavelength(xy, xy_n)) # doctest: +ELLIPSIS (array(-509.0), array([ 0.4572314..., 0.1362814...]), array([ 0.0104096..., 0.7320745...])) """ cmfs, _illuminant = handle_spectral_arguments(cmfs) xy = as_float_array(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = XYZ_to_xy(cmfs.values) i_wl, xy_wl = closest_spectral_locus_wavelength(xy, xy_n, xy_s, inverse) xy_cwl = xy_wl wl = cmfs.wavelengths[i_wl] xy_e = (extend_line_segment(xy, xy_n) if inverse else extend_line_segment( xy_n, xy)) intersect = intersect_line_segments(np.concatenate((xy_n, xy_e), -1), np.hstack([xy_s[0], xy_s[-1]])).intersect intersect = np.reshape(intersect, wl.shape) i_wl_r, xy_cwl_r = closest_spectral_locus_wavelength( xy, xy_n, xy_s, not inverse) wl_r = -cmfs.wavelengths[i_wl_r] wl = np.where(intersect, wl_r, wl) xy_cwl = np.where(intersect[..., np.newaxis], xy_cwl_r, xy_cwl) return wl, np.squeeze(xy_wl), np.squeeze(xy_cwl)
def chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method='Von Kries', **kwargs): """ Adapts given stimulus from test viewing conditions to reference viewing conditions. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of stimulus to adapt. XYZ_w : array_like Test viewing condition *CIE XYZ* tristimulus values of the whitepoint. XYZ_wr : array_like Reference viewing condition *CIE XYZ* tristimulus values of the whitepoint. method : unicode, optional **{'Von Kries', 'CIE 1994', 'CMCCAT2000', 'Fairchild 1990'}**, Computation method. Other Parameters ---------------- E_o1 : numeric {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, Test illuminance :math:`E_{o1}` in :math:`cd/m^2`. E_o2 : numeric {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, Reference illuminance :math:`E_{o2}` in :math:`cd/m^2`. L_A1 : numeric or array_like {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`. L_A2 : numeric or array_like {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, Luminance of reference adapting field :math:`L_{A2}` in :math:`cd/m^2`. Y_n : numeric or array_like {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`}, Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`. Y_o : numeric {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, Luminance factor :math:`Y_o` of achromatic background normalised to domain [0.18, 1] in **'Reference'** domain-range scale. direction : unicode, optional {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, **{'Forward', 'Inverse'}**, Chromatic adaptation direction. discount_illuminant : bool, optional {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`}, Truth value indicating if the illuminant should be discounted. n : numeric, optional {:func:`colour.adaptation.chromatic_adaptation_CIE1994`}, Noise component in fundamental primary system. surround : InductionFactors_CMCCAT2000, optional {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`}, Surround viewing conditions induction factors. transform : unicode, optional {:func:`colour.adaptation.chromatic_adaptation_VonKries`}, **{'CAT02', 'XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp', 'Fairchild', 'CMCCAT97', 'CMCCAT2000', 'CAT02 Brill 2008', 'Bianco 2010', 'Bianco PC 2010'}**, Chromatic adaptation transform. Returns ------- ndarray *CIE XYZ_c* tristimulus values of the stimulus corresponding colour. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_wr`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ | ``Y_o`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_c`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-321994b`, :cite:`Fairchild1991a`, :cite:`Fairchild2013s`, :cite:`Fairchild2013t`, :cite:`Li2002a`, :cite:`Westland2012k` Examples -------- *Von Kries* chromatic adaptation: >>> import numpy as np >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775]) >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460]) >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr) ... # doctest: +ELLIPSIS array([ 0.2163881..., 0.1257 , 0.0384749...]) *CIE 1994* chromatic adaptation, requires extra *kwargs*: >>> XYZ = np.array([0.2800, 0.2126, 0.0527]) >>> XYZ_w = np.array([1.09867452, 1.00000000, 0.35591556]) >>> XYZ_wr = np.array([0.95045593, 1.00000000, 1.08905775]) >>> Y_o = 0.20 >>> E_o = 1000 >>> chromatic_adaptation( ... XYZ, XYZ_w, XYZ_wr, method='CIE 1994', Y_o=Y_o, E_o1=E_o, E_o2=E_o) ... # doctest: +ELLIPSIS array([ 0.2403379..., 0.2115621..., 0.1764301...]) *CMCCAT2000* chromatic adaptation, requires extra *kwargs*: >>> XYZ = np.array([0.2248, 0.2274, 0.0854]) >>> XYZ_w = np.array([1.1115, 1.0000, 0.3520]) >>> XYZ_wr = np.array([0.9481, 1.0000, 1.0730]) >>> L_A = 200 >>> chromatic_adaptation( ... XYZ, XYZ_w, XYZ_wr, method='CMCCAT2000', L_A1=L_A, L_A2=L_A) ... # doctest: +ELLIPSIS array([ 0.1952698..., 0.2306834..., 0.2497175...]) *Fairchild (1990)* chromatic adaptation, requires extra *kwargs*: >>> XYZ = np.array([0.1953, 0.2307, 0.2497]) >>> Y_n = 200 >>> chromatic_adaptation( ... XYZ, XYZ_w, XYZ_wr, method='Fairchild 1990', Y_n=Y_n) ... # doctest: +ELLIPSIS array([ 0.2332526..., 0.2332455..., 0.7611593...]) """ function = CHROMATIC_ADAPTATION_METHODS[method] domain_range_reference = get_domain_range_scale() == 'reference' domain_100 = (chromatic_adaptation_CIE1994, chromatic_adaptation_CMCCAT2000, chromatic_adaptation_Fairchild1990) if function in domain_100 and domain_range_reference: XYZ = as_float_array(XYZ) * 100 XYZ_w = as_float_array(XYZ_w) * 100 XYZ_wr = as_float_array(XYZ_wr) * 100 if kwargs.get('Y_o'): kwargs['Y_o'] = kwargs['Y_o'] * 100 kwargs.update({'XYZ_w': XYZ_w, 'XYZ_wr': XYZ_wr}) if function is chromatic_adaptation_CIE1994: from colour import XYZ_to_xy kwargs.update({'xy_o1': XYZ_to_xy(XYZ_w), 'xy_o2': XYZ_to_xy(XYZ_wr)}) elif function is chromatic_adaptation_Fairchild1990: kwargs.update({'XYZ_n': XYZ_w, 'XYZ_r': XYZ_wr}) XYZ_c = function(XYZ, **filter_kwargs(function, **kwargs)) if function in domain_100 and domain_range_reference: XYZ_c /= 100 return XYZ_c
def closest_spectral_locus_wavelength( xy: ArrayLike, xy_n: ArrayLike, xy_s: ArrayLike, inverse: Boolean = False) -> Tuple[NDArray, NDArray]: """ Return the coordinates and closest spectral locus wavelength index to the point where the line defined by the given achromatic stimulus :math:`xy_n` to colour stimulus :math:`xy_n` *CIE xy* chromaticity coordinates intersects the spectral locus. Parameters ---------- xy Colour stimulus *CIE xy* chromaticity coordinates. xy_n Achromatic stimulus *CIE xy* chromaticity coordinates. xy_s Spectral locus *CIE xy* chromaticity coordinates. inverse The intersection will be computed using the colour stimulus :math:`xy` to achromatic stimulus :math:`xy_n` inverse direction. Returns ------- :class:`tuple` Closest wavelength index, intersection point *CIE xy* chromaticity coordinates. Raises ------ ValueError If no closest spectral locus wavelength index and coordinates found. Examples -------- >>> from colour.colorimetry import MSDS_CMFS >>> cmfs = MSDS_CMFS['CIE 1931 2 Degree Standard Observer'] >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_n = np.array([0.31270000, 0.32900000]) >>> xy_s = XYZ_to_xy(cmfs.values) >>> ix, intersect = closest_spectral_locus_wavelength(xy, xy_n, xy_s) >>> print(ix) # 256 >>> print(intersect) # doctest: +ELLIPSIS [ 0.6835474... 0.3162840...] """ xy = as_float_array(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = as_float_array(xy_s) xy_e = (extend_line_segment(xy, xy_n) if inverse else extend_line_segment( xy_n, xy)) # Closing horse-shoe shape to handle line of purples intersections. xy_s = np.vstack([xy_s, xy_s[0, :]]) xy_wl = intersect_line_segments( np.concatenate((xy_n, xy_e), -1), np.hstack([xy_s, np.roll(xy_s, 1, axis=0)]), ).xy # Extracting the first intersection per-wavelength. xy_wl = np.sort(xy_wl, 1)[:, 0, :] if not len(xy_wl): raise ValueError( f"No closest spectral locus wavelength index and coordinates " f'found for "{xy}" colour stimulus and "{xy_n}" achromatic ' f'stimulus "xy" chromaticity coordinates!') i_wl = np.argmin(scipy.spatial.distance.cdist(xy_wl, xy_s), axis=-1) i_wl = np.reshape(i_wl, xy.shape[0:-1]) xy_wl = np.reshape(xy_wl, xy.shape) return i_wl, xy_wl
def read_LUT_SonySPI1D(path): """ Reads given *Sony* *.spi1d* *LUT* file. Parameters ---------- path : unicode *LUT* path. Returns ------- LUT1D or LUT2D :class:`LUT1D` or :class:`LUT2D` class instance. Examples -------- Reading a 1D *Sony* *.spi1d* *LUT*: >>> import os >>> path = os.path.join( ... os.path.dirname(__file__), 'tests', 'resources', 'sony_spi1d', ... 'oetf_reverse_sRGB_1D.spi1d') >>> print(read_LUT_SonySPI1D(path)) LUT1D - oetf reverse sRGB 1D ---------------------------- <BLANKLINE> Dimensions : 1 Domain : [-0.1 1.5] Size : (16,) Comment 01 : Generated by "Colour 0.3.11". Comment 02 : "colour.models.oetf_reverse_sRGB". Reading a 2D *Sony* *.spi1d* *LUT*: >>> path = os.path.join( ... os.path.dirname(__file__), 'tests', 'resources', 'sony_spi1d', ... 'oetf_reverse_sRGB_2D.spi1d') >>> print(read_LUT_SonySPI1D(path)) LUT2D - oetf reverse sRGB 2D ---------------------------- <BLANKLINE> Dimensions : 2 Domain : [[-0.1 -0.1 -0.1] [ 1.5 1.5 1.5]] Size : (16, 3) Comment 01 : Generated by "Colour 0.3.11". Comment 02 : "colour.models.oetf_reverse_sRGB". """ title = path_to_title(path) domain_min, domain_max = np.array([0, 1]) dimensions = 1 table = [] comments = [] with open(path) as spi1d_file: lines = spi1d_file.readlines() for line in lines: line = line.strip() if len(line) == 0: continue if line.startswith('#'): comments.append(line[1:].strip()) continue tokens = line.split() if tokens[0] == 'Version': continue if tokens[0] == 'From': domain_min, domain_max = parse_array(tokens[1:]) elif tokens[0] == 'Length': continue elif tokens[0] == 'Components': component = DEFAULT_INT_DTYPE(tokens[1]) assert component in (1, 3), ( 'Only 1 or 3 components are supported!') dimensions = 1 if component == 1 else 2 elif tokens[0] in ('{', '}'): continue else: table.append(parse_array(tokens)) table = as_float_array(table) if dimensions == 1: return LUT1D(np.squeeze(table), title, np.array([domain_min, domain_max]), comments=comments) elif dimensions == 2: return LUT2D(table, title, np.array([[domain_min, domain_min, domain_min], [domain_max, domain_max, domain_max]]), comments=comments)
def chromatic_adaptation_Fairchild1990(XYZ_1, XYZ_n, XYZ_r, Y_n, discount_illuminant=False): """ Adapts given stimulus *CIE XYZ_1* tristimulus values from test viewing conditions to reference viewing conditions using *Fairchild (1990)* chromatic adaptation model. Parameters ---------- XYZ_1 : array_like *CIE XYZ_1* tristimulus values of test sample / stimulus. XYZ_n : array_like Test viewing condition *CIE XYZ_n* tristimulus values of whitepoint. XYZ_r : array_like Reference viewing condition *CIE XYZ_r* tristimulus values of whitepoint. Y_n : numeric or array_like Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- ndarray Adapted *CIE XYZ_2* tristimulus values of stimulus. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_1`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_n`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``XYZ_r`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_2`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild1991a`, :cite:`Fairchild2013s` Examples -------- >>> XYZ_1 = np.array([19.53, 23.07, 24.97]) >>> XYZ_n = np.array([111.15, 100.00, 35.20]) >>> XYZ_r = np.array([94.81, 100.00, 107.30]) >>> Y_n = 200 >>> chromatic_adaptation_Fairchild1990(XYZ_1, XYZ_n, XYZ_r, Y_n) ... # doctest: +ELLIPSIS array([ 23.3252634..., 23.3245581..., 76.1159375...]) """ XYZ_1 = to_domain_100(XYZ_1) XYZ_n = to_domain_100(XYZ_n) XYZ_r = to_domain_100(XYZ_r) Y_n = as_float_array(Y_n) LMS_1 = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_1) LMS_n = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_n) LMS_r = vector_dot(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ_r) p_LMS = degrees_of_adaptation( LMS_1, Y_n, discount_illuminant=discount_illuminant) a_LMS_1 = p_LMS / LMS_n a_LMS_2 = p_LMS / LMS_r A_1 = row_as_diagonal(a_LMS_1) A_2 = row_as_diagonal(a_LMS_2) LMSp_1 = vector_dot(A_1, LMS_1) c = 0.219 - 0.0784 * np.log10(Y_n) C = row_as_diagonal(tstack([c, c, c])) LMS_a = vector_dot(C, LMSp_1) LMSp_2 = vector_dot(np.linalg.inv(C), LMS_a) LMS_c = vector_dot(np.linalg.inv(A_2), LMSp_2) XYZ_c = vector_dot(MATRIX_RGB_TO_XYZ_FAIRCHILD1990, LMS_c) return from_range_100(XYZ_c)
def logarithmic_function_basic( x: FloatingOrArrayLike, style: Union[Literal["log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"], str, ] = "log2", base: Integer = 2, ) -> FloatingOrNDArray: """ Define the basic logarithmic function. Parameters ---------- x The data to undergo basic logarithmic conversion. style Defines the behaviour for the logarithmic function to operate: - *log10*: Applies a base 10 logarithm to the passed value. - *antiLog10*: Applies a base 10 anti-logarithm to the passed value. - *log2*: Applies a base 2 logarithm to the passed value. - *antiLog2*: Applies a base 2 anti-logarithm to the passed value. - *logB*: Applies an arbitrary base logarithm to the passed value. - *antiLogB*: Applies an arbitrary base anti-logarithm to the passed value. base Logarithmic base used for the conversion. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Logarithmically converted data. Examples -------- The basic logarithmic function *styles* operate as follows: >>> logarithmic_function_basic(0.18) # doctest: +ELLIPSIS -2.4739311... >>> logarithmic_function_basic(0.18, 'log10') # doctest: +ELLIPSIS -0.7447274... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... 0.18, 'logB', 3) -1.5608767... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -2.473931188332412, 'antiLog2') 0.18000000... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -0.7447274948966939, 'antiLog10') 0.18000000... >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -1.5608767950073117, 'antiLogB', 3) 0.18000000... """ x = as_float_array(x) style = validate_method( style, ["log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"], '"{0}" style is invalid, it must be one of {1}!', ) if style == "log10": return as_float(np.where(x >= FLT_MIN, np.log10(x), np.log10(FLT_MIN))) elif style == "antilog10": return as_float(10**x) elif style == "log2": return as_float(np.where(x >= FLT_MIN, np.log2(x), np.log2(FLT_MIN))) elif style == "antilog2": return as_float(2**x) elif style == "logb": return as_float(np.log(x) / np.log(base)) else: # style == 'antilogb' return as_float(base**x)
def YCbCr_to_RGB(YCbCr, K=YCBCR_WEIGHTS['ITU-R BT.709'], in_bits=8, in_legal=True, in_int=False, out_bits=10, out_legal=False, out_int=False, **kwargs): """ Converts an array of *Y'CbCr* colour encoding values to the corresponding *R'G'B'* values array. Parameters ---------- YCbCr : array_like Input *Y'CbCr* colour encoding array of integer or float values. K : array_like, optional Luma weighting coefficients of red and blue. See :attr:`colour.YCBCR_WEIGHTS` for presets. Default is *(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*. in_bits : int, optional Bit depth for integer input, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Default is *10*. in_legal : bool, optional Whether to treat the input values as legal range. Default is *False*. in_int : bool, optional Whether to treat the input values as ``in_bits`` integer code values. Default is *False*. out_bits : int, optional Bit depth for integer output, or used in the calculation of the denominator for legal range float values, i.e. 8-bit means the float value for legal white is *235 / 255*. Ignored if ``out_legal`` and ``out_int`` are both *False*. Default is *8*. out_legal : bool, optional Whether to return legal range values. Default is *True*. out_int : bool, optional Whether to return values as ``out_bits`` integer code values. Default is *False*. Other Parameters ---------------- in_range : array_like, optional Array overriding the computed range such as *in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range`` is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using :func:`colour.models.rgb.ycbcr.YCbCr_ranges` definition. out_range : array_like, optional Array overriding the computed range such as *out_range = (RGB_min, RGB_max)*. If ``out_range`` is undefined, *RGB_min* and *RGB_max* will be computed using :func:`colour.CV_range` definition. Returns ------- ndarray *R'G'B'* array of integer or float values. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``YCbCr`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ - \\* This definition has input and output integer switches, thus the domain-range scale information is only given for the floating point mode. Warning ------- For *Recommendation ITU-R BT.2020*, :func:`colour.YCbCr_to_RGB` definition is only applicable to the non-constant luminance implementation. :func:`colour.YcCbcCrc_to_RGB` definition should be used for the constant luminance case as per :cite:`InternationalTelecommunicationUnion2015h`. References ---------- :cite:`InternationalTelecommunicationUnion2011e`, :cite:`InternationalTelecommunicationUnion2015i`, :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`, :cite:`Wikipedia2004d` Examples -------- >>> YCbCr = np.array([502, 512, 512]) >>> YCbCr_to_RGB(YCbCr, in_bits=10, in_legal=True, in_int=True) array([ 0.5, 0.5, 0.5]) """ if in_int: YCbCr = as_float_array(YCbCr) else: YCbCr = to_domain_1(YCbCr) Y, Cb, Cr = tsplit(YCbCr.astype(DEFAULT_FLOAT_DTYPE)) Kr, Kb = K Y_min, Y_max, C_min, C_max = kwargs.get( 'in_range', YCbCr_ranges(in_bits, in_legal, in_int)) RGB_min, RGB_max = kwargs.get('out_range', CV_range(out_bits, out_legal, out_int)) Y -= Y_min Cb -= (C_max + C_min) / 2 Cr -= (C_max + C_min) / 2 Y *= 1 / (Y_max - Y_min) Cb *= 1 / (C_max - C_min) Cr *= 1 / (C_max - C_min) R = Y + (2 - 2 * Kr) * Cr B = Y + (2 - 2 * Kb) * Cb G = (Y - Kr * R - Kb * B) / (1 - Kr - Kb) RGB = tstack([R, G, B]) RGB *= RGB_max - RGB_min RGB += RGB_min RGB = np.round(RGB).astype(DEFAULT_INT_DTYPE) if out_int else from_range_1( RGB) 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 oetf_ARIBSTDB67( E: FloatingOrArrayLike, r: FloatingOrArrayLike = 0.5, constants: Structure = CONSTANTS_ARIBSTDB67, ) -> FloatingOrNDArray: """ Define *ARIB STD-B67 (Hybrid Log-Gamma)* opto-electrical transfer function (OETF). Parameters ---------- E Voltage normalised by the reference white level and proportional to the implicit light intensity that would be detected with a reference camera color channel R, G, B. r Video level corresponding to reference white level. constants *ARIB STD-B67 (Hybrid Log-Gamma)* constants. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Resulting non-linear signal :math:`E'`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E_p`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - This definition uses the *mirror* negative number handling mode of :func:`colour.models.gamma_function` definition to the sign of negative numbers. References ---------- :cite:`AssociationofRadioIndustriesandBusinesses2015a` Examples -------- >>> oetf_ARIBSTDB67(0.18) # doctest: +ELLIPSIS 0.2121320... """ E = to_domain_1(E) r = as_float_array(r) a = constants.a b = constants.b c = constants.c E_p = np.where(E <= 1, r * gamma_function(E, 0.5, "mirror"), a * np.log(E - b) + c) return as_float(from_range_1(E_p))
def lightness(Y, method='CIE 1976', **kwargs): """ Returns the *Lightness* :math:`L` using given method. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. method : unicode, optional **{'CIE 1976', 'Glasser 1958', 'Wyszecki 1963', 'Fairchild 2010', 'Fairchild 2011'}**, Computation method. Other Parameters ---------------- Y_n : numeric or array_like, optional {:func:`colour.colorimetry.lightness_CIE1976`}, White reference *luminance* :math:`Y_n`. epsilon : numeric or array_like, optional {:func:`colour.colorimetry.lightness_Fairchild2010`, :func:`colour.colorimetry.lightness_Fairchild2011`}, :math:`\\epsilon` exponent. Returns ------- numeric or array_like *Lightness* :math:`L`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``L`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2010`, :cite:`Fairchild2011`, :cite:`Glasser1958a`, :cite:`Lindbloom2003d`, :cite:`Wikipedia2007c`, :cite:`Wyszecki1963b`, :cite:`Wyszecki2000bd` Examples -------- >>> lightness(12.19722535) # doctest: +ELLIPSIS 41.5278758... >>> lightness(12.19722535, Y_n=100) # doctest: +ELLIPSIS 41.5278758... >>> lightness(12.19722535, Y_n=95) # doctest: +ELLIPSIS 42.5199307... >>> lightness(12.19722535, method='Glasser 1958') # doctest: +ELLIPSIS 39.8351264... >>> lightness(12.19722535, method='Wyszecki 1963') # doctest: +ELLIPSIS 40.5475745... >>> lightness(12.19722535, epsilon=0.710, method='Fairchild 2011') ... # doctest: +ELLIPSIS 29.8295108... """ Y = as_float_array(Y) function = LIGHTNESS_METHODS[method] domain_range_reference = get_domain_range_scale() == 'reference' domain_1 = (lightness_Fairchild2010, lightness_Fairchild2011) if function in domain_1 and domain_range_reference: Y = Y / 100 return function(Y, **filter_kwargs(function, **kwargs))
def exponent_hdr_CIELab(Y_s, Y_abs, method='Fairchild 2011'): """ Computes *hdr-CIELAB* colourspace *Lightness* :math:`\\epsilon` exponent using *Fairchild and Wyble (2010)* or *Fairchild and Chen (2011)* method. Parameters ---------- 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 ------- array_like *hdr-CIELAB* colourspace *Lightness* :math:`\\epsilon` exponent. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y_s`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> exponent_hdr_CIELab(0.2, 100) # doctest: +ELLIPSIS 0.4738510... >>> exponent_hdr_CIELab(0.2, 100, method='Fairchild 2010') ... # doctest: +ELLIPSIS 1.8360198... """ Y_s = to_domain_1(Y_s) Y_abs = as_float_array(Y_abs) 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': epsilon = 1.50 else: epsilon = 0.58 sf = 1.25 - 0.25 * (Y_s / 0.184) lf = np.log(318) / np.log(Y_abs) if method_l == 'fairchild 2010': epsilon *= sf * lf else: epsilon /= sf * lf return epsilon
def plot_RGB_chromaticities_in_chromaticity_diagram( RGB, colourspace='sRGB', chromaticity_diagram_callable=( plot_RGB_colourspaces_in_chromaticity_diagram), method='CIE 1931', scatter_parameters=None, **kwargs): """ Plots given *RGB* colourspace array in the *Chromaticity Diagram* according to given method. Parameters ---------- RGB : array_like *RGB* colourspace array. colourspace : optional, unicode *RGB* colourspace of the *RGB* array. 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. scatter_parameters : dict, optional Parameters for the :func:`plt.scatter` definition, if ``c`` is set to *RGB*, the scatter will use given ``RGB`` colours. 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 -------- >>> RGB = np.random.random((128, 128, 3)) >>> plot_RGB_chromaticities_in_chromaticity_diagram( ... RGB, 'ITU-R BT.709') ... # doctest: +SKIP .. image:: ../_static/Plotting_\ Plot_RGB_Chromaticities_In_Chromaticity_Diagram_Plot.png :align: center :alt: plot_RGB_chromaticities_in_chromaticity_diagram """ RGB = as_float_array(RGB).reshape(-1, 3) settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() scatter_settings = { 's': 40, 'c': 'RGB', 'marker': 'o', 'alpha': 0.85, } if scatter_parameters is not None: scatter_settings.update(scatter_parameters) settings = dict(kwargs) settings.update({'axes': axes, 'standalone': False}) colourspace = first_item(filter_RGB_colourspaces(colourspace).values()) settings['colourspaces'] = (['^{0}$'.format(colourspace.name)] + settings.get('colourspaces', [])) chromaticity_diagram_callable(**settings) use_RGB_colours = scatter_settings['c'].upper() == 'RGB' if use_RGB_colours: RGB = RGB[RGB[:, 1].argsort()] scatter_settings['c'] = np.clip( RGB_to_RGB(RGB, colourspace, COLOUR_STYLE_CONSTANTS.colour.colourspace, apply_encoding_cctf=True).reshape(-1, 3), 0, 1) XYZ = RGB_to_XYZ(RGB, colourspace.whitepoint, colourspace.whitepoint, colourspace.RGB_to_XYZ_matrix) if method == 'CIE 1931': ij = XYZ_to_xy(XYZ, colourspace.whitepoint) elif method == 'CIE 1960 UCS': ij = UCS_to_uv(XYZ_to_UCS(XYZ)) elif method == 'CIE 1976 UCS': ij = Luv_to_uv(XYZ_to_Luv(XYZ, colourspace.whitepoint), colourspace.whitepoint) axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings) settings.update({'standalone': True}) settings.update(kwargs) return render(**settings)
def plot_pointer_gamut(method='CIE 1931', **kwargs): """ Plots *Pointer's Gamut* according to given method. Parameters ---------- method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, Plotting method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_pointer_gamut() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Pointer_Gamut.png :align: center :alt: plot_pointer_gamut """ settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() if method == 'CIE 1931': def XYZ_to_ij(XYZ, *args): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ, *args) def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ, *args): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) 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 XYZ_to_ij(XYZ, *args): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ, *args), *args) 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)) ij = xy_to_ij(as_float_array(POINTER_GAMUT_BOUNDARIES)) alpha_p = COLOUR_STYLE_CONSTANTS.opacity.high colour_p = COLOUR_STYLE_CONSTANTS.colour.darkest axes.plot(ij[..., 0], ij[..., 1], label='Pointer\'s Gamut', color=colour_p, alpha=alpha_p) axes.plot((ij[-1][0], ij[0][0]), (ij[-1][1], ij[0][1]), color=colour_p, alpha=alpha_p) XYZ = Lab_to_XYZ(LCHab_to_Lab(POINTER_GAMUT_DATA), POINTER_GAMUT_ILLUMINANT) ij = XYZ_to_ij(XYZ, POINTER_GAMUT_ILLUMINANT) axes.scatter(ij[..., 0], ij[..., 1], alpha=alpha_p / 2, color=colour_p, marker='+') settings.update({'axes': axes}) settings.update(kwargs) return render(**settings)
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)
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 logarithmic_function_quasilog( x: FloatingOrArrayLike, style: Union[Literal["linToLog", "logToLin"], str] = "linToLog", base: Integer = 2, log_side_slope: Floating = 1, lin_side_slope: Floating = 1, log_side_offset: Floating = 0, lin_side_offset: Floating = 0, ) -> FloatingOrNDArray: """ Define the quasilog logarithmic function. Parameters ---------- x Linear/non-linear data to undergo encoding/decoding. style Defines the behaviour for the logarithmic function to operate: - *linToLog*: Applies a logarithm to convert linear data to logarithmic data. - *logToLin*: Applies an anti-logarithm to convert logarithmic data to linear data. base Logarithmic base used for the conversion. log_side_slope Slope (or gain) applied to the log side of the logarithmic function. The default value is 1. lin_side_slope Slope of the linear side of the logarithmic function. The default value is 1. log_side_offset Offset applied to the log side of the logarithmic function. The default value is 0. lin_side_offset Offset applied to the linear side of the logarithmic function. The default value is 0. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Encoded/Decoded data. Examples -------- >>> logarithmic_function_quasilog( # doctest: +ELLIPSIS ... 0.18, 'linToLog') -2.4739311... >>> logarithmic_function_quasilog( # doctest: +ELLIPSIS ... -2.473931188332412, 'logToLin') 0.18000000... """ x = as_float_array(x) style = validate_method( style, ["lintolog", "logtolin"], '"{0}" style is invalid, it must be one of {1}!', ) if style == "lintolog": return as_float(log_side_slope * ( np.log(np.maximum(lin_side_slope * x + lin_side_offset, FLT_MIN)) / np.log(base)) + log_side_offset) else: # style == 'logtolin' return as_float((base**( (x - log_side_offset) / log_side_slope) - lin_side_offset) / lin_side_slope)