Coverage for recovery/tests/test_mallett2019.py: 100%
55 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.recovery.mallett2019` module."""
3from __future__ import annotations
5import numpy as np
6import pytest
8from colour.characterisation import SDS_COLOURCHECKERS
9from colour.colorimetry import (
10 CCS_ILLUMINANTS,
11 MSDS_CMFS,
12 SDS_ILLUMINANTS,
13 SpectralShape,
14 reshape_msds,
15 reshape_sd,
16 sd_to_XYZ,
17)
18from colour.difference import JND_CIE1976, delta_E_CIE1976
19from colour.models import (
20 RGB_COLOURSPACE_PAL_SECAM,
21 RGB_COLOURSPACE_sRGB,
22 XYZ_to_Lab,
23 XYZ_to_RGB,
24)
25from colour.recovery import (
26 MSDS_BASIS_FUNCTIONS_sRGB_MALLETT2019,
27 RGB_to_sd_Mallett2019,
28 spectral_primary_decomposition_Mallett2019,
29)
31__author__ = "Colour Developers"
32__copyright__ = "Copyright 2013 Colour Developers"
33__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
34__maintainer__ = "Colour Developers"
35__email__ = "colour-developers@colour-science.org"
36__status__ = "Production"
38__all__ = [
39 "FixtureMallett2019",
40 "TestSpectralPrimaryDecompositionMallett2019",
41 "TestRGB_to_sd_Mallett2019",
42]
45class FixtureMallett2019:
46 """A fixture for testing the :mod:`colour.recovery.mallett2019` module."""
48 @pytest.fixture(autouse=True)
49 def setup_fixture_Mallett2019(self) -> None:
50 """Configure the class instance."""
52 self._basis = MSDS_BASIS_FUNCTIONS_sRGB_MALLETT2019
53 self._RGB_colourspace = RGB_COLOURSPACE_sRGB
54 self._cmfs = reshape_msds(
55 MSDS_CMFS["CIE 1931 2 Degree Standard Observer"],
56 SpectralShape(360, 780, 10),
57 )
58 self._sd_D65 = reshape_sd(SDS_ILLUMINANTS["D65"], self._cmfs.shape)
59 self._xy_D65 = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"]
61 def check_basis_functions(self) -> None:
62 """
63 Test :func:`colour.recovery.RGB_to_sd_Mallett2019` definition or the
64 more specialised :func:`colour.recovery.RGB_to_sd_Mallett2019`
65 definition.
66 """
68 # Make sure the white point is reconstructed as a perfectly flat
69 # spectrum.
70 RGB = np.full(3, 1.0)
71 sd = RGB_to_sd_Mallett2019(RGB, self._basis)
72 assert np.var(sd.values) < 1e-5
74 # Check if the primaries or their combination exceeds the [0, 1] range.
75 lower = np.zeros_like(sd.values) - 1e-12
76 upper = np.ones_like(sd.values) + 1e12
77 for RGB in [[1, 1, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1]]:
78 sd = RGB_to_sd_Mallett2019(RGB, self._basis)
79 np.testing.assert_array_less(sd.values, upper)
80 np.testing.assert_array_less(lower, sd.values)
82 # Check Delta E's using a colour checker.
83 for name, sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].items():
84 XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
85 Lab = XYZ_to_Lab(XYZ, self._xy_D65)
86 RGB = XYZ_to_RGB(XYZ, self._RGB_colourspace, self._xy_D65)
88 recovered_sd = RGB_to_sd_Mallett2019(RGB, self._basis)
89 recovered_XYZ = sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100
90 recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)
92 error = delta_E_CIE1976(Lab, recovered_Lab)
94 if error > 4 * JND_CIE1976 / 100: # pragma: no cover
95 pytest.fail(f'Delta E for "{name}" is {error}!')
98class TestSpectralPrimaryDecompositionMallett2019(FixtureMallett2019):
99 """
100 Define :func:`colour.recovery.mallett2019.\
101spectral_primary_decomposition_Mallett2019` definition unit tests methods.
102 """
104 def setup_method(self) -> None:
105 """Initialise the common tests attributes."""
107 self._RGB_colourspace = RGB_COLOURSPACE_PAL_SECAM
109 def test_spectral_primary_decomposition_Mallett2019(self) -> None:
110 """
111 Test :func:`colour.recovery.mallett2019.\
112test_spectral_primary_decomposition_Mallett2019` definition.
113 """
115 self._basis = spectral_primary_decomposition_Mallett2019(
116 self._RGB_colourspace, self._cmfs, self._sd_D65
117 )
119 self.check_basis_functions()
121 self._basis = spectral_primary_decomposition_Mallett2019(
122 self._RGB_colourspace,
123 self._cmfs,
124 self._sd_D65,
125 optimisation_kwargs={"options": {"maxiter": 10}},
126 )
128 self.check_basis_functions()
131class TestRGB_to_sd_Mallett2019(FixtureMallett2019):
132 """
133 Define :func:`colour.recovery.mallett2019.RGB_to_sd_Mallett2019` definition
134 unit tests methods.
135 """
137 def setup_method(self) -> None:
138 """Initialise the common tests attributes."""
140 self._RGB_colourspace = RGB_COLOURSPACE_sRGB
141 self._basis = MSDS_BASIS_FUNCTIONS_sRGB_MALLETT2019
143 def test_RGB_to_sd_Mallett2019(self) -> None:
144 """
145 Test :func:`colour.recovery.mallett2019.RGB_to_sd_Mallett2019`
146 definition.
147 """
149 self.check_basis_functions()