Coverage for io/tests/test_fichet2021.py: 100%
144 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.io.fichet2021` module."""
3from __future__ import annotations
5import os
6import shutil
7import tempfile
9import numpy as np
11from colour.characterisation import SDS_COLOURCHECKERS
12from colour.colorimetry import (
13 MSDS_CMFS,
14 SDS_ILLUMINANTS,
15 SpectralShape,
16 sds_and_msds_to_msds,
17)
18from colour.constants import CONSTANT_LIGHT_SPEED, TOLERANCE_ABSOLUTE_TESTS
19from colour.hints import NDArrayFloat, cast
20from colour.io import (
21 Specification_Fichet2021,
22 read_spectral_image_Fichet2021,
23 sd_to_spectrum_attribute_Fichet2021,
24 spectrum_attribute_to_sd_Fichet2021,
25 write_spectral_image_Fichet2021,
26)
27from colour.io.fichet2021 import (
28 components_to_sRGB_Fichet2021,
29 match_groups_to_nm,
30 sds_and_msds_to_components_Fichet2021,
31)
33__author__ = "Colour Developers"
34__copyright__ = "Copyright 2013 Colour Developers"
35__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
36__maintainer__ = "Colour Developers"
37__email__ = "colour-developers@colour-science.org"
38__status__ = "Production"
40__all__ = [
41 "ROOT_RESOURCES",
42 "TestMatchGroupsToNm",
43 "TestSdToSpectrumAttributeFichet2021",
44 "TestSpectrumAttributeToSdFichet2021",
45 "TestSdsAndMsdsToComponentsFichet2021",
46 "TestComponentsToSRGBFichet2021",
47 "TestReadSpectralImageFichet2021",
48 "TestWriteSpectralImageFichet2021",
49]
51ROOT_RESOURCES: str = os.path.join(os.path.dirname(__file__), "resources")
54class TestMatchGroupsToNm:
55 """
56 Define :func:`colour.io.fichet2021.match_groups_to_nm` definition unit
57 tests methods.
58 """
60 def test_match_groups_to_nm(self) -> None:
61 """Test :func:`colour.io.fichet2021.match_groups_to_nm` definition."""
63 np.testing.assert_allclose(
64 match_groups_to_nm("555.5", "n", "m"),
65 555.5,
66 atol=TOLERANCE_ABSOLUTE_TESTS,
67 )
69 np.testing.assert_allclose(
70 match_groups_to_nm("555.5", "", "m"),
71 555500000000.0,
72 atol=TOLERANCE_ABSOLUTE_TESTS,
73 )
75 np.testing.assert_allclose(
76 match_groups_to_nm(str(CONSTANT_LIGHT_SPEED / (555 * 1e-9)), "", "Hz"),
77 555.0,
78 atol=TOLERANCE_ABSOLUTE_TESTS,
79 )
82class TestSdToSpectrumAttributeFichet2021:
83 """
84 Define :func:`colour.io.fichet2021.sd_to_spectrum_attribute_Fichet2021`
85 definition unit tests methods.
86 """
88 def test_sd_to_spectrum_attribute_Fichet2021(self) -> None:
89 """
90 Test :func:`colour.io.fichet2021.\
91sd_to_spectrum_attribute_Fichet2021` definition.
92 """
94 assert (
95 sd_to_spectrum_attribute_Fichet2021(SDS_ILLUMINANTS["D65"], 2)[:56]
96 == "300.00nm:0.03;305.00nm:1.66;310.00nm:3.29;315.00nm:11.77"
97 )
100class TestSpectrumAttributeToSdFichet2021:
101 """
102 Define :func:`colour.io.fichet2021.spectrum_attribute_to_sd_Fichet2021`
103 definition unit tests methods.
104 """
106 def test_spectrum_attribute_to_sd_Fichet2021(self) -> None:
107 """
108 Test :func:`colour.io.fichet2021.\
109spectrum_attribute_to_sd_Fichet2021` definition.
110 """
112 sd = spectrum_attribute_to_sd_Fichet2021(
113 "300.00nm:0.03;305.00nm:1.66;310.00nm:3.29;315.00nm:11.77"
114 )
116 np.testing.assert_allclose(
117 sd.wavelengths,
118 np.array([300.0, 305.0, 310.0, 315.0]),
119 atol=TOLERANCE_ABSOLUTE_TESTS,
120 )
122 np.testing.assert_allclose(
123 sd.values,
124 np.array([0.03, 1.66, 3.29, 11.77]),
125 atol=TOLERANCE_ABSOLUTE_TESTS,
126 )
129class TestSdsAndMsdsToComponentsFichet2021:
130 """
131 Define :func:`colour.io.fichet2021.sds_and_msds_to_components_Fichet2021`
132 definition unit tests methods.
133 """
135 def test_sds_and_msds_to_components_Fichet2021(self) -> None:
136 """
137 Test :func:`colour.io.fichet2021.\
138sds_and_msds_to_components_Fichet2021` definition.
139 """
141 components = sds_and_msds_to_components_Fichet2021(SDS_ILLUMINANTS["D65"])
143 assert "T" in components
145 components = sds_and_msds_to_components_Fichet2021(
146 SDS_ILLUMINANTS["D65"], Specification_Fichet2021(is_emissive=True)
147 )
149 assert "S0" in components
151 np.testing.assert_allclose(
152 components["S0"][0],
153 SDS_ILLUMINANTS["D65"].wavelengths,
154 atol=TOLERANCE_ABSOLUTE_TESTS,
155 )
157 np.testing.assert_allclose(
158 components["S0"][1],
159 np.reshape(SDS_ILLUMINANTS["D65"].values, (1, 1, -1)),
160 atol=TOLERANCE_ABSOLUTE_TESTS,
161 )
163 components = sds_and_msds_to_components_Fichet2021(
164 list(SDS_COLOURCHECKERS["ColorChecker N Ohta"].values())
165 )
167 assert components["T"][1].shape == (1, 24, 81)
170class TestComponentsToSRGBFichet2021:
171 """
172 Define :func:`colour.io.fichet2021.components_to_sRGB_Fichet2021`
173 definition unit tests methods.
174 """
176 def test_components_to_sRGB_Fichet2021(self) -> None:
177 """
178 Test :func:`colour.io.fichet2021.components_to_sRGB_Fichet2021`
179 definition.
180 """
182 specification = Specification_Fichet2021(is_emissive=True)
183 components = sds_and_msds_to_components_Fichet2021(
184 SDS_ILLUMINANTS["D65"], specification
185 )
186 RGB, attributes = components_to_sRGB_Fichet2021(components, specification)
188 np.testing.assert_allclose(
189 cast("NDArrayFloat", RGB),
190 np.array([[[0.17998291, 0.18000802, 0.18000908]]]),
191 atol=TOLERANCE_ABSOLUTE_TESTS,
192 )
194 assert [attribute.name for attribute in attributes] == [
195 "X",
196 "Y",
197 "Z",
198 "illuminant",
199 "chromaticities",
200 "EV",
201 ]
203 for attribute in attributes:
204 if attribute.name == "X":
205 sd_X = spectrum_attribute_to_sd_Fichet2021(attribute.value)
206 np.testing.assert_allclose(
207 sd_X.values,
208 MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]
209 .signals["x_bar"]
210 .values,
211 atol=TOLERANCE_ABSOLUTE_TESTS,
212 )
213 elif attribute.name == "illuminant":
214 sd_illuminant = spectrum_attribute_to_sd_Fichet2021(attribute.value)
215 np.testing.assert_allclose(
216 sd_illuminant.values,
217 SDS_ILLUMINANTS["E"].values,
218 atol=TOLERANCE_ABSOLUTE_TESTS,
219 )
220 elif attribute.name == "chromaticities":
221 assert attribute.value == [
222 0.64,
223 0.33,
224 0.3,
225 0.6,
226 0.15,
227 0.06,
228 0.3127,
229 0.329,
230 ]
232 specification = Specification_Fichet2021(is_emissive=False)
233 components = sds_and_msds_to_components_Fichet2021(
234 list(SDS_COLOURCHECKERS["ColorChecker N Ohta"].values()), specification
235 )
236 RGB, attributes = components_to_sRGB_Fichet2021(components, specification)
238 np.testing.assert_allclose(
239 cast("NDArrayFloat", RGB),
240 np.array(
241 [
242 [
243 [0.17617566, 0.07822266, 0.05031637],
244 [0.55943028, 0.30875974, 0.22283237],
245 [0.11315875, 0.19922170, 0.33614049],
246 [0.09458646, 0.14840988, 0.04988729],
247 [0.23628263, 0.22587419, 0.44382286],
248 [0.13383963, 0.51702099, 0.40286142],
249 [0.70140973, 0.19925074, 0.02292392],
250 [0.06838428, 0.10600215, 0.37710859],
251 [0.55811797, 0.09062764, 0.12199424],
252 [0.10779019, 0.04434715, 0.14682113],
253 [0.34888054, 0.50195490, 0.04773998],
254 [0.79166868, 0.36502900, 0.02678776],
255 [0.02722027, 0.04781536, 0.30913913],
256 [0.06013188, 0.30558427, 0.06062012],
257 [0.44611192, 0.02849786, 0.04207225],
258 [0.85188200, 0.57960585, 0.01053590],
259 [0.50608734, 0.08898812, 0.29720873],
260 [-0.03338628, 0.24880620, 0.38541145],
261 [0.88687341, 0.88867240, 0.87460352],
262 [0.58637305, 0.58330907, 0.58216473],
263 [0.35827233, 0.35810703, 0.35873042],
264 [0.20316001, 0.20298624, 0.20353015],
265 [0.09106388, 0.09288101, 0.09424415],
266 [0.03266569, 0.03364008, 0.03526672],
267 ]
268 ]
269 ),
270 atol=TOLERANCE_ABSOLUTE_TESTS,
271 )
273 assert [attribute.name for attribute in attributes] == [
274 "X",
275 "Y",
276 "Z",
277 "illuminant",
278 "chromaticities",
279 ]
281 components = {}
282 RGB, attributes = components_to_sRGB_Fichet2021(components, specification)
283 assert RGB is None
284 assert attributes == []
287def _test_spectral_image_D65(path: str) -> None:
288 """Test the *D65* spectral image."""
290 components = read_spectral_image_Fichet2021(path, additional_data=False)
292 assert "S0" in components
294 np.testing.assert_allclose(
295 components["S0"][0],
296 SDS_ILLUMINANTS["D65"].wavelengths,
297 atol=TOLERANCE_ABSOLUTE_TESTS,
298 )
300 np.testing.assert_allclose(
301 components["S0"][1],
302 np.reshape(SDS_ILLUMINANTS["D65"].values, (1, 1, -1)),
303 atol=0.05,
304 )
306 components, specification = read_spectral_image_Fichet2021(
307 os.path.join(ROOT_RESOURCES, "D65.exr"), additional_data=True
308 )
310 assert specification.is_emissive is True
311 assert specification.is_polarised is False
312 assert specification.is_bispectral is False
314 attribute_names = [attribute.name for attribute in specification.attributes]
316 for attribute_name in [
317 "EV",
318 "X",
319 "Y",
320 "Z",
321 "chromaticities",
322 "emissiveUnits",
323 "illuminant",
324 "polarisationHandedness",
325 "spectralLayoutVersion",
326 ]:
327 assert attribute_name in attribute_names
329 for attribute in specification.attributes:
330 if attribute.name == "spectralLayoutVersion":
331 assert attribute.value == "1.0"
332 elif attribute.name == "polarisationHandedness":
333 assert attribute.value == "right"
334 elif attribute.name == "emissiveUnits":
335 assert attribute.value == "W.m^-2.sr^-1"
336 elif attribute.name == "illuminant":
337 sd_illuminant = spectrum_attribute_to_sd_Fichet2021(attribute.value)
338 np.testing.assert_allclose(
339 sd_illuminant.values,
340 SDS_ILLUMINANTS["D65"].values,
341 atol=TOLERANCE_ABSOLUTE_TESTS,
342 )
345def _test_spectral_image_Ohta1997(path: str) -> None:
346 """Test the *Ohta (1997)* spectral image."""
348 components, specification = read_spectral_image_Fichet2021(
349 path, additional_data=True
350 )
352 assert "T" in components
354 msds = sds_and_msds_to_msds(
355 [
356 sd.copy().align(SpectralShape(400, 700, 20))
357 for sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].values()
358 ]
359 )
361 np.testing.assert_allclose(
362 components["T"][0],
363 msds.wavelengths,
364 atol=TOLERANCE_ABSOLUTE_TESTS,
365 )
367 np.testing.assert_allclose(
368 components["T"][1],
369 np.reshape(np.transpose(msds.values), (4, 6, -1)),
370 atol=0.0005,
371 )
373 assert specification.is_emissive is False
374 assert specification.is_polarised is False
375 assert specification.is_bispectral is False
378def _test_spectral_image_Polarised(path: str) -> None:
379 """Test the *Polarised* spectral image."""
381 components, specification = read_spectral_image_Fichet2021(
382 path, additional_data=True
383 )
385 assert list(components.keys()) == ["S0", "S1", "S2", "S3"]
387 assert specification.is_emissive is True
388 assert specification.is_polarised is True
389 assert specification.is_bispectral is False
392def _test_spectral_image_BiSpectral(path: str) -> None:
393 """Test the *Bi-Spectral* image."""
395 components, specification = read_spectral_image_Fichet2021(
396 path, additional_data=True
397 )
399 assert list(components.keys()) == [
400 "T",
401 380.0,
402 390.0,
403 400.0,
404 410.0,
405 420.0,
406 430.0,
407 440.0,
408 450.0,
409 460.0,
410 470.0,
411 480.0,
412 490.0,
413 500.0,
414 510.0,
415 520.0,
416 530.0,
417 540.0,
418 550.0,
419 560.0,
420 570.0,
421 580.0,
422 590.0,
423 600.0,
424 610.0,
425 620.0,
426 630.0,
427 640.0,
428 650.0,
429 660.0,
430 670.0,
431 680.0,
432 690.0,
433 700.0,
434 710.0,
435 720.0,
436 730.0,
437 740.0,
438 750.0,
439 760.0,
440 770.0,
441 ]
443 assert specification.is_emissive is False
444 assert specification.is_polarised is False
445 assert specification.is_bispectral is True
448class TestReadSpectralImageFichet2021:
449 """
450 Define :func:`colour.io.fichet2021.read_spectral_image_Fichet2021`
451 definition unit tests methods.
452 """
454 def test_read_spectral_image_Fichet2021(self) -> None:
455 """
456 Test :func:`colour.io.fichet2021.read_spectral_image_Fichet2021`
457 definition.
458 """
460 _test_spectral_image_D65(os.path.join(ROOT_RESOURCES, "D65.exr"))
462 _test_spectral_image_Ohta1997(os.path.join(ROOT_RESOURCES, "Ohta1997.exr"))
464 _test_spectral_image_Polarised(os.path.join(ROOT_RESOURCES, "Polarised.exr"))
466 _test_spectral_image_BiSpectral(os.path.join(ROOT_RESOURCES, "BiSpectral.exr"))
469class TestWriteSpectralImageFichet2021:
470 """
471 Define :func:`colour.io.fichet2021.write_spectral_image_Fichet2021`
472 definition unit tests methods.
473 """
475 def setup_method(self) -> None:
476 """Initialise the common tests attributes."""
478 self._temporary_directory = tempfile.mkdtemp()
480 def teardown_method(self) -> None:
481 """After tests actions."""
483 shutil.rmtree(self._temporary_directory)
485 def test_write_spectral_image_Fichet2021(self) -> None:
486 """
487 Test :func:`colour.io.fichet2021.write_spectral_image_Fichet2021`
488 definition.
489 """
491 path = os.path.join(self._temporary_directory, "D65.exr")
492 specification = Specification_Fichet2021(is_emissive=True)
493 write_spectral_image_Fichet2021(
494 SDS_ILLUMINANTS["D65"], path, "float16", specification
495 )
496 _test_spectral_image_D65(path)
498 path = os.path.join(self._temporary_directory, "D65.exr")
499 msds = [
500 sd.copy().align(SpectralShape(400, 700, 20))
501 for sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].values()
502 ]
503 specification = Specification_Fichet2021(is_emissive=False)
504 write_spectral_image_Fichet2021(
505 msds, path, "float16", specification, shape=(4, 6, 16)
506 )
507 _test_spectral_image_Ohta1997(path)
509 for basename, test_callable in [
510 ("Polarised.exr", _test_spectral_image_Polarised),
511 ("BiSpectral.exr", _test_spectral_image_BiSpectral),
512 ]:
513 components, specification = read_spectral_image_Fichet2021(
514 os.path.join(ROOT_RESOURCES, basename), additional_data=True
515 )
516 path = os.path.join(self._temporary_directory, basename)
517 write_spectral_image_Fichet2021(components, path, "float16", specification)
518 test_callable(path)
520 # Test with specification.attributes = None for both emissive and non-emissive
521 specification_emissive = Specification_Fichet2021(is_emissive=True)
522 specification_emissive.attributes = None
523 path = os.path.join(self._temporary_directory, "D65_no_attrs.exr")
524 write_spectral_image_Fichet2021(
525 SDS_ILLUMINANTS["D65"], path, "float16", specification_emissive
526 )
528 specification_non_emissive = Specification_Fichet2021(is_emissive=False)
529 specification_non_emissive.attributes = None
530 path = os.path.join(self._temporary_directory, "Ohta_no_attrs.exr")
531 write_spectral_image_Fichet2021(
532 msds, path, "float16", specification_non_emissive, shape=(4, 6, 16)
533 )