Coverage for colour/appearance/tests/test_ciecam02.py: 100%

131 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

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

2 

3from __future__ import annotations 

4 

5from itertools import product 

6 

7import numpy as np 

8import pytest 

9 

10from colour.appearance import ( 

11 VIEWING_CONDITIONS_CIECAM02, 

12 CAM_Specification_CIECAM02, 

13 CIECAM02_to_XYZ, 

14 InductionFactors_CIECAM02, 

15 XYZ_to_CIECAM02, 

16) 

17from colour.constants import TOLERANCE_ABSOLUTE_TESTS 

18from colour.utilities import ( 

19 as_float_array, 

20 domain_range_scale, 

21 ignore_numpy_errors, 

22 tsplit, 

23) 

24 

25__author__ = "Colour Developers" 

26__copyright__ = "Copyright 2013 Colour Developers" 

27__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" 

28__maintainer__ = "Colour Developers" 

29__email__ = "colour-developers@colour-science.org" 

30__status__ = "Production" 

31 

32__all__ = [ 

33 "TestXYZ_to_CIECAM02", 

34 "TestCIECAM02_to_XYZ", 

35] 

36 

37 

38class TestXYZ_to_CIECAM02: 

39 """ 

40 Define :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition unit 

41 tests methods. 

42 """ 

43 

44 def test_XYZ_to_CIECAM02(self) -> None: 

45 """ 

46 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition. 

47 

48 Notes 

49 ----- 

50 - The test values have been generated from data of the following file 

51 by *Fairchild (2013)*: 

52 http://rit-mcsl.org/fairchild//files/AppModEx.xls 

53 """ 

54 

55 XYZ = np.array([19.01, 20.00, 21.78]) 

56 XYZ_w = np.array([95.05, 100.00, 108.88]) 

57 L_A = 318.31 

58 Y_b = 20 

59 surround = InductionFactors_CIECAM02(1, 0.69, 1) 

60 np.testing.assert_allclose( 

61 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

62 np.array([41.73, 0.1, 219, 2.36, 195.37, 0.11, 278.1, np.nan]), 

63 atol=0.05, 

64 ) 

65 

66 XYZ = np.array([57.06, 43.06, 31.96]) 

67 L_A = 31.83 

68 np.testing.assert_allclose( 

69 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

70 np.array([65.96, 48.57, 19.6, 52.25, 152.67, 41.67, 399.6, np.nan]), 

71 atol=0.05, 

72 ) 

73 

74 XYZ = np.array([3.53, 6.56, 2.14]) 

75 XYZ_w = np.array([109.85, 100.00, 35.58]) 

76 L_A = 318.31 

77 np.testing.assert_allclose( 

78 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

79 np.array([21.79, 46.94, 177.1, 58.79, 141.17, 48.8, 220.4, np.nan]), 

80 atol=0.05, 

81 ) 

82 

83 XYZ = np.array([19.01, 20.00, 21.78]) 

84 L_A = 31.83 

85 np.testing.assert_allclose( 

86 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

87 np.array([42.53, 51.92, 248.9, 60.22, 122.83, 44.54, 305.8, np.nan]), 

88 atol=0.05, 

89 ) 

90 

91 XYZ = np.array([61.45276998, 7.00421901, 82.24067384]) 

92 XYZ_w = np.array([95.05, 100, 108.88]) 

93 L_A = 4.074366543152521 

94 np.testing.assert_allclose( 

95 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

96 np.array( 

97 [ 

98 21.72630603341673, 

99 411.5190338631848, 

100 349.12875710099053, 

101 227.15081998415593, 

102 57.657243286322725, 

103 297.49693233026602, 

104 375.5788601911363, 

105 np.nan, 

106 ] 

107 ), 

108 atol=0.05, 

109 ) 

110 

111 def test_n_dimensional_XYZ_to_CIECAM02(self) -> None: 

112 """ 

113 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition 

114 n-dimensional support. 

115 """ 

116 

117 XYZ = np.array([19.01, 20.00, 21.78]) 

118 XYZ_w = np.array([95.05, 100.00, 108.88]) 

119 L_A = 318.31 

120 Y_b = 20 

121 surround = VIEWING_CONDITIONS_CIECAM02["Average"] 

122 specification = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround) 

123 

124 XYZ = np.tile(XYZ, (6, 1)) 

125 specification = np.tile(specification, (6, 1)) 

126 np.testing.assert_allclose( 

127 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

128 specification, 

129 atol=TOLERANCE_ABSOLUTE_TESTS, 

130 ) 

131 

132 XYZ_w = np.tile(XYZ_w, (6, 1)) 

133 np.testing.assert_allclose( 

134 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

135 specification, 

136 atol=TOLERANCE_ABSOLUTE_TESTS, 

137 ) 

138 

139 XYZ = np.reshape(XYZ, (2, 3, 3)) 

140 XYZ_w = np.reshape(XYZ_w, (2, 3, 3)) 

141 specification = np.reshape(specification, (2, 3, 8)) 

142 np.testing.assert_allclose( 

143 XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround), 

144 specification, 

145 atol=TOLERANCE_ABSOLUTE_TESTS, 

146 ) 

147 

148 @ignore_numpy_errors 

149 def test_domain_range_scale_XYZ_to_CIECAM02(self) -> None: 

150 """ 

151 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition 

152 domain and range scale support. 

153 """ 

154 

155 XYZ = np.array([19.01, 20.00, 21.78]) 

156 XYZ_w = np.array([95.05, 100.00, 108.88]) 

157 L_A = 318.31 

158 Y_b = 20 

159 surround = VIEWING_CONDITIONS_CIECAM02["Average"] 

160 specification = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround) 

161 

162 d_r = ( 

163 ("reference", 1, 1), 

164 ( 

165 "1", 

166 0.01, 

167 np.array( 

168 [ 

169 1 / 100, 

170 1 / 100, 

171 1 / 360, 

172 1 / 100, 

173 1 / 100, 

174 1 / 100, 

175 1 / 400, 

176 np.nan, 

177 ] 

178 ), 

179 ), 

180 ( 

181 "100", 

182 1, 

183 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]), 

184 ), 

185 ) 

186 for scale, factor_a, factor_b in d_r: 

187 with domain_range_scale(scale): 

188 np.testing.assert_allclose( 

189 XYZ_to_CIECAM02( 

190 XYZ * factor_a, XYZ_w * factor_a, L_A, Y_b, surround 

191 ), 

192 as_float_array(specification) * factor_b, 

193 atol=TOLERANCE_ABSOLUTE_TESTS, 

194 ) 

195 

196 @ignore_numpy_errors 

197 def test_nan_XYZ_to_CIECAM02(self) -> None: 

198 """ 

199 Test :func:`colour.appearance.ciecam02.XYZ_to_CIECAM02` definition 

200 nan support. 

201 """ 

202 

203 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan] 

204 cases = np.array(list(set(product(cases, repeat=3)))) 

205 surround = InductionFactors_CIECAM02(cases[0, 0], cases[0, 0], cases[0, 0]) 

206 XYZ_to_CIECAM02(cases, cases, cases[..., 0], cases[..., 0], surround) 

207 

208 

209class TestCIECAM02_to_XYZ: 

210 """ 

211 Define :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition unit 

212 tests methods. 

213 """ 

214 

215 def test_CIECAM02_to_XYZ(self) -> None: 

216 """Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition.""" 

217 

218 specification = CAM_Specification_CIECAM02( 

219 41.73, 0.1, 219, 2.36, 195.37, 0.11, 278.1 

220 ) 

221 XYZ_w = np.array([95.05, 100.00, 108.88]) 

222 L_A = 318.31 

223 Y_b = 20 

224 surround = InductionFactors_CIECAM02(1, 0.69, 1) 

225 np.testing.assert_allclose( 

226 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

227 np.array([19.01, 20.00, 21.78]), 

228 atol=0.05, 

229 ) 

230 

231 specification = CAM_Specification_CIECAM02( 

232 65.96, 48.57, 19.6, 52.25, 152.67, 41.67, 399.6, np.nan 

233 ) 

234 L_A = 31.83 

235 np.testing.assert_allclose( 

236 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

237 np.array([57.06, 43.06, 31.96]), 

238 atol=0.05, 

239 ) 

240 

241 specification = CAM_Specification_CIECAM02( 

242 21.79, 46.94, 177.1, 58.79, 141.17, 48.8, 220.4, np.nan 

243 ) 

244 XYZ_w = np.array([109.85, 100.00, 35.58]) 

245 L_A = 318.31 

246 np.testing.assert_allclose( 

247 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

248 np.array([3.53, 6.56, 2.14]), 

249 atol=0.05, 

250 ) 

251 

252 specification = CAM_Specification_CIECAM02( 

253 42.53, 51.92, 248.9, 60.22, 122.83, 44.54, 305.8, np.nan 

254 ) 

255 L_A = 31.83 

256 np.testing.assert_allclose( 

257 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

258 np.array([19.01, 20.00, 21.78]), 

259 atol=0.05, 

260 ) 

261 

262 specification = CAM_Specification_CIECAM02( 

263 21.72630603341673, 

264 411.5190338631848, 

265 349.12875710099053, 

266 227.15081998415593, 

267 57.657243286322725, 

268 297.49693233026602, 

269 375.5788601911363, 

270 np.nan, 

271 ) 

272 XYZ_w = np.array([95.05, 100, 108.88]) 

273 L_A = 4.074366543152521 

274 np.testing.assert_allclose( 

275 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

276 np.array([61.45276998, 7.00421901, 82.24067384]), 

277 atol=0.05, 

278 ) 

279 

280 def test_n_dimensional_CIECAM02_to_XYZ(self) -> None: 

281 """ 

282 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition 

283 n-dimensional support. 

284 """ 

285 

286 XYZ = np.array([19.01, 20.00, 21.78]) 

287 XYZ_w = np.array([95.05, 100.00, 108.88]) 

288 L_A = 318.31 

289 Y_b = 20 

290 surround = VIEWING_CONDITIONS_CIECAM02["Average"] 

291 specification = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround) 

292 XYZ = CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround) 

293 

294 specification = CAM_Specification_CIECAM02( 

295 *np.transpose(np.tile(tsplit(specification), (6, 1))).tolist() 

296 ) 

297 XYZ = np.tile(XYZ, (6, 1)) 

298 np.testing.assert_allclose( 

299 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

300 XYZ, 

301 atol=TOLERANCE_ABSOLUTE_TESTS, 

302 ) 

303 

304 XYZ_w = np.tile(XYZ_w, (6, 1)) 

305 np.testing.assert_allclose( 

306 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

307 XYZ, 

308 atol=TOLERANCE_ABSOLUTE_TESTS, 

309 ) 

310 

311 specification = CAM_Specification_CIECAM02( 

312 *tsplit(np.reshape(specification, (2, 3, 8))).tolist() 

313 ) 

314 XYZ_w = np.reshape(XYZ_w, (2, 3, 3)) 

315 XYZ = np.reshape(XYZ, (2, 3, 3)) 

316 np.testing.assert_allclose( 

317 CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

318 XYZ, 

319 atol=TOLERANCE_ABSOLUTE_TESTS, 

320 ) 

321 

322 @ignore_numpy_errors 

323 def test_domain_range_scale_CIECAM02_to_XYZ(self) -> None: 

324 """ 

325 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition 

326 domain and range scale support. 

327 """ 

328 

329 XYZ_i = np.array([19.01, 20.00, 21.78]) 

330 XYZ_w = np.array([95.05, 100.00, 108.88]) 

331 L_A = 318.31 

332 Y_b = 20 

333 surround = VIEWING_CONDITIONS_CIECAM02["Average"] 

334 specification = XYZ_to_CIECAM02(XYZ_i, XYZ_w, L_A, Y_b, surround) 

335 XYZ = CIECAM02_to_XYZ(specification, XYZ_w, L_A, Y_b, surround) 

336 

337 d_r = ( 

338 ("reference", 1, 1), 

339 ( 

340 "1", 

341 np.array( 

342 [ 

343 1 / 100, 

344 1 / 100, 

345 1 / 360, 

346 1 / 100, 

347 1 / 100, 

348 1 / 100, 

349 1 / 400, 

350 np.nan, 

351 ] 

352 ), 

353 0.01, 

354 ), 

355 ( 

356 "100", 

357 np.array([1, 1, 100 / 360, 1, 1, 1, 100 / 400, np.nan]), 

358 1, 

359 ), 

360 ) 

361 for scale, factor_a, factor_b in d_r: 

362 with domain_range_scale(scale): 

363 np.testing.assert_allclose( 

364 CIECAM02_to_XYZ( 

365 specification * factor_a, 

366 XYZ_w * factor_b, 

367 L_A, 

368 Y_b, 

369 surround, 

370 ), 

371 XYZ * factor_b, 

372 atol=TOLERANCE_ABSOLUTE_TESTS, 

373 ) 

374 

375 @ignore_numpy_errors 

376 def test_raise_exception_CIECAM02_to_XYZ(self) -> None: 

377 """ 

378 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition 

379 raised exception. 

380 """ 

381 

382 pytest.raises( 

383 ValueError, 

384 CIECAM02_to_XYZ, 

385 CAM_Specification_CIECAM02(41.731091132513917, None, 219.04843265831178), 

386 np.array([95.05, 100.00, 108.88]), 

387 318.31, 

388 20.0, 

389 VIEWING_CONDITIONS_CIECAM02["Average"], 

390 ) 

391 

392 @ignore_numpy_errors 

393 def test_nan_CIECAM02_to_XYZ(self) -> None: 

394 """ 

395 Test :func:`colour.appearance.ciecam02.CIECAM02_to_XYZ` definition 

396 nan support. 

397 """ 

398 

399 cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan] 

400 cases = np.array(list(set(product(cases, repeat=3)))) 

401 surround = InductionFactors_CIECAM02(cases[0, 0], cases[0, 0], cases[0, 0]) 

402 CIECAM02_to_XYZ( 

403 CAM_Specification_CIECAM02( 

404 cases[..., 0], cases[..., 0], cases[..., 0], M=50 

405 ), 

406 cases, 

407 cases[..., 0], 

408 cases[..., 0], 

409 surround, 

410 )