Coverage for colour/appearance/tests/test_ciecam02.py: 100%
131 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"""Define the unit tests for the :mod:`colour.appearance.ciecam02` module."""
3from __future__ import annotations
5from itertools import product
7import numpy as np
8import pytest
10from colour.appearance import (
11 VIEWING_CONDITIONS_CIECAM02,
12 CAM_Specification_CIECAM02,
13 CIECAM02_to_XYZ,
14 InductionFactors_CIECAM02,
15 XYZ_to_CIECAM02,
16)
17from colour.constants import TOLERANCE_ABSOLUTE_TESTS
18from colour.utilities import (
19 as_float_array,
20 domain_range_scale,
21 ignore_numpy_errors,
22 tsplit,
23)
25__author__ = "Colour Developers"
26__copyright__ = "Copyright 2013 Colour Developers"
27__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
28__maintainer__ = "Colour Developers"
29__email__ = "colour-developers@colour-science.org"
30__status__ = "Production"
32__all__ = [
33 "TestXYZ_to_CIECAM02",
34 "TestCIECAM02_to_XYZ",
35]
38class TestXYZ_to_CIECAM02:
39 """
40 Define :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition unit
41 tests methods.
42 """
44 def test_XYZ_to_CIECAM02(self) -> None:
45 """
46 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition.
48 Notes
49 -----
50 - The test values have been generated from data of the following file
51 by *Fairchild (2013)*:
52 http://rit-mcsl.org/fairchild//files/AppModEx.xls
53 """
55 XYZ = np.array([19.01, 20.00, 21.78])
56 XYZ_w = np.array([95.05, 100.00, 108.88])
57 L_A = 318.31
58 Y_b = 20
59 surround = InductionFactors_CIECAM02(1, 0.69, 1)
60 np.testing.assert_allclose(
61 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
62 np.array([41.73, 0.1, 219, 2.36, 195.37, 0.11, 278.1, np.nan]),
63 atol=0.05,
64 )
66 XYZ = np.array([57.06, 43.06, 31.96])
67 L_A = 31.83
68 np.testing.assert_allclose(
69 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
70 np.array([65.96, 48.57, 19.6, 52.25, 152.67, 41.67, 399.6, np.nan]),
71 atol=0.05,
72 )
74 XYZ = np.array([3.53, 6.56, 2.14])
75 XYZ_w = np.array([109.85, 100.00, 35.58])
76 L_A = 318.31
77 np.testing.assert_allclose(
78 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
79 np.array([21.79, 46.94, 177.1, 58.79, 141.17, 48.8, 220.4, np.nan]),
80 atol=0.05,
81 )
83 XYZ = np.array([19.01, 20.00, 21.78])
84 L_A = 31.83
85 np.testing.assert_allclose(
86 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
87 np.array([42.53, 51.92, 248.9, 60.22, 122.83, 44.54, 305.8, np.nan]),
88 atol=0.05,
89 )
91 XYZ = np.array([61.45276998, 7.00421901, 82.24067384])
92 XYZ_w = np.array([95.05, 100, 108.88])
93 L_A = 4.074366543152521
94 np.testing.assert_allclose(
95 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
96 np.array(
97 [
98 21.72630603341673,
99 411.5190338631848,
100 349.12875710099053,
101 227.15081998415593,
102 57.657243286322725,
103 297.49693233026602,
104 375.5788601911363,
105 np.nan,
106 ]
107 ),
108 atol=0.05,
109 )
111 def test_n_dimensional_XYZ_to_CIECAM02(self) -> None:
112 """
113 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition
114 n-dimensional support.
115 """
117 XYZ = np.array([19.01, 20.00, 21.78])
118 XYZ_w = np.array([95.05, 100.00, 108.88])
119 L_A = 318.31
120 Y_b = 20
121 surround = VIEWING_CONDITIONS_CIECAM02["Average"]
122 specification = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround)
124 XYZ = np.tile(XYZ, (6, 1))
125 specification = np.tile(specification, (6, 1))
126 np.testing.assert_allclose(
127 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
128 specification,
129 atol=TOLERANCE_ABSOLUTE_TESTS,
130 )
132 XYZ_w = np.tile(XYZ_w, (6, 1))
133 np.testing.assert_allclose(
134 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
135 specification,
136 atol=TOLERANCE_ABSOLUTE_TESTS,
137 )
139 XYZ = np.reshape(XYZ, (2, 3, 3))
140 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
141 specification = np.reshape(specification, (2, 3, 8))
142 np.testing.assert_allclose(
143 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround),
144 specification,
145 atol=TOLERANCE_ABSOLUTE_TESTS,
146 )
148 @ignore_numpy_errors
149 def test_domain_range_scale_XYZ_to_CIECAM02(self) -> None:
150 """
151 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition
152 domain and range scale support.
153 """
155 XYZ = np.array([19.01, 20.00, 21.78])
156 XYZ_w = np.array([95.05, 100.00, 108.88])
157 L_A = 318.31
158 Y_b = 20
159 surround = VIEWING_CONDITIONS_CIECAM02["Average"]
160 specification = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround)
162 d_r = (
163 ("reference", 1, 1),
164 (
165 "1",
166 0.01,
167 np.array(
168 [
169 1 / 100,
170 1 / 100,
171 1 / 360,
172 1 / 100,
173 1 / 100,
174 1 / 100,
175 1 / 400,
176 np.nan,
177 ]
178 ),
179 ),
180 (
181 "100",
182 1,
183 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]),
184 ),
185 )
186 for scale, factor_a, factor_b in d_r:
187 with domain_range_scale(scale):
188 np.testing.assert_allclose(
189 XYZ_to_CIECAM02(
190 XYZ * factor_a, XYZ_w * factor_a, L_A, Y_b, surround
191 ),
192 as_float_array(specification) * factor_b,
193 atol=TOLERANCE_ABSOLUTE_TESTS,
194 )
196 @ignore_numpy_errors
197 def test_nan_XYZ_to_CIECAM02(self) -> None:
198 """
199 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition
200 nan support.
201 """
203 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
204 cases = np.array(list(set(product(cases, repeat=3))))
205 surround = InductionFactors_CIECAM02(cases[0, 0], cases[0, 0], cases[0, 0])
206 XYZ_to_CIECAM02(cases, cases, cases[..., 0], cases[..., 0], surround)
209class TestCIECAM02_to_XYZ:
210 """
211 Define :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition unit
212 tests methods.
213 """
215 def test_CIECAM02_to_XYZ(self) -> None:
216 """Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition."""
218 specification = CAM_Specification_CIECAM02(
219 41.73, 0.1, 219, 2.36, 195.37, 0.11, 278.1
220 )
221 XYZ_w = np.array([95.05, 100.00, 108.88])
222 L_A = 318.31
223 Y_b = 20
224 surround = InductionFactors_CIECAM02(1, 0.69, 1)
225 np.testing.assert_allclose(
226 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
227 np.array([19.01, 20.00, 21.78]),
228 atol=0.05,
229 )
231 specification = CAM_Specification_CIECAM02(
232 65.96, 48.57, 19.6, 52.25, 152.67, 41.67, 399.6, np.nan
233 )
234 L_A = 31.83
235 np.testing.assert_allclose(
236 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
237 np.array([57.06, 43.06, 31.96]),
238 atol=0.05,
239 )
241 specification = CAM_Specification_CIECAM02(
242 21.79, 46.94, 177.1, 58.79, 141.17, 48.8, 220.4, np.nan
243 )
244 XYZ_w = np.array([109.85, 100.00, 35.58])
245 L_A = 318.31
246 np.testing.assert_allclose(
247 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
248 np.array([3.53, 6.56, 2.14]),
249 atol=0.05,
250 )
252 specification = CAM_Specification_CIECAM02(
253 42.53, 51.92, 248.9, 60.22, 122.83, 44.54, 305.8, np.nan
254 )
255 L_A = 31.83
256 np.testing.assert_allclose(
257 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
258 np.array([19.01, 20.00, 21.78]),
259 atol=0.05,
260 )
262 specification = CAM_Specification_CIECAM02(
263 21.72630603341673,
264 411.5190338631848,
265 349.12875710099053,
266 227.15081998415593,
267 57.657243286322725,
268 297.49693233026602,
269 375.5788601911363,
270 np.nan,
271 )
272 XYZ_w = np.array([95.05, 100, 108.88])
273 L_A = 4.074366543152521
274 np.testing.assert_allclose(
275 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
276 np.array([61.45276998, 7.00421901, 82.24067384]),
277 atol=0.05,
278 )
280 def test_n_dimensional_CIECAM02_to_XYZ(self) -> None:
281 """
282 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition
283 n-dimensional support.
284 """
286 XYZ = np.array([19.01, 20.00, 21.78])
287 XYZ_w = np.array([95.05, 100.00, 108.88])
288 L_A = 318.31
289 Y_b = 20
290 surround = VIEWING_CONDITIONS_CIECAM02["Average"]
291 specification = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround)
292 XYZ = CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
294 specification = CAM_Specification_CIECAM02(
295 *np.transpose(np.tile(tsplit(specification), (6, 1))).tolist()
296 )
297 XYZ = np.tile(XYZ, (6, 1))
298 np.testing.assert_allclose(
299 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
300 XYZ,
301 atol=TOLERANCE_ABSOLUTE_TESTS,
302 )
304 XYZ_w = np.tile(XYZ_w, (6, 1))
305 np.testing.assert_allclose(
306 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
307 XYZ,
308 atol=TOLERANCE_ABSOLUTE_TESTS,
309 )
311 specification = CAM_Specification_CIECAM02(
312 *tsplit(np.reshape(specification, (2, 3, 8))).tolist()
313 )
314 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
315 XYZ = np.reshape(XYZ, (2, 3, 3))
316 np.testing.assert_allclose(
317 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
318 XYZ,
319 atol=TOLERANCE_ABSOLUTE_TESTS,
320 )
322 @ignore_numpy_errors
323 def test_domain_range_scale_CIECAM02_to_XYZ(self) -> None:
324 """
325 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition
326 domain and range scale support.
327 """
329 XYZ_i = np.array([19.01, 20.00, 21.78])
330 XYZ_w = np.array([95.05, 100.00, 108.88])
331 L_A = 318.31
332 Y_b = 20
333 surround = VIEWING_CONDITIONS_CIECAM02["Average"]
334 specification = XYZ_to_CIECAM02(XYZ_i, XYZ_w, L_A, Y_b, surround)
335 XYZ = CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
337 d_r = (
338 ("reference", 1, 1),
339 (
340 "1",
341 np.array(
342 [
343 1 / 100,
344 1 / 100,
345 1 / 360,
346 1 / 100,
347 1 / 100,
348 1 / 100,
349 1 / 400,
350 np.nan,
351 ]
352 ),
353 0.01,
354 ),
355 (
356 "100",
357 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]),
358 1,
359 ),
360 )
361 for scale, factor_a, factor_b in d_r:
362 with domain_range_scale(scale):
363 np.testing.assert_allclose(
364 CIECAM02_to_XYZ(
365 specification * factor_a,
366 XYZ_w * factor_b,
367 L_A,
368 Y_b,
369 surround,
370 ),
371 XYZ * factor_b,
372 atol=TOLERANCE_ABSOLUTE_TESTS,
373 )
375 @ignore_numpy_errors
376 def test_raise_exception_CIECAM02_to_XYZ(self) -> None:
377 """
378 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition
379 raised exception.
380 """
382 pytest.raises(
383 ValueError,
384 CIECAM02_to_XYZ,
385 CAM_Specification_CIECAM02(41.731091132513917, None, 219.04843265831178),
386 np.array([95.05, 100.00, 108.88]),
387 318.31,
388 20.0,
389 VIEWING_CONDITIONS_CIECAM02["Average"],
390 )
392 @ignore_numpy_errors
393 def test_nan_CIECAM02_to_XYZ(self) -> None:
394 """
395 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition
396 nan support.
397 """
399 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
400 cases = np.array(list(set(product(cases, repeat=3))))
401 surround = InductionFactors_CIECAM02(cases[0, 0], cases[0, 0], cases[0, 0])
402 CIECAM02_to_XYZ(
403 CAM_Specification_CIECAM02(
404 cases[..., 0], cases[..., 0], cases[..., 0], M=50
405 ),
406 cases,
407 cases[..., 0],
408 cases[..., 0],
409 surround,
410 )