Coverage for colour/appearance/rlab.py: 100%
62 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2RLAB Colour Appearance Model
3============================
5Define the *RLAB* colour appearance model for predicting perceptual colour
6attributes under varying viewing conditions.
8- :attr:`colour.VIEWING_CONDITIONS_RLAB`
9- :attr:`colour.D_FACTOR_RLAB`
10- :class:`colour.CAM_Specification_RLAB`
11- :func:`colour.XYZ_to_RLAB`
13References
14----------
15- :cite:`Fairchild1996a` : Fairchild, M. D. (1996). Refinement of the RLAB
16 color space. Color Research & Application, 21(5), 338-346.
17 doi:10.1002/(SICI)1520-6378(199610)21:5<338::AID-COL3>3.0.CO;2-Z
18- :cite:`Fairchild2013w` : Fairchild, M. D. (2013). The RLAB Model. In Color
19 Appearance Models (3rd ed., pp. 5563-5824). Wiley. ISBN:B00DAYO8E2
20"""
22from __future__ import annotations
24from dataclasses import dataclass, field
26import numpy as np
28from colour.algebra import sdiv, sdiv_mode, spow, vecmul
29from colour.appearance.hunt import MATRIX_XYZ_TO_HPE, XYZ_to_rgb
30from colour.hints import Annotated, ArrayLike, Domain100, NDArrayFloat # noqa: TC001
31from colour.utilities import (
32 CanonicalMapping,
33 MixinDataclassArray,
34 as_float,
35 as_float_array,
36 from_range_degrees,
37 row_as_diagonal,
38 to_domain_100,
39 tsplit,
40)
42__author__ = "Colour Developers"
43__copyright__ = "Copyright 2013 Colour Developers"
44__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
45__maintainer__ = "Colour Developers"
46__email__ = "colour-developers@colour-science.org"
47__status__ = "Production"
49__all__ = [
50 "MATRIX_R",
51 "VIEWING_CONDITIONS_RLAB",
52 "D_FACTOR_RLAB",
53 "CAM_ReferenceSpecification_RLAB",
54 "CAM_Specification_RLAB",
55 "XYZ_to_RLAB",
56]
58MATRIX_R: NDArrayFloat = np.array(
59 [
60 [1.9569, -1.1882, 0.2313],
61 [0.3612, 0.6388, 0.0000],
62 [0.0000, 0.0000, 1.0000],
63 ]
64)
65"""*RLAB* colour appearance model precomputed helper matrix."""
67VIEWING_CONDITIONS_RLAB: CanonicalMapping = CanonicalMapping(
68 {"Average": 1 / 2.3, "Dim": 1 / 2.9, "Dark": 1 / 3.5}
69)
70VIEWING_CONDITIONS_RLAB.__doc__ = """
71Define the reference *RLAB* colour appearance model viewing conditions.
73References
74----------
75:cite:`Fairchild1996a`, :cite:`Fairchild2013w`
76"""
78D_FACTOR_RLAB: CanonicalMapping = CanonicalMapping(
79 {
80 "Hard Copy Images": 1,
81 "Soft Copy Images": 0,
82 "Projected Transparencies, Dark Room": 0.5,
83 }
84)
85D_FACTOR_RLAB.__doc__ = """
86Define the *RLAB* colour appearance model *Discounting-the-Illuminant*
87factor values for the specified media types.
89References
90----------
91:cite:`Fairchild1996a`, :cite:`Fairchild2013w`
93Aliases:
95- 'hard_cp_img': 'Hard Copy Images'
96- 'soft_cp_img': 'Soft Copy Images'
97- 'projected_dark': 'Projected Transparencies, Dark Room'
98"""
99D_FACTOR_RLAB["hard_cp_img"] = D_FACTOR_RLAB["Hard Copy Images"]
100D_FACTOR_RLAB["soft_cp_img"] = D_FACTOR_RLAB["Soft Copy Images"]
101D_FACTOR_RLAB["projected_dark"] = D_FACTOR_RLAB["Projected Transparencies, Dark Room"]
104@dataclass
105class CAM_ReferenceSpecification_RLAB(MixinDataclassArray):
106 """
107 Define the *RLAB* colour appearance model reference specification.
109 This specification contains field names consistent with the *Fairchild
110 (2013)* reference.
112 Parameters
113 ----------
114 LR
115 Correlate of *lightness* :math:`L^R`.
116 CR
117 Correlate of *achromatic chroma* :math:`C^R`.
118 hR
119 *Hue* angle :math:`h^R` in degrees.
120 sR
121 Correlate of *saturation* :math:`s^R`.
122 HR
123 *Hue* :math:`h` composition :math:`H^R`.
124 aR
125 Red-green chromatic response :math:`a^R`.
126 bR
127 Yellow-blue chromatic response :math:`b^R`.
129 References
130 ----------
131 :cite:`Fairchild1996a`, :cite:`Fairchild2013w`
132 """
134 LR: float | NDArrayFloat | None = field(default_factory=lambda: None)
135 CR: float | NDArrayFloat | None = field(default_factory=lambda: None)
136 hR: float | NDArrayFloat | None = field(default_factory=lambda: None)
137 sR: float | NDArrayFloat | None = field(default_factory=lambda: None)
138 HR: float | NDArrayFloat | None = field(default_factory=lambda: None)
139 aR: float | NDArrayFloat | None = field(default_factory=lambda: None)
140 bR: float | NDArrayFloat | None = field(default_factory=lambda: None)
143@dataclass
144class CAM_Specification_RLAB(MixinDataclassArray):
145 """
146 Define the *RLAB* colour appearance model specification.
148 This specification provides a standardized interface for the *RLAB* model
149 with field names consistent across all colour appearance models in
150 :mod:`colour.appearance`. While the field names differ from the original
151 *Fairchild (2013)* reference notation, they map directly to the model's
152 perceptual correlates.
154 Parameters
155 ----------
156 J
157 Correlate of *lightness* :math:`L^R`.
158 C
159 Correlate of *achromatic chroma* :math:`C^R`.
160 h
161 *Hue* angle :math:`h^R` in degrees.
162 s
163 Correlate of *saturation* :math:`s^R`.
164 HC
165 *Hue* :math:`h` composition :math:`H^C`.
166 a
167 Red-green chromatic response :math:`a^R`.
168 b
169 Yellow-blue chromatic response :math:`b^R`.
171 Notes
172 -----
173 - This specification is the one used in the current model
174 implementation.
176 References
177 ----------
178 :cite:`Fairchild1996a`, :cite:`Fairchild2013w`
179 """
181 J: NDArrayFloat | None = field(default_factory=lambda: None)
182 C: NDArrayFloat | None = field(default_factory=lambda: None)
183 h: NDArrayFloat | None = field(default_factory=lambda: None)
184 s: NDArrayFloat | None = field(default_factory=lambda: None)
185 HC: NDArrayFloat | None = field(default_factory=lambda: None)
186 a: NDArrayFloat | None = field(default_factory=lambda: None)
187 b: NDArrayFloat | None = field(default_factory=lambda: None)
190def XYZ_to_RLAB(
191 XYZ: Domain100,
192 XYZ_n: Domain100,
193 Y_n: ArrayLike,
194 sigma: ArrayLike = VIEWING_CONDITIONS_RLAB["Average"],
195 D: ArrayLike = D_FACTOR_RLAB["Hard Copy Images"],
196) -> Annotated[CAM_Specification_RLAB, 360]:
197 """
198 Compute the *RLAB* colour appearance model correlates from the specified
199 *CIE XYZ* tristimulus values.
201 Parameters
202 ----------
203 XYZ
204 *CIE XYZ* tristimulus values of test sample / stimulus.
205 XYZ_n
206 *CIE XYZ* tristimulus values of reference white.
207 Y_n
208 Absolute adapting luminance in :math:`cd/m^2`.
209 sigma
210 Relative luminance of the surround, see
211 :attr:`colour.VIEWING_CONDITIONS_RLAB` for reference.
212 D
213 *Discounting-the-Illuminant* factor normalised to domain [0, 1].
215 Returns
216 -------
217 CAM_Specification_RLAB
218 *RLAB* colour appearance model specification.
220 Notes
221 -----
222 +---------------------+-----------------------+---------------+
223 | **Domain** | **Scale - Reference** | **Scale - 1** |
224 +=====================+=======================+===============+
225 | ``XYZ`` | 100 | 1 |
226 +---------------------+-----------------------+---------------+
227 | ``XYZ_n`` | 100 | 1 |
228 +---------------------+-----------------------+---------------+
230 +---------------------+-----------------------+---------------+
231 | **Range** | **Scale - Reference** | **Scale - 1** |
232 +=====================+=======================+===============+
233 | ``specification.h`` | 360 | 1 |
234 +---------------------+-----------------------+---------------+
236 References
237 ----------
238 :cite:`Fairchild1996a`, :cite:`Fairchild2013w`
240 Examples
241 --------
242 >>> XYZ = np.array([19.01, 20.00, 21.78])
243 >>> XYZ_n = np.array([109.85, 100, 35.58])
244 >>> Y_n = 31.83
245 >>> sigma = VIEWING_CONDITIONS_RLAB["Average"]
246 >>> D = D_FACTOR_RLAB["Hard Copy Images"]
247 >>> XYZ_to_RLAB(XYZ, XYZ_n, Y_n, sigma, D) # doctest: +ELLIPSIS
248 CAM_Specification_RLAB(J=49.8347069..., C=54.8700585..., \
249h=286.4860208..., s=1.1010410..., HC=None, a=15.5711021..., \
250b=-52.6142956...)
251 """
253 XYZ = to_domain_100(XYZ)
254 XYZ_n = to_domain_100(XYZ_n)
255 Y_n = as_float_array(Y_n)
256 D = as_float_array(D)
257 sigma = as_float_array(sigma)
259 # Converting to cone responses.
260 LMS_n = XYZ_to_rgb(XYZ_n)
262 # Computing the :math:`A` matrix.
263 LMS_l_E = 3 * LMS_n / np.sum(LMS_n, axis=-1)[..., None]
264 LMS_p_L = (1 + spow(Y_n[..., None], 1 / 3) + LMS_l_E) / (
265 1 + spow(Y_n[..., None], 1 / 3) + 1 / LMS_l_E
266 )
268 LMS_a_L = (LMS_p_L + D[..., None] * (1 - LMS_p_L)) / LMS_n
270 M = np.matmul(np.matmul(MATRIX_R, row_as_diagonal(LMS_a_L)), MATRIX_XYZ_TO_HPE)
271 XYZ_ref = vecmul(M, XYZ)
273 Y_ref: NDArrayFloat
274 X_ref, Y_ref, Z_ref = tsplit(XYZ_ref)
276 # Computing the correlate of *Lightness* :math:`L^R`.
277 LR = 100 * spow(Y_ref, sigma)
279 # Computing opponent colour dimensions :math:`a^R` and :math:`b^R`.
280 aR = 430 * (spow(X_ref, sigma) - spow(Y_ref, sigma))
281 bR = 170 * (spow(Y_ref, sigma) - spow(Z_ref, sigma))
283 # Computing the *hue* angle :math:`h^R`.
284 hR = np.degrees(np.arctan2(bR, aR)) % 360
285 # TODO: Implement hue composition computation.
287 # Computing the correlate of *chroma* :math:`C^R`.
288 CR = np.hypot(aR, bR)
290 # Computing the correlate of *saturation* :math:`s^R`.
291 with sdiv_mode():
292 sR = sdiv(CR, LR)
294 return CAM_Specification_RLAB(
295 J=LR,
296 C=CR,
297 h=as_float(from_range_degrees(hR)),
298 s=sR,
299 HC=None,
300 a=as_float(aR),
301 b=as_float(bR),
302 )