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

1"""Define the unit tests for the :mod:`colour.appearance.hunt` module.""" 

2 

3from __future__ import annotations 

4 

5import contextlib 

6from itertools import product 

7 

8import numpy as np 

9 

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 

17 

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" 

24 

25__all__ = [ 

26 "TestXYZ_to_Hunt", 

27] 

28 

29 

30class TestXYZ_to_Hunt: 

31 """ 

32 Define :func:`colour.appearance.hunt.XYZ_to_Hunt` definition unit tests 

33 methods. 

34 """ 

35 

36 def test_XYZ_to_Hunt(self) -> None: 

37 """ 

38 Test :func:`colour.appearance.hunt.XYZ_to_Hunt` definition. 

39 

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

46 

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 ) 

58 

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 ) 

66 

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 ) 

76 

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 ) 

84 

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

90 

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) 

98 

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 ) 

106 

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 ) 

114 

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 ) 

124 

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

130 

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) 

138 

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 ) 

158 

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

165 

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 

173 

174 with contextlib.suppress(ValueError): 

175 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround) 

176 

177 with contextlib.suppress(ValueError): 

178 XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w, S=S) 

179 

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) 

182 

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

189 

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 

196 

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 ) 

221 

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

228 

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 )