Coverage for appearance/tests/test_ciecam16.py: 100%
141 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""Define the unit tests for the :mod:`colour.appearance.ciecam16` module."""
3from __future__ import annotations
5from itertools import product
7import numpy as np
8import pytest
10from colour.appearance import (
11 VIEWING_CONDITIONS_CIECAM16,
12 CAM_Specification_CIECAM16,
13 CIECAM16_to_XYZ,
14 InductionFactors_CIECAM16,
15 XYZ_to_CIECAM16,
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_CIECAM16",
34 "TestCIECAM16_to_XYZ",
35]
38class TestXYZ_to_CIECAM16:
39 """
40 Define :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition unit
41 tests methods.
42 """
44 def test_XYZ_to_CIECAM16(self) -> None:
45 """
46 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition.
47 """
49 XYZ = np.array([19.01, 20.00, 21.78])
50 XYZ_w = np.array([95.05, 100.00, 108.88])
51 L_A = 318.31
52 Y_b = 20
53 surround = VIEWING_CONDITIONS_CIECAM16["Average"]
54 np.testing.assert_allclose(
55 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
56 np.array(
57 [
58 41.73120791,
59 0.10335574,
60 217.06795977,
61 2.34501507,
62 195.37170899,
63 0.10743677,
64 275.59498615,
65 np.nan,
66 ]
67 ),
68 atol=TOLERANCE_ABSOLUTE_TESTS,
69 )
71 XYZ = np.array([57.06, 43.06, 31.96])
72 L_A = 31.83
73 np.testing.assert_allclose(
74 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
75 np.array(
76 [
77 65.42828069,
78 49.67956420,
79 17.48659243,
80 52.94308868,
81 152.06985268,
82 42.62473321,
83 398.03047943,
84 np.nan,
85 ]
86 ),
87 atol=TOLERANCE_ABSOLUTE_TESTS,
88 )
90 XYZ = np.array([3.53, 6.56, 2.14])
91 XYZ_w = np.array([109.85, 100, 35.58])
92 L_A = 318.31
93 np.testing.assert_allclose(
94 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
95 np.array(
96 [
97 21.36052893,
98 50.99381895,
99 178.86724266,
100 61.57953092,
101 139.78582768,
102 53.00732582,
103 223.01823806,
104 np.nan,
105 ]
106 ),
107 atol=TOLERANCE_ABSOLUTE_TESTS,
108 )
110 XYZ = np.array([19.01, 20.00, 21.78])
111 L_A = 318.31
112 np.testing.assert_allclose(
113 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
114 np.array(
115 [
116 41.36326063,
117 52.81154022,
118 258.88676291,
119 53.12406914,
120 194.52011798,
121 54.89682038,
122 311.24768647,
123 np.nan,
124 ]
125 ),
126 atol=TOLERANCE_ABSOLUTE_TESTS,
127 )
129 XYZ = np.array([61.45276998, 7.00421901, 82.2406738])
130 XYZ_w = np.array([95.05, 100.00, 108.88])
131 L_A = 4.074366543152521
132 np.testing.assert_allclose(
133 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
134 np.array(
135 [
136 2.212842606688056,
137 597.366327557872864,
138 352.035143755398565,
139 484.428915071471351,
140 18.402345804194972,
141 431.850377022773955,
142 378.267899100834541,
143 np.nan,
144 ]
145 ),
146 atol=TOLERANCE_ABSOLUTE_TESTS,
147 )
149 XYZ = np.array([60.70, 49.60, 10.29])
150 XYZ_w = np.array([96.46, 100.00, 108.62])
151 L_A = 40
152 Y_b = 16
153 np.testing.assert_allclose(
154 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
155 np.array(
156 [
157 70.4406,
158 58.6035,
159 57.9145,
160 54.5604,
161 172.1555,
162 51.2479,
163 50.7425,
164 np.nan,
165 ]
166 ),
167 atol=5e-5,
168 )
170 def test_n_dimensional_XYZ_to_CIECAM16(self) -> None:
171 """
172 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition
173 n-dimensional support.
174 """
176 XYZ = np.array([19.01, 20.00, 21.78])
177 XYZ_w = np.array([95.05, 100.00, 108.88])
178 L_A = 318.31
179 Y_b = 20
180 surround = VIEWING_CONDITIONS_CIECAM16["Average"]
181 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround)
183 XYZ = np.tile(XYZ, (6, 1))
184 specification = np.tile(specification, (6, 1))
185 np.testing.assert_allclose(
186 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
187 specification,
188 atol=TOLERANCE_ABSOLUTE_TESTS,
189 )
191 XYZ_w = np.tile(XYZ_w, (6, 1))
192 np.testing.assert_allclose(
193 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
194 specification,
195 atol=TOLERANCE_ABSOLUTE_TESTS,
196 )
198 XYZ = np.reshape(XYZ, (2, 3, 3))
199 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
200 specification = np.reshape(specification, (2, 3, 8))
201 np.testing.assert_allclose(
202 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround),
203 specification,
204 atol=TOLERANCE_ABSOLUTE_TESTS,
205 )
207 @ignore_numpy_errors
208 def test_domain_range_scale_XYZ_to_CIECAM16(self) -> None:
209 """
210 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition
211 domain and range scale support.
212 """
214 XYZ = np.array([19.01, 20.00, 21.78])
215 XYZ_w = np.array([95.05, 100.00, 108.88])
216 L_A = 318.31
217 Y_b = 20
218 surround = VIEWING_CONDITIONS_CIECAM16["Average"]
219 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround)
221 d_r = (
222 ("reference", 1, 1),
223 (
224 "1",
225 0.01,
226 np.array(
227 [
228 1 / 100,
229 1 / 100,
230 1 / 360,
231 1 / 100,
232 1 / 100,
233 1 / 100,
234 1 / 400,
235 np.nan,
236 ]
237 ),
238 ),
239 (
240 "100",
241 1,
242 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]),
243 ),
244 )
245 for scale, factor_a, factor_b in d_r:
246 with domain_range_scale(scale):
247 np.testing.assert_allclose(
248 XYZ_to_CIECAM16(
249 XYZ * factor_a, XYZ_w * factor_a, L_A, Y_b, surround
250 ),
251 as_float_array(specification) * factor_b,
252 atol=TOLERANCE_ABSOLUTE_TESTS,
253 )
255 @ignore_numpy_errors
256 def test_nan_XYZ_to_CIECAM16(self) -> None:
257 """
258 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition
259 nan support.
260 """
262 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
263 cases = np.array(list(set(product(cases, repeat=3))))
264 surround = InductionFactors_CIECAM16(cases[0, 0], cases[0, 0], cases[0, 0])
265 XYZ_to_CIECAM16(cases, cases, cases[..., 0], cases[..., 0], surround)
268class TestCIECAM16_to_XYZ:
269 """
270 Define :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition unit
271 tests methods.
272 """
274 def test_CIECAM16_to_XYZ(self) -> None:
275 """
276 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition.
277 """
279 specification = CAM_Specification_CIECAM16(
280 41.73120791, 0.10335574, 217.06795977
281 )
282 XYZ_w = np.array([95.05, 100.00, 108.88])
283 L_A = 318.31
284 Y_b = 20
285 surround = VIEWING_CONDITIONS_CIECAM16["Average"]
286 np.testing.assert_allclose(
287 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
288 np.array([19.01, 20.00, 21.78]),
289 atol=TOLERANCE_ABSOLUTE_TESTS,
290 )
292 specification = CAM_Specification_CIECAM16(
293 65.42828069, 49.67956420, 17.48659243
294 )
295 L_A = 31.83
296 np.testing.assert_allclose(
297 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
298 np.array([57.06, 43.06, 31.96]),
299 atol=TOLERANCE_ABSOLUTE_TESTS,
300 )
302 specification = CAM_Specification_CIECAM16(
303 21.36052893, 50.99381895, 178.86724266
304 )
305 XYZ_w = np.array([109.85, 100, 35.58])
306 L_A = 318.31
307 np.testing.assert_allclose(
308 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
309 np.array([3.53, 6.56, 2.14]),
310 atol=TOLERANCE_ABSOLUTE_TESTS,
311 )
313 specification = CAM_Specification_CIECAM16(
314 41.36326063, 52.81154022, 258.88676291
315 )
316 L_A = 318.31
317 np.testing.assert_allclose(
318 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
319 np.array([19.01, 20.00, 21.78]),
320 atol=TOLERANCE_ABSOLUTE_TESTS,
321 )
323 specification = CAM_Specification_CIECAM16(
324 2.212842606688056, 597.366327557872864, 352.035143755398565
325 )
326 XYZ_w = np.array([95.05, 100.00, 108.88])
327 L_A = 4.074366543152521
328 np.testing.assert_allclose(
329 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
330 np.array([61.45276998, 7.00421901, 82.2406738]),
331 atol=TOLERANCE_ABSOLUTE_TESTS,
332 )
334 specification = CAM_Specification_CIECAM16(70.4406, 58.6035, 57.9145)
335 XYZ_w = np.array([96.46, 100.00, 108.62])
336 L_A = 40
337 Y_b = 16
338 np.testing.assert_allclose(
339 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
340 np.array([60.70, 49.60, 10.29]),
341 atol=1e-4,
342 )
344 def test_n_dimensional_CIECAM16_to_XYZ(self) -> None:
345 """
346 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition
347 n-dimensional support.
348 """
350 XYZ = np.array([19.01, 20.00, 21.78])
351 XYZ_w = np.array([95.05, 100.00, 108.88])
352 L_A = 318.31
353 Y_b = 20
354 surround = VIEWING_CONDITIONS_CIECAM16["Average"]
355 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround)
356 XYZ = CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
358 specification = CAM_Specification_CIECAM16(
359 *np.transpose(np.tile(tsplit(specification), (6, 1))).tolist()
360 )
361 XYZ = np.tile(XYZ, (6, 1))
362 np.testing.assert_allclose(
363 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
364 XYZ,
365 atol=TOLERANCE_ABSOLUTE_TESTS,
366 )
368 XYZ_w = np.tile(XYZ_w, (6, 1))
369 np.testing.assert_allclose(
370 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
371 XYZ,
372 atol=TOLERANCE_ABSOLUTE_TESTS,
373 )
375 specification = CAM_Specification_CIECAM16(
376 *tsplit(np.reshape(specification, (2, 3, 8))).tolist()
377 )
378 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
379 XYZ = np.reshape(XYZ, (2, 3, 3))
380 np.testing.assert_allclose(
381 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
382 XYZ,
383 atol=TOLERANCE_ABSOLUTE_TESTS,
384 )
386 @ignore_numpy_errors
387 def test_domain_range_scale_CIECAM16_to_XYZ(self) -> None:
388 """
389 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition
390 domain and range scale support.
391 """
393 XYZ = np.array([19.01, 20.00, 21.78])
394 XYZ_w = np.array([95.05, 100.00, 108.88])
395 L_A = 318.31
396 Y_b = 20
397 surround = VIEWING_CONDITIONS_CIECAM16["Average"]
398 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround)
399 XYZ = CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
401 d_r = (
402 ("reference", 1, 1),
403 (
404 "1",
405 np.array(
406 [
407 1 / 100,
408 1 / 100,
409 1 / 360,
410 1 / 100,
411 1 / 100,
412 1 / 100,
413 1 / 400,
414 np.nan,
415 ]
416 ),
417 0.01,
418 ),
419 (
420 "100",
421 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]),
422 1,
423 ),
424 )
425 for scale, factor_a, factor_b in d_r:
426 with domain_range_scale(scale):
427 np.testing.assert_allclose(
428 CIECAM16_to_XYZ(
429 specification * factor_a,
430 XYZ_w * factor_b,
431 L_A,
432 Y_b,
433 surround,
434 ),
435 XYZ * factor_b,
436 atol=TOLERANCE_ABSOLUTE_TESTS,
437 )
439 @ignore_numpy_errors
440 def test_raise_exception_CIECAM16_to_XYZ(self) -> None:
441 """
442 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition
443 raised exception.
444 """
446 pytest.raises(
447 ValueError,
448 CIECAM16_to_XYZ,
449 CAM_Specification_CIECAM16(41.731207905126638, None, 217.06795976739301),
450 np.array([95.05, 100.00, 108.88]),
451 318.31,
452 20.0,
453 VIEWING_CONDITIONS_CIECAM16["Average"],
454 )
456 @ignore_numpy_errors
457 def test_nan_CIECAM16_to_XYZ(self) -> None:
458 """
459 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition nan
460 support.
461 """
463 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
464 cases = np.array(list(set(product(cases, repeat=3))))
465 surround = InductionFactors_CIECAM16(cases[0, 0], cases[0, 0], cases[0, 0])
466 CIECAM16_to_XYZ(
467 CAM_Specification_CIECAM16(
468 cases[..., 0], cases[..., 0], cases[..., 0], M=50
469 ),
470 cases,
471 cases[..., 0],
472 cases[..., 0],
473 surround,
474 )