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

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

2

3 from __future__ import annotations

4

5 from itertools import product

6

7 import numpy as np

8 import pytest

9

10 from colour.appearance import (

11 VIEWING_CONDITIONS_CIECAM16,

12 CAM_Specification_CIECAM16,

13 CIECAM16_to_XYZ,

14 InductionFactors_CIECAM16,

15 XYZ_to_CIECAM16,

16)

17 from colour.constants import TOLERANCE_ABSOLUTE_TESTS

18 from 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

38 class 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

268 class 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 )