Coverage for colour/adaptation/__init__.py: 100%
51 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 23:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 23:01 +1300
1"""
2Provide chromatic adaptation models that predict how colours appear under
3different illumination conditions.
5References
6----------
7- :cite:`CIETC1-321994b` : CIE TC 1-32. (1994). CIE 109-1994 A Method of
8 Predicting Corresponding Colours under Different Chromatic and Illuminance
9 Adaptations. Commission Internationale de l'Eclairage.
10 ISBN:978-3-900734-51-0
11- :cite:`Fairchild1991a` : Fairchild, M. D. (1991). Formulation and testing
12 of an incomplete-chromatic-adaptation model. Color Research & Application,
13 16(4), 243-250. doi:10.1002/col.5080160406
14- :cite:`Fairchild2013s` : Fairchild, M. D. (2013). FAIRCHILD'S 1990 MODEL.
15 In Color Appearance Models (3rd ed., pp. 4418-4495). Wiley. ISBN:B00DAYO8E2
16- :cite:`Fairchild2013t` : Fairchild, M. D. (2013). Chromatic Adaptation
17 Models. In Color Appearance Models (3rd ed., pp. 4179-4252). Wiley.
18 ISBN:B00DAYO8E2
19- :cite:`Fairchild2020` : Fairchild, M. D. (2020). Von Kries 2020: Evolution
20 of degree of chromatic adaptation. Color and Imaging Conference, 28(1),
21 252-257. doi:10.2352/issn.2169-2629.2020.28.40
22- :cite:`Li2002a` : Li, C., Luo, M. R., Rigg, B., & Hunt, R. W. G. (2002).
23 CMC 2000 chromatic adaptation transform: CMCCAT2000. Color Research &
24 Application, 27(1), 49-58. doi:10.1002/col.10005
25- :cite:`Li2025` : Li, M. (2025). One Step CAT16 Chromatic Adaptation
26 Transform. https://github.com/colour-science/colour/pull/1349\
27#issuecomment-3058339414
28- :cite:`Westland2012k` : Westland, S., Ripamonti, C., & Cheung, V. (2012).
29 CMCCAT2000. In Computational Colour Science Using MATLAB (2nd ed., pp.
30 83-86). ISBN:978-0-470-66569-5
31- :cite:`Zhai2018` : Zhai, Q., & Luo, M. R. (2018). Study of chromatic
32 adaptation via neutral white matches on different viewing media. Optics
33 Express, 26(6), 7724. doi:10.1364/OE.26.007724
34"""
36from __future__ import annotations
38import typing
40if typing.TYPE_CHECKING:
41 from colour.hints import Any, ArrayLike, Literal, NDArrayFloat
43# isort: split
45from colour.utilities import (
46 CanonicalMapping,
47 as_float_array,
48 filter_kwargs,
49 get_domain_range_scale,
50 validate_method,
51)
53# isort: split
55from .datasets import (
56 CAT_BIANCO2010,
57 CAT_BRADFORD,
58 CAT_CAT02,
59 CAT_CAT02_BRILL2008,
60 CAT_CAT16,
61 CAT_CMCCAT97,
62 CAT_CMCCAT2000,
63 CAT_FAIRCHILD,
64 CAT_PC_BIANCO2010,
65 CAT_SHARP,
66 CAT_VON_KRIES,
67 CAT_XYZ_SCALING,
68 CHROMATIC_ADAPTATION_TRANSFORMS,
69)
71# isort: split
73from .vonkries import (
74 chromatic_adaptation_VonKries,
75 matrix_chromatic_adaptation_VonKries,
76)
78# isort: split
80from .fairchild1990 import chromatic_adaptation_Fairchild1990
81from .fairchild2020 import (
82 CONDITIONS_DEGREE_OF_ADAPTATION_VK20,
83 chromatic_adaptation_vK20,
84 matrix_chromatic_adaptation_vk20,
85)
87# isort: split
89from .cie1994 import chromatic_adaptation_CIE1994
90from .cmccat2000 import (
91 VIEWING_CONDITIONS_CMCCAT2000,
92 InductionFactors_CMCCAT2000,
93 chromatic_adaptation_CMCCAT2000,
94 chromatic_adaptation_forward_CMCCAT2000,
95 chromatic_adaptation_inverse_CMCCAT2000,
96)
97from .zhai2018 import chromatic_adaptation_Zhai2018
99# isort: split
101from .li2025 import CAT_CAT16_INVERSE, chromatic_adaptation_Li2025
103__all__ = [
104 "CAT_BIANCO2010",
105 "CAT_BRADFORD",
106 "CAT_CAT02",
107 "CAT_CAT02_BRILL2008",
108 "CAT_CAT16",
109 "CAT_CMCCAT97",
110 "CAT_CMCCAT2000",
111 "CAT_FAIRCHILD",
112 "CAT_PC_BIANCO2010",
113 "CAT_SHARP",
114 "CAT_VON_KRIES",
115 "CAT_XYZ_SCALING",
116 "CHROMATIC_ADAPTATION_TRANSFORMS",
117]
118__all__ += [
119 "chromatic_adaptation_VonKries",
120 "matrix_chromatic_adaptation_VonKries",
121]
122__all__ += [
123 "chromatic_adaptation_Fairchild1990",
124]
125__all__ += [
126 "CONDITIONS_DEGREE_OF_ADAPTATION_VK20",
127 "chromatic_adaptation_vK20",
128 "matrix_chromatic_adaptation_vk20",
129]
130__all__ += [
131 "chromatic_adaptation_CIE1994",
132]
133__all__ += [
134 "VIEWING_CONDITIONS_CMCCAT2000",
135 "InductionFactors_CMCCAT2000",
136 "chromatic_adaptation_CMCCAT2000",
137 "chromatic_adaptation_forward_CMCCAT2000",
138 "chromatic_adaptation_inverse_CMCCAT2000",
139]
140__all__ += [
141 "chromatic_adaptation_Zhai2018",
142]
143__all__ += [
144 "CAT_CAT16_INVERSE",
145 "chromatic_adaptation_Li2025",
146]
148CHROMATIC_ADAPTATION_METHODS: CanonicalMapping = CanonicalMapping(
149 {
150 "CIE 1994": chromatic_adaptation_CIE1994,
151 "CMCCAT2000": chromatic_adaptation_CMCCAT2000,
152 "Fairchild 1990": chromatic_adaptation_Fairchild1990,
153 "Li 2025": chromatic_adaptation_Li2025,
154 "Von Kries": chromatic_adaptation_VonKries,
155 "Zhai 2018": chromatic_adaptation_Zhai2018,
156 "vK20": chromatic_adaptation_vK20,
157 }
158)
159CHROMATIC_ADAPTATION_METHODS.__doc__ = """
160Supported chromatic adaptation methods.
162References
163----------
164:cite:`CIETC1-321994b`, :cite:`Fairchild1991a`, :cite:`Fairchild2013s`,
165:cite:`Fairchild2013t`, :cite:`Fairchild2020`, :cite:`Li2002a`,
166:cite:`Li2025`, :cite:`Westland2012k`, :cite:`Zhai2018`
167"""
170def chromatic_adaptation(
171 XYZ: ArrayLike,
172 XYZ_w: ArrayLike,
173 XYZ_wr: ArrayLike,
174 method: (
175 Literal[
176 "CIE 1994",
177 "CMCCAT2000",
178 "Fairchild 1990",
179 "Li 2025",
180 "Von Kries",
181 "Zhai 2018",
182 "vK20",
183 ]
184 | str
185 ) = "Von Kries",
186 **kwargs: Any,
187) -> NDArrayFloat:
188 """
189 Adapt the specified stimulus *CIE XYZ* tristimulus values from test
190 viewing conditions to reference viewing conditions using the specified
191 chromatic adaptation method.
193 Parameters
194 ----------
195 XYZ
196 *CIE XYZ* tristimulus values of stimulus to adapt.
197 XYZ_w
198 Test viewing condition *CIE XYZ* tristimulus values of the
199 whitepoint.
200 XYZ_wr
201 Reference viewing condition *CIE XYZ* tristimulus values of the
202 whitepoint.
203 method
204 Computation method.
206 Other Parameters
207 ----------------
208 E_o1
209 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
210 Test illuminance :math:`E_{o1}` in :math:`lux`.
211 E_o2
212 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
213 Reference illuminance :math:`E_{o2}` in :math:`lux`.
214 n
215 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
216 Noise component in fundamental primary system.
217 Y_o
218 {:func:`colour.adaptation.chromatic_adaptation_CIE1994`},
219 Luminance factor :math:`Y_o` of achromatic background normalised
220 to domain [0.18, 1] in **'Reference'** domain-range scale.
221 direction
222 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
223 Chromatic adaptation direction.
224 L_A1
225 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
226 Luminance of test adapting field :math:`L_{A1}` in
227 :math:`cd/m^2`.
228 L_A2
229 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
230 Luminance of reference adapting field :math:`L_{A2}` in
231 :math:`cd/m^2`.
232 surround
233 {:func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`},
234 Surround viewing conditions induction factors.
235 discount_illuminant
236 {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`},
237 Truth value indicating if the illuminant should be discounted.
238 Y_n
239 {:func:`colour.adaptation.chromatic_adaptation_Fairchild1990`},
240 Luminance :math:`Y_n` of test adapting stimulus in
241 :math:`cd/m^2`.
242 L_A
243 {:func:`colour.adaptation.chromatic_adaptation_Li2025`},
244 Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`.
245 F_surround
246 {:func:`colour.adaptation.chromatic_adaptation_Li2025`},
247 Maximum degree of adaptation :math:`F` from surround viewing
248 conditions.
249 discount_illuminant
250 {:func:`colour.adaptation.chromatic_adaptation_Li2025`},
251 Truth value indicating if the illuminant should be discounted.
252 D_b
253 {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
254 Degree of adaptation :math:`D_\\beta` of input illuminant
255 :math:`\\beta`.
256 D_d
257 {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
258 Degree of adaptation :math:`D_\\Delta` of output illuminant
259 :math:`\\Delta`.
260 XYZ_r
261 {:func:`colour.adaptation.chromatic_adaptation_vK20`},
262 Reference viewing conditions *CIE XYZ* tristimulus values of
263 whitepoint.
264 coefficients
265 {:func:`colour.adaptation.chromatic_adaptation_vK20`},
266 *vK20* degree of adaptation coefficients.
267 transform
268 {:func:`colour.adaptation.chromatic_adaptation_VonKries`,
269 :func:`colour.adaptation.chromatic_adaptation_vK20`,
270 :func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
271 Chromatic adaptation transform.
272 XYZ_wo
273 {:func:`colour.adaptation.chromatic_adaptation_Zhai2018`},
274 Baseline illuminant (:math:`BI`) :math:`o`.
276 Returns
277 -------
278 :class:`numpy.ndarray`
279 *CIE XYZ* tristimulus values of the stimulus corresponding colour.
281 Notes
282 -----
283 +------------+-----------------------+---------------+
284 | **Domain** | **Scale - Reference** | **Scale - 1** |
285 +============+=======================+===============+
286 | ``XYZ`` | 1 | 1 |
287 +------------+-----------------------+---------------+
288 | ``XYZ_w`` | 1 | 1 |
289 +------------+-----------------------+---------------+
290 | ``XYZ_wr`` | 1 | 1 |
291 +------------+-----------------------+---------------+
292 | ``XYZ_wo`` | 1 | 1 |
293 +------------+-----------------------+---------------+
294 | ``Y_o`` | 1 | 1 |
295 +------------+-----------------------+---------------+
297 +------------+-----------------------+---------------+
298 | **Range** | **Scale - Reference** | **Scale - 1** |
299 +============+=======================+===============+
300 | ``XYZ_c`` | 1 | 1 |
301 +------------+-----------------------+---------------+
303 References
304 ----------
305 :cite:`CIETC1-321994b`, :cite:`Fairchild1991a`,
306 :cite:`Fairchild2013s`, :cite:`Fairchild2013t`, :cite:`Li2002a`,
307 :cite:`Li2025`, :cite:`Westland2012k`
309 Examples
310 --------
311 *Von Kries* chromatic adaptation:
313 >>> import numpy as np
314 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
315 >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775])
316 >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460])
317 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr)
318 ... # doctest: +ELLIPSIS
319 array([ 0.2163881..., 0.1257 , 0.0384749...])
321 *vK2020* chromatic adaptation:
323 >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775])
324 >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460])
325 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="vK20")
326 ... # doctest: +ELLIPSIS
327 array([ 0.2146884..., 0.1245616..., 0.0466255...])
329 *CIE 1994* chromatic adaptation, requires extra *kwargs*:
331 >>> XYZ = np.array([0.2800, 0.2126, 0.0527])
332 >>> XYZ_w = np.array([1.09867452, 1.00000000, 0.35591556])
333 >>> XYZ_wr = np.array([0.95045593, 1.00000000, 1.08905775])
334 >>> Y_o = 0.20
335 >>> E_o = 1000
336 >>> chromatic_adaptation(
337 ... XYZ, XYZ_w, XYZ_wr, method="CIE 1994", Y_o=Y_o, E_o1=E_o, E_o2=E_o
338 ... )
339 ... # doctest: +ELLIPSIS
340 array([ 0.2403379..., 0.2115621..., 0.1764301...])
342 *CMCCAT2000* chromatic adaptation, requires extra *kwargs*:
344 >>> XYZ = np.array([0.2248, 0.2274, 0.0854])
345 >>> XYZ_w = np.array([1.1115, 1.0000, 0.3520])
346 >>> XYZ_wr = np.array([0.9481, 1.0000, 1.0730])
347 >>> L_A = 200
348 >>> chromatic_adaptation(
349 ... XYZ, XYZ_w, XYZ_wr, method="CMCCAT2000", L_A1=L_A, L_A2=L_A
350 ... )
351 ... # doctest: +ELLIPSIS
352 array([ 0.1952698..., 0.2306834..., 0.2497175...])
354 *Fairchild (1990)* chromatic adaptation, requires extra *kwargs*:
356 >>> XYZ = np.array([0.1953, 0.2307, 0.2497])
357 >>> Y_n = 200
358 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="Fairchild 1990", Y_n=Y_n)
359 ... # doctest: +ELLIPSIS
360 array([ 0.2332526..., 0.2332455..., 0.7611593...])
362 *Li (2025)* chromatic adaptation:
364 >>> XYZ = np.array([0.1953, 0.2307, 0.2497])
365 >>> L_A = 64
366 >>> F_surround = 1.0
367 >>> chromatic_adaptation(
368 ... XYZ, XYZ_w, XYZ_wr, method="Li 2025", L_A=L_A, F_surround=F_surround
369 ... )
370 ... # doctest: +ELLIPSIS
371 array([ 0.2039701..., 0.2304747..., 0.6783065...])
373 *Zhai and Luo (2018)* chromatic adaptation:
375 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
376 >>> XYZ_w = np.array([0.95045593, 1.00000000, 1.08905775])
377 >>> XYZ_wr = np.array([0.96429568, 1.00000000, 0.82510460])
378 >>> chromatic_adaptation(XYZ, XYZ_w, XYZ_wr, method="Zhai 2018")
379 ... # doctest: +ELLIPSIS
380 array([ 0.2163881..., 0.1257 , 0.0384749...])
381 >>> chromatic_adaptation(
382 ... XYZ,
383 ... XYZ_w,
384 ... XYZ_wr,
385 ... method="Zhai 2018",
386 ... D_b=0.9,
387 ... XYZ_wo=np.array([100, 100, 100]),
388 ... )
389 ... # doctest: +ELLIPSIS
390 array([ 0.2152436..., 0.1253522..., 0.0388406...])
391 """
393 method = validate_method(method, tuple(CHROMATIC_ADAPTATION_METHODS))
395 function = CHROMATIC_ADAPTATION_METHODS[method]
397 domain_range_reference = get_domain_range_scale() == "reference"
398 domain_100 = (
399 chromatic_adaptation_CIE1994,
400 chromatic_adaptation_CMCCAT2000,
401 chromatic_adaptation_Fairchild1990,
402 chromatic_adaptation_Li2025,
403 chromatic_adaptation_Zhai2018,
404 )
406 if function in domain_100 and domain_range_reference:
407 XYZ = as_float_array(XYZ) * 100
408 XYZ_w = as_float_array(XYZ_w) * 100
409 XYZ_wr = as_float_array(XYZ_wr) * 100
411 if "Y_o" in kwargs:
412 kwargs["Y_o"] = kwargs["Y_o"] * 100
414 if "XYZ_wo" in kwargs:
415 kwargs["XYZ_wo"] = kwargs["XYZ_wo"] * 100
417 kwargs.update({"XYZ_w": XYZ_w, "XYZ_wr": XYZ_wr})
419 if function is chromatic_adaptation_CIE1994:
420 from colour import XYZ_to_xy # noqa: PLC0415
422 kwargs.update({"xy_o1": XYZ_to_xy(XYZ_w), "xy_o2": XYZ_to_xy(XYZ_wr)})
423 elif function is chromatic_adaptation_Fairchild1990:
424 kwargs.update({"XYZ_n": XYZ_w, "XYZ_r": XYZ_wr})
425 elif function is chromatic_adaptation_Li2025:
426 kwargs.update({"XYZ_ws": XYZ_w, "XYZ_wd": XYZ_wr})
427 elif function is chromatic_adaptation_Zhai2018:
428 kwargs.update({"XYZ_wb": XYZ_w, "XYZ_wd": XYZ_wr})
429 elif function is chromatic_adaptation_vK20:
430 kwargs.update({"XYZ_p": XYZ_w, "XYZ_n": XYZ_wr})
432 XYZ_c = function(XYZ, **filter_kwargs(function, **kwargs))
434 if function in domain_100 and domain_range_reference:
435 XYZ_c /= 100
437 return XYZ_c
440__all__ += [
441 "CHROMATIC_ADAPTATION_METHODS",
442 "chromatic_adaptation",
443]