Coverage for appearance/tests/test_hunt.py: 100%
95 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.hunt` module."""
3from __future__ import annotations
5import contextlib
6from itertools import product
8import numpy as np
10from colour.appearance import (
11 VIEWING_CONDITIONS_HUNT,
12 InductionFactors_Hunt,
13 XYZ_to_Hunt,
14)
15from colour.constants import TOLERANCE_ABSOLUTE_TESTS
16from colour.utilities import as_float_array, domain_range_scale, ignore_numpy_errors
18__author__ = "Colour Developers"
19__copyright__ = "Copyright 2013 Colour Developers"
20__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
21__maintainer__ = "Colour Developers"
22__email__ = "colour-developers@colour-science.org"
23__status__ = "Production"
25__all__ = [
26 "TestXYZ_to_Hunt",
27]
30class TestXYZ_to_Hunt:
31 """
32 Define :func:`colour.appearance.hunt.XYZ_to_Hunt` definition unit tests
33 methods.
34 """
36 def test_XYZ_to_Hunt(self) -> None:
37 """
38 Test :func:`colour.appearance.hunt.XYZ_to_Hunt` definition.
40 Notes
41 -----
42 - The test values have been generated from data of the following file
43 by *Fairchild (2013)*:
44 http://rit-mcsl.org/fairchild//files/AppModEx.xls
45 """
47 XYZ = np.array([19.01, 20.00, 21.78])
48 XYZ_w = np.array([95.05, 100.00, 108.88])
49 XYZ_b = XYZ_w * np.array([1, 0.2, 1])
50 L_A = 318.31
51 surround = VIEWING_CONDITIONS_HUNT["Normal Scenes"]
52 CCT_w = 6504.0
53 np.testing.assert_allclose(
54 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w),
55 np.array([42.12, 0.16, 269.3, 0.03, 31.92, 0.16, np.nan, np.nan]),
56 atol=0.05,
57 )
59 XYZ = np.array([57.06, 43.06, 31.96])
60 L_A = 31.83
61 np.testing.assert_allclose(
62 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w),
63 np.array([66.76, 63.89, 18.6, 153.36, 31.22, 58.28, np.nan, np.nan]),
64 atol=0.05,
65 )
67 XYZ = np.array([3.53, 6.56, 2.14])
68 XYZ_w = np.array([109.85, 100.00, 35.58])
69 L_A = 318.31
70 CCT_w = 2856
71 np.testing.assert_allclose(
72 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w),
73 np.array([19.56, 74.58, 178.3, 245.4, 18.9, 76.33, np.nan, np.nan]),
74 atol=0.05,
75 )
77 XYZ = np.array([19.01, 20.00, 21.78])
78 L_A = 31.83
79 np.testing.assert_allclose(
80 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w),
81 np.array([40.27, 73.84, 262.8, 209.29, 22.15, 67.35, np.nan, np.nan]),
82 atol=0.05,
83 )
85 def test_n_dimensional_XYZ_to_Hunt(self) -> None:
86 """
87 Test :func:`colour.appearance.hunt.XYZ_to_Hunt` definition
88 n-dimensional support.
89 """
91 XYZ = np.array([19.01, 20.00, 21.78])
92 XYZ_w = np.array([95.05, 100.00, 108.88])
93 XYZ_b = XYZ_w * np.array([1, 0.2, 1])
94 L_A = 318.31
95 surround = VIEWING_CONDITIONS_HUNT["Normal Scenes"]
96 CCT_w = 6504.0
97 specification = XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w)
99 XYZ = np.tile(XYZ, (6, 1))
100 specification = np.tile(specification, (6, 1))
101 np.testing.assert_allclose(
102 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w),
103 specification,
104 atol=TOLERANCE_ABSOLUTE_TESTS,
105 )
107 XYZ_w = np.tile(XYZ_w, (6, 1))
108 XYZ_b = np.tile(XYZ_b, (6, 1))
109 np.testing.assert_allclose(
110 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w),
111 specification,
112 atol=TOLERANCE_ABSOLUTE_TESTS,
113 )
115 XYZ = np.reshape(XYZ, (2, 3, 3))
116 XYZ_w = np.reshape(XYZ_w, (2, 3, 3))
117 XYZ_b = np.reshape(XYZ_b, (2, 3, 3))
118 specification = np.reshape(specification, (2, 3, 8))
119 np.testing.assert_allclose(
120 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w),
121 specification,
122 atol=TOLERANCE_ABSOLUTE_TESTS,
123 )
125 def test_domain_range_scale_XYZ_to_Hunt(self) -> None:
126 """
127 Test :func:`colour.appearance.hunt.XYZ_to_Hunt` definition domain
128 and range scale support.
129 """
131 XYZ = np.array([19.01, 20.00, 21.78])
132 XYZ_w = np.array([95.05, 100.00, 108.88])
133 XYZ_b = np.array([95.05, 100.00, 108.88])
134 L_A = 318.31
135 surround = VIEWING_CONDITIONS_HUNT["Normal Scenes"]
136 CCT_w = 6504.0
137 specification = XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w)
139 d_r = (
140 ("reference", 1, 1),
141 ("1", 0.01, np.array([1, 1, 1 / 360, 1, 1, 1, np.nan, np.nan])),
142 ("100", 1, np.array([1, 1, 100 / 360, 1, 1, 1, np.nan, np.nan])),
143 )
144 for scale, factor_a, factor_b in d_r:
145 with domain_range_scale(scale):
146 np.testing.assert_allclose(
147 XYZ_to_Hunt(
148 XYZ * factor_a,
149 XYZ_w * factor_a,
150 XYZ_b * factor_a,
151 L_A,
152 surround,
153 CCT_w=CCT_w,
154 ),
155 as_float_array(specification) * factor_b,
156 atol=TOLERANCE_ABSOLUTE_TESTS,
157 )
159 @ignore_numpy_errors
160 def test_raise_exception_XYZ_to_Hunt(self) -> None:
161 """
162 Test :func:`colour.appearance.hunt.XYZ_to_Hunt` definition raised
163 exception.
164 """
166 XYZ = np.array([19.01, 20.00, 21.78])
167 XYZ_w = np.array([95.05, 100.00, 108.88])
168 XYZ_b = np.array([95.05, 100.00, 108.88])
169 L_A = 318.31
170 surround = VIEWING_CONDITIONS_HUNT["Normal Scenes"]
171 CCT_w = 6504.0
172 S = S_w = 0.5
174 with contextlib.suppress(ValueError):
175 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround)
177 with contextlib.suppress(ValueError):
178 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w, S=S)
180 with contextlib.suppress(ValueError):
181 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w, S_w=S_w)
183 @ignore_numpy_errors
184 def test_XYZ_p_XYZ_to_Hunt(self) -> None:
185 """
186 Test :func:`colour.appearance.hunt.XYZ_to_Hunt` definition *XYZ_p*
187 argument handling.
188 """
190 XYZ = np.array([19.01, 20.00, 21.78])
191 XYZ_w = np.array([95.05, 100.00, 108.88])
192 XYZ_b = XYZ_p = np.array([95.05, 100.00, 108.88])
193 L_A = 318.31
194 surround = VIEWING_CONDITIONS_HUNT["Normal Scenes"]
195 CCT_w = 6504.0
197 np.testing.assert_allclose(
198 XYZ_to_Hunt(
199 XYZ,
200 XYZ_w,
201 XYZ_b,
202 L_A,
203 surround,
204 XYZ_p=XYZ_p,
205 CCT_w=CCT_w,
206 ),
207 np.array(
208 [
209 30.046267861960700,
210 0.121050839936350,
211 269.273759446144600,
212 0.019909320692942,
213 22.209765491265024,
214 0.123896438259997,
215 np.nan,
216 np.nan,
217 ]
218 ),
219 atol=TOLERANCE_ABSOLUTE_TESTS,
220 )
222 @ignore_numpy_errors
223 def test_nan_XYZ_to_Hunt(self) -> None:
224 """
225 Test :func:`colour.appearance.hunt.XYZ_to_Hunt` definition
226 nan support.
227 """
229 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
230 cases = np.array(list(set(product(cases, repeat=3))))
231 surround = InductionFactors_Hunt(cases[0, 0], cases[0, 0])
232 XYZ_to_Hunt(
233 cases,
234 cases,
235 cases,
236 cases[0, 0],
237 surround,
238 cases[0, 0],
239 CCT_w=cases,
240 )