Coverage for recovery/tests/test_mallett2019.py: 100%

1"""Define the unit tests for the :mod:`colour.recovery.mallett2019` module."""

2

3 from __future__ import annotations

4

5 import numpy as np

6 import pytest

7

8 from colour.characterisation import SDS_COLOURCHECKERS

9 from 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)

18 from colour.difference import JND_CIE1976, delta_E_CIE1976

19 from colour.models import (

20 RGB_COLOURSPACE_PAL_SECAM,

21 RGB_COLOURSPACE_sRGB,

22 XYZ_to_Lab,

23 XYZ_to_RGB,

24)

25 from colour.recovery import (

26 MSDS_BASIS_FUNCTIONS_sRGB_MALLETT2019,

27 RGB_to_sd_Mallett2019,

28 spectral_primary_decomposition_Mallett2019,

29)

30

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"

37

38 __all__ = [

39 "FixtureMallett2019",

40 "TestSpectralPrimaryDecompositionMallett2019",

41 "TestRGB_to_sd_Mallett2019",

42]

43

44

45 class FixtureMallett2019:

46 """A fixture for testing the :mod:`colour.recovery.mallett2019` module."""

47

48 @pytest.fixture(autouse=True)

49 def setup_fixture_Mallett2019(self) -> None:

50 """Configure the class instance."""

51

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"]

60

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 """

67

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

73

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)

81

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)

87

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)

91

92 error = delta_E_CIE1976(Lab, recovered_Lab)

93

94 if error > 4 * JND_CIE1976 / 100: # pragma: no cover

95 pytest.fail(f'Delta E for "{name}" is {error}!')

96

97

98 class TestSpectralPrimaryDecompositionMallett2019(FixtureMallett2019):

99 """

100 Define :func:`colour.recovery.mallett2019.\

101 spectral_primary_decomposition_Mallett2019` definition unit tests methods.

102 """

103

104 def setup_method(self) -> None:

105 """Initialise the common tests attributes."""

106

107 self._RGB_colourspace = RGB_COLOURSPACE_PAL_SECAM

108

109 def test_spectral_primary_decomposition_Mallett2019(self) -> None:

110 """

111 Test :func:`colour.recovery.mallett2019.\

112 test_spectral_primary_decomposition_Mallett2019` definition.

113 """

114

115 self._basis = spectral_primary_decomposition_Mallett2019(

116 self._RGB_colourspace, self._cmfs, self._sd_D65

117 )

118

119 self.check_basis_functions()

120

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 )

127

128 self.check_basis_functions()

129

130

131 class TestRGB_to_sd_Mallett2019(FixtureMallett2019):

132 """

133 Define :func:`colour.recovery.mallett2019.RGB_to_sd_Mallett2019` definition

134 unit tests methods.

135 """

136

137 def setup_method(self) -> None:

138 """Initialise the common tests attributes."""

139

140 self._RGB_colourspace = RGB_COLOURSPACE_sRGB

141 self._basis = MSDS_BASIS_FUNCTIONS_sRGB_MALLETT2019

142

143 def test_RGB_to_sd_Mallett2019(self) -> None:

144 """

145 Test :func:`colour.recovery.mallett2019.RGB_to_sd_Mallett2019`

146 definition.

147 """

148

149 self.check_basis_functions()