Coverage for colour/appearance/tests/test_hellwig2022.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"""
2Define the unit tests for the :mod:`colour.appearance.hellwig2022` module.
4References
5----------
6- :cite:`Fairchild2022` : Fairchild, M. D., & Hellwig, L. (2022). Private
7 Discussion with Mansencal, T.
8"""
10from __future__ import annotations
12from itertools import product
14import numpy as np
15import pytest
17from colour.appearance import (
18 VIEWING_CONDITIONS_HELLWIG2022,
19 CAM_Specification_Hellwig2022,
20 Hellwig2022_to_XYZ,
21 InductionFactors_Hellwig2022,
22 XYZ_to_Hellwig2022,
23)
24from colour.constants import TOLERANCE_ABSOLUTE_TESTS
25from colour.utilities import (
26 as_float_array,
27 domain_range_scale,
28 ignore_numpy_errors,
29 tsplit,
30)
32__author__ = "Colour Developers"
33__copyright__ = "Copyright 2013 Colour Developers"
34__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
35__maintainer__ = "Colour Developers"
36__email__ = "colour-developers@colour-science.org"
37__status__ = "Production"
39__all__ = [
40 "TestXYZ_to_Hellwig2022",
41 "TestHellwig2022_to_XYZ",
42]
45class TestXYZ_to_Hellwig2022:
46 """
47 Define :func:`colour.appearance.hellwig2022.XYZ_to_Hellwig2022` definition
48 unit tests methods.
49 """
51 def test_XYZ_to_Hellwig2022(self) -> None:
52 """
53 Test :func:`colour.appearance.hellwig2022.XYZ_to_Hellwig2022`
54 definition.
55 """
57 XYZ = np.array([19.01, 20.00, 21.78])
58 XYZ_w = np.array([95.05, 100.00, 108.88])
59 L_A = 318.31
60 Y_b = 20
61 surround = VIEWING_CONDITIONS_HELLWIG2022["Average"]
62 np.testing.assert_allclose(
63 XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround),
64 np.array(
65 [
66 41.731,
67 0.026,
68 217.068,
69 0.061,
70 55.852,
71 0.034,
72 275.59498615,
73 np.nan,
74 41.88027828,
75 56.05183586,
76 ]
77 ),
78 atol=0.01,
79 )
81 XYZ = np.array([57.06, 43.06, 31.96])
82 L_A = 31.83
83 np.testing.assert_allclose(
84 XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround),
85 np.array(
86 [
87 65.428,
88 31.330,
89 17.487,
90 47.200,
91 64.077,
92 30.245,
93 398.03047943,
94 np.nan,
95 70.50187436,
96 69.04574688,
97 ]
98 ),
99 atol=0.01,
100 )
102 XYZ = np.array([3.53, 6.56, 2.14])
103 XYZ_w = np.array([109.85, 100, 35.58])
104 L_A = 318.31
105 np.testing.assert_allclose(
106 XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround),
107 np.array(
108 [
109 21.361,
110 30.603,
111 178.867,
112 141.223,
113 28.590,
114 40.376,
115 223.01823806,
116 np.nan,
117 29.35191711,
118 39.28664523,
119 ]
120 ),
121 atol=0.01,
122 )
124 XYZ = np.array([19.01, 20.00, 21.78])
125 XYZ_w = np.array([109.85, 100.00, 35.58])
126 L_A = 31.38
127 np.testing.assert_allclose(
128 XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround),
129 np.array(
130 [
131 41.064050542871215,
132 31.939561618552826,
133 259.034056616436715,
134 76.668720573462167,
135 40.196783565499423,
136 30.818359671352116,
137 311.329371306428470,
138 np.nan,
139 49.676917719967385,
140 48.627748198047854,
141 ]
142 ),
143 atol=0.01,
144 )
146 def test_n_dimensional_XYZ_to_Hellwig2022(self) -> None:
147 """
148 Test :func:`colour.appearance.hellwig2022.XYZ_to_Hellwig2022` definition
149 n-dimensional support.
150 """
152 XYZ = np.array([19.01, 20.00, 21.78])
153 XYZ_w = np.array([95.05, 100.00, 108.88])
154 L_A = 318.31
155 Y_b = 20
156 surround = VIEWING_CONDITIONS_HELLWIG2022["Average"]
157 specification = XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround)
159 XYZ = np.tile(XYZ, (6, 1))
160 specification = np.tile(specification, (6, 1))
161 np.testing.assert_allclose(
162 XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround),
163 specification,
164 atol=TOLERANCE_ABSOLUTE_TESTS,
165 )
167 XYZ_w = np.tile(XYZ_w, (6, 1))
168 np.testing.assert_allclose(
169 XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround),
170 specification,
171 atol=TOLERANCE_ABSOLUTE_TESTS,
172 )
174 XYZ = np.reshape(XYZ, (2, 3, 3))
175 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
176 specification = np.reshape(specification, (2, 3, 10))
177 np.testing.assert_allclose(
178 XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround),
179 specification,
180 atol=TOLERANCE_ABSOLUTE_TESTS,
181 )
183 @ignore_numpy_errors
184 def test_domain_range_scale_XYZ_to_Hellwig2022(self) -> None:
185 """
186 Test :func:`colour.appearance.hellwig2022.XYZ_to_Hellwig2022`
187 definition domain and range scale support.
188 """
190 XYZ = np.array([19.01, 20.00, 21.78])
191 XYZ_w = np.array([95.05, 100.00, 108.88])
192 L_A = 318.31
193 Y_b = 20
194 surround = VIEWING_CONDITIONS_HELLWIG2022["Average"]
195 specification = XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround)
197 d_r = (
198 ("reference", 1, 1),
199 (
200 "1",
201 0.01,
202 np.array(
203 [
204 1 / 100,
205 1 / 100,
206 1 / 360,
207 1 / 100,
208 1 / 100,
209 1 / 100,
210 1 / 400,
211 np.nan,
212 1 / 100,
213 1 / 100,
214 ]
215 ),
216 ),
217 (
218 "100",
219 1,
220 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan, 1, 1]),
221 ),
222 )
223 for scale, factor_a, factor_b in d_r:
224 with domain_range_scale(scale):
225 np.testing.assert_allclose(
226 XYZ_to_Hellwig2022(
227 XYZ * factor_a, XYZ_w * factor_a, L_A, Y_b, surround
228 ),
229 as_float_array(specification) * factor_b,
230 atol=TOLERANCE_ABSOLUTE_TESTS,
231 )
233 @ignore_numpy_errors
234 def test_nan_XYZ_to_Hellwig2022(self) -> None:
235 """
236 Test :func:`colour.appearance.hellwig2022.XYZ_to_Hellwig2022
237 definition nan support.
238 """
240 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
241 cases = np.array(list(set(product(cases, repeat=3))))
242 surround = InductionFactors_Hellwig2022(cases[0, 0], cases[0, 0], cases[0, 0])
243 XYZ_to_Hellwig2022(cases, cases, cases[..., 0], cases[..., 0], surround)
246class TestHellwig2022_to_XYZ:
247 """
248 Define :func:`colour.appearance.hellwig2022.Hellwig2022_to_XYZ` definition
249 unit tests methods.
250 """
252 def test_Hellwig2022_to_XYZ(self) -> None:
253 """
254 Test :func:`colour.appearance.hellwig2022.Hellwig2022_to_XYZ`
255 definition.
256 """
258 specification = CAM_Specification_Hellwig2022(
259 41.731207905126638, 0.025763615829912909, 217.06795976739301
260 )
261 XYZ_w = np.array([95.05, 100.00, 108.88])
262 L_A = 318.31
263 Y_b = 20
264 surround = VIEWING_CONDITIONS_HELLWIG2022["Average"]
265 np.testing.assert_allclose(
266 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
267 np.array([19.01, 20.00, 21.78]),
268 atol=TOLERANCE_ABSOLUTE_TESTS,
269 )
271 specification = CAM_Specification_Hellwig2022(
272 65.428280687118473, 31.330032520870901, 17.486592427576902
273 )
274 L_A = 31.83
275 np.testing.assert_allclose(
276 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
277 np.array([57.06, 43.06, 31.96]),
278 atol=TOLERANCE_ABSOLUTE_TESTS,
279 )
281 specification = CAM_Specification_Hellwig2022(
282 21.360528925833027, 30.603219780800902, 178.8672426588991
283 )
284 XYZ_w = np.array([109.85, 100, 35.58])
285 L_A = 318.31
286 np.testing.assert_allclose(
287 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
288 np.array([3.53, 6.56, 2.14]),
289 atol=TOLERANCE_ABSOLUTE_TESTS,
290 )
292 specification = CAM_Specification_Hellwig2022(
293 41.064050542871215, 31.939561618552826, 259.03405661643671
294 )
295 L_A = 31.38
296 np.testing.assert_allclose(
297 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
298 np.array([19.01, 20.00, 21.78]),
299 atol=TOLERANCE_ABSOLUTE_TESTS,
300 )
302 specification = CAM_Specification_Hellwig2022(
303 J_HK=41.880278283880095, C=0.025763615829913, h=217.067959767393010
304 )
305 XYZ_w = np.array([95.05, 100.00, 108.88])
306 L_A = 318.31
307 Y_b = 20
308 surround = VIEWING_CONDITIONS_HELLWIG2022["Average"]
309 np.testing.assert_allclose(
310 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
311 np.array([19.01, 20.00, 21.78]),
312 atol=TOLERANCE_ABSOLUTE_TESTS,
313 )
315 def test_n_dimensional_Hellwig2022_to_XYZ(self) -> None:
316 """
317 Test :func:`colour.appearance.hellwig2022.Hellwig2022_to_XYZ`
318 definition n-dimensional support.
319 """
321 XYZ = np.array([19.01, 20.00, 21.78])
322 XYZ_w = np.array([95.05, 100.00, 108.88])
323 L_A = 318.31
324 Y_b = 20
325 surround = VIEWING_CONDITIONS_HELLWIG2022["Average"]
326 specification = XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround)
327 XYZ = Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
329 specification = CAM_Specification_Hellwig2022(
330 *np.transpose(np.tile(tsplit(specification), (6, 1))).tolist()
331 )
332 XYZ = np.tile(XYZ, (6, 1))
333 np.testing.assert_allclose(
334 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
335 XYZ,
336 atol=TOLERANCE_ABSOLUTE_TESTS,
337 )
339 XYZ_w = np.tile(XYZ_w, (6, 1))
340 np.testing.assert_allclose(
341 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
342 XYZ,
343 atol=TOLERANCE_ABSOLUTE_TESTS,
344 )
346 specification = CAM_Specification_Hellwig2022(
347 *tsplit(np.reshape(specification, (2, 3, 10))).tolist()
348 )
349 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
350 XYZ = np.reshape(XYZ, (2, 3, 3))
351 np.testing.assert_allclose(
352 Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),
353 XYZ,
354 atol=TOLERANCE_ABSOLUTE_TESTS,
355 )
357 @ignore_numpy_errors
358 def test_domain_range_scale_Hellwig2022_to_XYZ(self) -> None:
359 """
360 Test :func:`colour.appearance.hellwig2022.Hellwig2022_to_XYZ`
361 definition domain and range scale support.
362 """
364 XYZ = np.array([19.01, 20.00, 21.78])
365 XYZ_w = np.array([95.05, 100.00, 108.88])
366 L_A = 318.31
367 Y_b = 20
368 surround = VIEWING_CONDITIONS_HELLWIG2022["Average"]
369 specification = XYZ_to_Hellwig2022(XYZ, XYZ_w, L_A, Y_b, surround)
370 XYZ = Hellwig2022_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)
372 d_r = (
373 ("reference", 1, 1),
374 (
375 "1",
376 np.array(
377 [
378 1 / 100,
379 1 / 100,
380 1 / 360,
381 1 / 100,
382 1 / 100,
383 1 / 100,
384 1 / 400,
385 np.nan,
386 1 / 100,
387 1 / 100,
388 ]
389 ),
390 0.01,
391 ),
392 (
393 "100",
394 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan, 1, 1]),
395 1,
396 ),
397 )
398 for scale, factor_a, factor_b in d_r:
399 with domain_range_scale(scale):
400 np.testing.assert_allclose(
401 Hellwig2022_to_XYZ(
402 specification * factor_a,
403 XYZ_w * factor_b,
404 L_A,
405 Y_b,
406 surround,
407 ),
408 XYZ * factor_b,
409 atol=TOLERANCE_ABSOLUTE_TESTS,
410 )
412 @ignore_numpy_errors
413 def test_raise_exception_Hellwig2022_to_XYZ(self) -> None:
414 """
415 Test :func:`colour.appearance.hellwig2022.Hellwig2022_to_XYZ`
416 definition raised exception.
417 """
418 pytest.raises(
419 ValueError,
420 Hellwig2022_to_XYZ,
421 CAM_Specification_Hellwig2022(
422 J_HK=None, C=0.025763615829912909, h=217.06795976739301
423 ),
424 np.array([95.05, 100.00, 108.88]),
425 318.31,
426 20.0,
427 VIEWING_CONDITIONS_HELLWIG2022["Average"],
428 )
430 pytest.raises(
431 ValueError,
432 Hellwig2022_to_XYZ,
433 CAM_Specification_Hellwig2022(41.731207905126638, None, 217.06795976739301),
434 np.array([95.05, 100.00, 108.88]),
435 318.31,
436 20.0,
437 VIEWING_CONDITIONS_HELLWIG2022["Average"],
438 )
440 @ignore_numpy_errors
441 def test_nan_Hellwig2022_to_XYZ(self) -> None:
442 """
443 Test :func:`colour.appearance.hellwig2022.Hellwig2022_to_XYZ`
444 definition nan support.
445 """
447 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
448 cases = np.array(list(set(product(cases, repeat=3))))
449 surround = InductionFactors_Hellwig2022(cases[0, 0], cases[0, 0], cases[0, 0])
450 Hellwig2022_to_XYZ(
451 CAM_Specification_Hellwig2022(
452 cases[..., 0], cases[..., 0], cases[..., 0], M=50
453 ),
454 cases,
455 cases[..., 0],
456 cases[..., 0],
457 surround,
458 )