Coverage for appearance/tests/test_cam16.py: 100%
131 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.cam16` module."""
3from __future__ import annotations
5from itertools import product
7import numpy as np
8import pytest
10from colour.appearance import (
11 VIEWING_CONDITIONS_CAM16,
12 CAM16_to_XYZ,
13 CAM_Specification_CAM16,
14 InductionFactors_CAM16,
15 XYZ_to_CAM16,
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_CAM16",
34 "TestCAM16_to_XYZ",
35]
38class TestXYZ_to_CAM16:
39 """
40 Define :func:`colour.appearance.cam16.XYZ_to_CAM16` definition unit
41 tests methods.
42 """
44 def test_XYZ_to_CAM16(self) -> None:
45 """Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition."""
47 XYZ = np.array([19.01, 20.00, 21.78])
48 XYZ_w = np.array([95.05, 100.00, 108.88])
49 L_A = 318.31
50 Y_b = 20
51 surround = VIEWING_CONDITIONS_CAM16["Average"]
52 np.testing.assert_allclose(
53 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
54 np.array(
55 [
56 41.73120791,
57 0.10335574,
58 217.06795977,
59 2.34501507,
60 195.37170899,
61 0.10743677,
62 275.59498615,
63 np.nan,
64 ]
65 ),
66 atol=TOLERANCE_ABSOLUTE_TESTS,
67 )
69 XYZ = np.array([57.06, 43.06, 31.96])
70 L_A = 31.83
71 np.testing.assert_allclose(
72 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
73 np.array(
74 [
75 65.42828069,
76 49.67956420,
77 17.48659243,
78 52.94308868,
79 152.06985268,
80 42.62473321,
81 398.03047943,
82 np.nan,
83 ]
84 ),
85 atol=TOLERANCE_ABSOLUTE_TESTS,
86 )
88 XYZ = np.array([3.53, 6.56, 2.14])
89 XYZ_w = np.array([109.85, 100, 35.58])
90 L_A = 318.31
91 np.testing.assert_allclose(
92 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
93 np.array(
94 [
95 21.36052893,
96 50.99381895,
97 178.86724266,
98 61.57953092,
99 139.78582768,
100 53.00732582,
101 223.01823806,
102 np.nan,
103 ]
104 ),
105 atol=TOLERANCE_ABSOLUTE_TESTS,
106 )
108 XYZ = np.array([19.01, 20.00, 21.78])
109 L_A = 318.31
110 np.testing.assert_allclose(
111 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
112 np.array(
113 [
114 41.36326063,
115 52.81154022,
116 258.88676291,
117 53.12406914,
118 194.52011798,
119 54.89682038,
120 311.24768647,
121 np.nan,
122 ]
123 ),
124 atol=TOLERANCE_ABSOLUTE_TESTS,
125 )
127 XYZ = np.array([61.45276998, 7.00421901, 82.2406738])
128 XYZ_w = np.array([95.05, 100.00, 108.88])
129 L_A = 4.074366543152521
130 np.testing.assert_allclose(
131 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
132 np.array(
133 [
134 21.03801957,
135 457.78881613,
136 350.06445098,
137 241.50642846,
138 56.74143988,
139 330.94646237,
140 376.43915877,
141 np.nan,
142 ]
143 ),
144 atol=TOLERANCE_ABSOLUTE_TESTS,
145 )
147 def test_n_dimensional_XYZ_to_CAM16(self) -> None:
148 """
149 Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition
150 n-dimensional support.
151 """
153 XYZ = np.array([19.01, 20.00, 21.78])
154 XYZ_w = np.array([95.05, 100.00, 108.88])
155 L_A = 318.31
156 Y_b = 20
157 surround = VIEWING_CONDITIONS_CAM16["Average"]
158 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)
160 XYZ = np.tile(XYZ, (6, 1))
161 specification = np.tile(specification, (6, 1))
162 np.testing.assert_allclose(
163 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
164 specification,
165 atol=TOLERANCE_ABSOLUTE_TESTS,
166 )
168 XYZ_w = np.tile(XYZ_w, (6, 1))
169 np.testing.assert_allclose(
170 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
171 specification,
172 atol=TOLERANCE_ABSOLUTE_TESTS,
173 )
175 XYZ = np.reshape(XYZ, (2, 3, 3))
176 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
177 specification = np.reshape(specification, (2, 3, 8))
178 np.testing.assert_allclose(
179 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),
180 specification,
181 atol=TOLERANCE_ABSOLUTE_TESTS,
182 )
184 @ignore_numpy_errors
185 def test_domain_range_scale_XYZ_to_CAM16(self) -> None:
186 """
187 Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition domain
188 and range scale support.
189 """
191 XYZ = np.array([19.01, 20.00, 21.78])
192 XYZ_w = np.array([95.05, 100.00, 108.88])
193 L_A = 318.31
194 Y_b = 20
195 surround = VIEWING_CONDITIONS_CAM16["Average"]
196 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)
198 d_r = (
199 ("reference", 1, 1),
200 (
201 "1",
202 0.01,
203 np.array(
204 [
205 1 / 100,
206 1 / 100,
207 1 / 360,
208 1 / 100,
209 1 / 100,
210 1 / 100,
211 1 / 400,
212 np.nan,
213 ]
214 ),
215 ),
216 (
217 "100",
218 1,
219 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]),
220 ),
221 )
222 for scale, factor_a, factor_b in d_r:
223 with domain_range_scale(scale):
224 np.testing.assert_allclose(
225 XYZ_to_CAM16(XYZ * factor_a, XYZ_w * factor_a, L_A, Y_b, surround),
226 as_float_array(specification) * factor_b,
227 atol=TOLERANCE_ABSOLUTE_TESTS,
228 )
230 @ignore_numpy_errors
231 def test_nan_XYZ_to_CAM16(self) -> None:
232 """
233 Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition
234 nan support.
235 """
237 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
238 cases = np.array(list(set(product(cases, repeat=3))))
239 surround = InductionFactors_CAM16(cases[0, 0], cases[0, 0], cases[0, 0])
240 XYZ_to_CAM16(cases, cases, cases[..., 0], cases[..., 0], surround)
243class TestCAM16_to_XYZ:
244 """
245 Define :func:`colour.appearance.cam16.CAM16_to_XYZ` definition unit tests
246 methods.
247 """
249 def test_CAM16_to_XYZ(self) -> None:
250 """Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition."""
252 specification = CAM_Specification_CAM16(41.73120791, 0.10335574, 217.06795977)
253 XYZ_w = np.array([95.05, 100.00, 108.88])
254 L_A = 318.31
255 Y_b = 20
256 surround = VIEWING_CONDITIONS_CAM16["Average"]
257 np.testing.assert_allclose(
258 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
259 np.array([19.01, 20.00, 21.78]),
260 atol=TOLERANCE_ABSOLUTE_TESTS,
261 )
263 specification = CAM_Specification_CAM16(65.42828069, 49.67956420, 17.48659243)
264 L_A = 31.83
265 np.testing.assert_allclose(
266 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
267 np.array([57.06, 43.06, 31.96]),
268 atol=TOLERANCE_ABSOLUTE_TESTS,
269 )
271 specification = CAM_Specification_CAM16(21.36052893, 50.99381895, 178.86724266)
272 XYZ_w = np.array([109.85, 100, 35.58])
273 L_A = 318.31
274 np.testing.assert_allclose(
275 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
276 np.array([3.53, 6.56, 2.14]),
277 atol=TOLERANCE_ABSOLUTE_TESTS,
278 )
280 specification = CAM_Specification_CAM16(41.36326063, 52.81154022, 258.88676291)
281 L_A = 318.31
282 np.testing.assert_allclose(
283 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
284 np.array([19.01, 20.00, 21.78]),
285 atol=TOLERANCE_ABSOLUTE_TESTS,
286 )
288 specification = CAM_Specification_CAM16(21.03801957, 457.78881613, 350.06445098)
289 XYZ_w = np.array([95.05, 100.00, 108.88])
290 L_A = 4.074366543152521
291 np.testing.assert_allclose(
292 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
293 np.array([61.45276998, 7.00421901, 82.2406738]),
294 atol=TOLERANCE_ABSOLUTE_TESTS,
295 )
297 def test_n_dimensional_CAM16_to_XYZ(self) -> None:
298 """
299 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition
300 n-dimensional support.
301 """
303 XYZ = np.array([19.01, 20.00, 21.78])
304 XYZ_w = np.array([95.05, 100.00, 108.88])
305 L_A = 318.31
306 Y_b = 20
307 surround = VIEWING_CONDITIONS_CAM16["Average"]
308 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)
309 XYZ = CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
311 specification = CAM_Specification_CAM16(
312 *np.transpose(np.tile(tsplit(specification), (6, 1))).tolist()
313 )
314 XYZ = np.tile(XYZ, (6, 1))
315 np.testing.assert_allclose(
316 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
317 XYZ,
318 atol=TOLERANCE_ABSOLUTE_TESTS,
319 )
321 XYZ_w = np.tile(XYZ_w, (6, 1))
322 np.testing.assert_allclose(
323 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
324 XYZ,
325 atol=TOLERANCE_ABSOLUTE_TESTS,
326 )
328 specification = CAM_Specification_CAM16(
329 *tsplit(np.reshape(specification, (2, 3, 8))).tolist()
330 )
331 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
332 XYZ = np.reshape(XYZ, (2, 3, 3))
333 np.testing.assert_allclose(
334 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
335 XYZ,
336 atol=TOLERANCE_ABSOLUTE_TESTS,
337 )
339 @ignore_numpy_errors
340 def test_domain_range_scale_CAM16_to_XYZ(self) -> None:
341 """
342 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition domain
343 and range scale support.
344 """
346 XYZ = np.array([19.01, 20.00, 21.78])
347 XYZ_w = np.array([95.05, 100.00, 108.88])
348 L_A = 318.31
349 Y_b = 20
350 surround = VIEWING_CONDITIONS_CAM16["Average"]
351 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)
352 XYZ = CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
354 d_r = (
355 ("reference", 1, 1),
356 (
357 "1",
358 np.array(
359 [
360 1 / 100,
361 1 / 100,
362 1 / 360,
363 1 / 100,
364 1 / 100,
365 1 / 100,
366 1 / 400,
367 np.nan,
368 ]
369 ),
370 0.01,
371 ),
372 (
373 "100",
374 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]),
375 1,
376 ),
377 )
378 for scale, factor_a, factor_b in d_r:
379 with domain_range_scale(scale):
380 np.testing.assert_allclose(
381 CAM16_to_XYZ(
382 specification * factor_a,
383 XYZ_w * factor_b,
384 L_A,
385 Y_b,
386 surround,
387 ),
388 XYZ * factor_b,
389 atol=TOLERANCE_ABSOLUTE_TESTS,
390 )
392 @ignore_numpy_errors
393 def test_raise_exception_CAM16_to_XYZ(self) -> None:
394 """
395 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition raised
396 exception.
397 """
399 pytest.raises(
400 ValueError,
401 CAM16_to_XYZ,
402 CAM_Specification_CAM16(41.731207905126638, None, 217.06795976739301),
403 np.array([95.05, 100.00, 108.88]),
404 318.31,
405 20.0,
406 VIEWING_CONDITIONS_CAM16["Average"],
407 )
409 @ignore_numpy_errors
410 def test_nan_CAM16_to_XYZ(self) -> None:
411 """
412 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition nan
413 support.
414 """
416 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
417 cases = np.array(list(set(product(cases, repeat=3))))
418 surround = InductionFactors_CAM16(cases[0, 0], cases[0, 0], cases[0, 0])
419 CAM16_to_XYZ(
420 CAM_Specification_CAM16(cases[..., 0], cases[..., 0], cases[..., 0], M=50),
421 cases,
422 cases[..., 0],
423 cases[..., 0],
424 surround,
425 )