Coverage for appearance/tests/test_ciecam16.py: 100%

141 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.ciecam16` 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_CIECAM16, 

12 CAM_Specification_CIECAM16, 

13 CIECAM16_to_XYZ, 

14 InductionFactors_CIECAM16, 

15 XYZ_to_CIECAM16, 

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

34 "TestCIECAM16_to_XYZ", 

35] 

36 

37 

38class TestXYZ_to_CIECAM16: 

39 """ 

40 Define :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition unit 

41 tests methods. 

42 """ 

43 

44 def test_XYZ_to_CIECAM16(self) -> None: 

45 """ 

46 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition. 

47 """ 

48 

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

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

51 L_A = 318.31 

52 Y_b = 20 

53 surround = VIEWING_CONDITIONS_CIECAM16["Average"] 

54 np.testing.assert_allclose( 

55 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

56 np.array( 

57 [ 

58 41.73120791, 

59 0.10335574, 

60 217.06795977, 

61 2.34501507, 

62 195.37170899, 

63 0.10743677, 

64 275.59498615, 

65 np.nan, 

66 ] 

67 ), 

68 atol=TOLERANCE_ABSOLUTE_TESTS, 

69 ) 

70 

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

72 L_A = 31.83 

73 np.testing.assert_allclose( 

74 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

75 np.array( 

76 [ 

77 65.42828069, 

78 49.67956420, 

79 17.48659243, 

80 52.94308868, 

81 152.06985268, 

82 42.62473321, 

83 398.03047943, 

84 np.nan, 

85 ] 

86 ), 

87 atol=TOLERANCE_ABSOLUTE_TESTS, 

88 ) 

89 

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

91 XYZ_w = np.array([109.85, 100, 35.58]) 

92 L_A = 318.31 

93 np.testing.assert_allclose( 

94 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

95 np.array( 

96 [ 

97 21.36052893, 

98 50.99381895, 

99 178.86724266, 

100 61.57953092, 

101 139.78582768, 

102 53.00732582, 

103 223.01823806, 

104 np.nan, 

105 ] 

106 ), 

107 atol=TOLERANCE_ABSOLUTE_TESTS, 

108 ) 

109 

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

111 L_A = 318.31 

112 np.testing.assert_allclose( 

113 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

114 np.array( 

115 [ 

116 41.36326063, 

117 52.81154022, 

118 258.88676291, 

119 53.12406914, 

120 194.52011798, 

121 54.89682038, 

122 311.24768647, 

123 np.nan, 

124 ] 

125 ), 

126 atol=TOLERANCE_ABSOLUTE_TESTS, 

127 ) 

128 

129 XYZ = np.array([61.45276998, 7.00421901, 82.2406738]) 

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

131 L_A = 4.074366543152521 

132 np.testing.assert_allclose( 

133 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

134 np.array( 

135 [ 

136 2.212842606688056, 

137 597.366327557872864, 

138 352.035143755398565, 

139 484.428915071471351, 

140 18.402345804194972, 

141 431.850377022773955, 

142 378.267899100834541, 

143 np.nan, 

144 ] 

145 ), 

146 atol=TOLERANCE_ABSOLUTE_TESTS, 

147 ) 

148 

149 XYZ = np.array([60.70, 49.60, 10.29]) 

150 XYZ_w = np.array([96.46, 100.00, 108.62]) 

151 L_A = 40 

152 Y_b = 16 

153 np.testing.assert_allclose( 

154 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

155 np.array( 

156 [ 

157 70.4406, 

158 58.6035, 

159 57.9145, 

160 54.5604, 

161 172.1555, 

162 51.2479, 

163 50.7425, 

164 np.nan, 

165 ] 

166 ), 

167 atol=5e-5, 

168 ) 

169 

170 def test_n_dimensional_XYZ_to_CIECAM16(self) -> None: 

171 """ 

172 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition 

173 n-dimensional support. 

174 """ 

175 

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

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

178 L_A = 318.31 

179 Y_b = 20 

180 surround = VIEWING_CONDITIONS_CIECAM16["Average"] 

181 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround) 

182 

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

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

185 np.testing.assert_allclose( 

186 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

187 specification, 

188 atol=TOLERANCE_ABSOLUTE_TESTS, 

189 ) 

190 

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

192 np.testing.assert_allclose( 

193 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

194 specification, 

195 atol=TOLERANCE_ABSOLUTE_TESTS, 

196 ) 

197 

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

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

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

201 np.testing.assert_allclose( 

202 XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround), 

203 specification, 

204 atol=TOLERANCE_ABSOLUTE_TESTS, 

205 ) 

206 

207 @ignore_numpy_errors 

208 def test_domain_range_scale_XYZ_to_CIECAM16(self) -> None: 

209 """ 

210 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition 

211 domain and range scale support. 

212 """ 

213 

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

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

216 L_A = 318.31 

217 Y_b = 20 

218 surround = VIEWING_CONDITIONS_CIECAM16["Average"] 

219 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround) 

220 

221 d_r = ( 

222 ("reference", 1, 1), 

223 ( 

224 "1", 

225 0.01, 

226 np.array( 

227 [ 

228 1 / 100, 

229 1 / 100, 

230 1 / 360, 

231 1 / 100, 

232 1 / 100, 

233 1 / 100, 

234 1 / 400, 

235 np.nan, 

236 ] 

237 ), 

238 ), 

239 ( 

240 "100", 

241 1, 

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

243 ), 

244 ) 

245 for scale, factor_a, factor_b in d_r: 

246 with domain_range_scale(scale): 

247 np.testing.assert_allclose( 

248 XYZ_to_CIECAM16( 

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

250 ), 

251 as_float_array(specification) * factor_b, 

252 atol=TOLERANCE_ABSOLUTE_TESTS, 

253 ) 

254 

255 @ignore_numpy_errors 

256 def test_nan_XYZ_to_CIECAM16(self) -> None: 

257 """ 

258 Test :func:`colour.appearance.ciecam16.XYZ_to_CIECAM16` definition 

259 nan support. 

260 """ 

261 

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

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

264 surround = InductionFactors_CIECAM16(cases[0, 0], cases[0, 0], cases[0, 0]) 

265 XYZ_to_CIECAM16(cases, cases, cases[..., 0], cases[..., 0], surround) 

266 

267 

268class TestCIECAM16_to_XYZ: 

269 """ 

270 Define :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition unit 

271 tests methods. 

272 """ 

273 

274 def test_CIECAM16_to_XYZ(self) -> None: 

275 """ 

276 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition. 

277 """ 

278 

279 specification = CAM_Specification_CIECAM16( 

280 41.73120791, 0.10335574, 217.06795977 

281 ) 

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

283 L_A = 318.31 

284 Y_b = 20 

285 surround = VIEWING_CONDITIONS_CIECAM16["Average"] 

286 np.testing.assert_allclose( 

287 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

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

289 atol=TOLERANCE_ABSOLUTE_TESTS, 

290 ) 

291 

292 specification = CAM_Specification_CIECAM16( 

293 65.42828069, 49.67956420, 17.48659243 

294 ) 

295 L_A = 31.83 

296 np.testing.assert_allclose( 

297 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

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

299 atol=TOLERANCE_ABSOLUTE_TESTS, 

300 ) 

301 

302 specification = CAM_Specification_CIECAM16( 

303 21.36052893, 50.99381895, 178.86724266 

304 ) 

305 XYZ_w = np.array([109.85, 100, 35.58]) 

306 L_A = 318.31 

307 np.testing.assert_allclose( 

308 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

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

310 atol=TOLERANCE_ABSOLUTE_TESTS, 

311 ) 

312 

313 specification = CAM_Specification_CIECAM16( 

314 41.36326063, 52.81154022, 258.88676291 

315 ) 

316 L_A = 318.31 

317 np.testing.assert_allclose( 

318 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

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

320 atol=TOLERANCE_ABSOLUTE_TESTS, 

321 ) 

322 

323 specification = CAM_Specification_CIECAM16( 

324 2.212842606688056, 597.366327557872864, 352.035143755398565 

325 ) 

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

327 L_A = 4.074366543152521 

328 np.testing.assert_allclose( 

329 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

330 np.array([61.45276998, 7.00421901, 82.2406738]), 

331 atol=TOLERANCE_ABSOLUTE_TESTS, 

332 ) 

333 

334 specification = CAM_Specification_CIECAM16(70.4406, 58.6035, 57.9145) 

335 XYZ_w = np.array([96.46, 100.00, 108.62]) 

336 L_A = 40 

337 Y_b = 16 

338 np.testing.assert_allclose( 

339 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

340 np.array([60.70, 49.60, 10.29]), 

341 atol=1e-4, 

342 ) 

343 

344 def test_n_dimensional_CIECAM16_to_XYZ(self) -> None: 

345 """ 

346 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition 

347 n-dimensional support. 

348 """ 

349 

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

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

352 L_A = 318.31 

353 Y_b = 20 

354 surround = VIEWING_CONDITIONS_CIECAM16["Average"] 

355 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround) 

356 XYZ = CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround) 

357 

358 specification = CAM_Specification_CIECAM16( 

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

360 ) 

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

362 np.testing.assert_allclose( 

363 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

364 XYZ, 

365 atol=TOLERANCE_ABSOLUTE_TESTS, 

366 ) 

367 

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

369 np.testing.assert_allclose( 

370 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

371 XYZ, 

372 atol=TOLERANCE_ABSOLUTE_TESTS, 

373 ) 

374 

375 specification = CAM_Specification_CIECAM16( 

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

377 ) 

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

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

380 np.testing.assert_allclose( 

381 CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround), 

382 XYZ, 

383 atol=TOLERANCE_ABSOLUTE_TESTS, 

384 ) 

385 

386 @ignore_numpy_errors 

387 def test_domain_range_scale_CIECAM16_to_XYZ(self) -> None: 

388 """ 

389 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition 

390 domain and range scale support. 

391 """ 

392 

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

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

395 L_A = 318.31 

396 Y_b = 20 

397 surround = VIEWING_CONDITIONS_CIECAM16["Average"] 

398 specification = XYZ_to_CIECAM16(XYZ, XYZ_w, L_A, Y_b, surround) 

399 XYZ = CIECAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround) 

400 

401 d_r = ( 

402 ("reference", 1, 1), 

403 ( 

404 "1", 

405 np.array( 

406 [ 

407 1 / 100, 

408 1 / 100, 

409 1 / 360, 

410 1 / 100, 

411 1 / 100, 

412 1 / 100, 

413 1 / 400, 

414 np.nan, 

415 ] 

416 ), 

417 0.01, 

418 ), 

419 ( 

420 "100", 

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

422 1, 

423 ), 

424 ) 

425 for scale, factor_a, factor_b in d_r: 

426 with domain_range_scale(scale): 

427 np.testing.assert_allclose( 

428 CIECAM16_to_XYZ( 

429 specification * factor_a, 

430 XYZ_w * factor_b, 

431 L_A, 

432 Y_b, 

433 surround, 

434 ), 

435 XYZ * factor_b, 

436 atol=TOLERANCE_ABSOLUTE_TESTS, 

437 ) 

438 

439 @ignore_numpy_errors 

440 def test_raise_exception_CIECAM16_to_XYZ(self) -> None: 

441 """ 

442 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition 

443 raised exception. 

444 """ 

445 

446 pytest.raises( 

447 ValueError, 

448 CIECAM16_to_XYZ, 

449 CAM_Specification_CIECAM16(41.731207905126638, None, 217.06795976739301), 

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

451 318.31, 

452 20.0, 

453 VIEWING_CONDITIONS_CIECAM16["Average"], 

454 ) 

455 

456 @ignore_numpy_errors 

457 def test_nan_CIECAM16_to_XYZ(self) -> None: 

458 """ 

459 Test :func:`colour.appearance.ciecam16.CIECAM16_to_XYZ` definition nan 

460 support. 

461 """ 

462 

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

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

465 surround = InductionFactors_CIECAM16(cases[0, 0], cases[0, 0], cases[0, 0]) 

466 CIECAM16_to_XYZ( 

467 CAM_Specification_CIECAM16( 

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

469 ), 

470 cases, 

471 cases[..., 0], 

472 cases[..., 0], 

473 surround, 

474 )