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

1"""

2 Define the unit tests for the :mod:`colour.appearance.zcam` module.

3"""

4

5 from __future__ import annotations

6

7 from itertools import permutations

8

9 import numpy as np

10 import pytest

11

12 from colour.appearance import (

13 VIEWING_CONDITIONS_ZCAM,

14 CAM_Specification_ZCAM,

15 InductionFactors_ZCAM,

16 XYZ_to_ZCAM,

17 ZCAM_to_XYZ,

18)

19 from colour.utilities import (

20 as_float_array,

21 domain_range_scale,

22 ignore_numpy_errors,

23 tsplit,

24)

25

26 __author__ = "Colour Developers"

27 __copyright__ = "Copyright 2013 Colour Developers"

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

29 __maintainer__ = "Colour Developers"

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

31 __status__ = "Production"

32

33 __all__ = ["TestXYZ_to_ZCAM", "TestZCAM_to_XYZ"]

34

35

36 class TestXYZ_to_ZCAM:

37 """

38 Defines :func:`colour.appearance.zcam.XYZ_to_ZCAM` definition unit tests

39 methods.

40 """

41

42 def test_XYZ_to_ZCAM(self) -> None:

43 """

44 Tests :func:`colour.appearance.zcam.XYZ_to_ZCAM` definition.

45 """

46

47 XYZ = np.array([185, 206, 163])

48 XYZ_w = np.array([256, 264, 202])

49 L_a = 264

50 Y_b = 100

51 surround = VIEWING_CONDITIONS_ZCAM["Average"]

52 np.testing.assert_allclose(

53 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround),

54 np.array(

55 [

56 92.2520,

57 3.0216,

58 196.3524,

59 19.1314,

60 321.3464,

61 10.5252,

62 237.6401,

63 np.nan,

64 34.7022,

65 25.2994,

66 91.6837,

67 ]

68 ),

69 rtol=0.025,

70 atol=0.025,

71 )

72

73 XYZ = np.array([89, 96, 120])

74 np.testing.assert_allclose(

75 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround),

76 np.array(

77 [

78 71.2071,

79 6.8539,

80 250.6422,

81 32.7963,

82 248.0394,

83 23.8744,

84 307.0595,

85 np.nan,

86 18.2796,

87 40.4621,

88 70.4026,

89 ]

90 ),

91 rtol=0.025,

92 atol=0.025,

93 )

94

95 # NOTE: Hue quadrature :math:`H_z` is significantly different for this

96 # test, i.e., 47.748252 vs 43.8258.

97 # NOTE: :math:`F_L` as reported in the supplemental document has the

98 # same value as for :math:`L_a` = 264 instead of 150. The values seem

99 # to be computed for :math:`L_a` = 264 and :math:`Y_b` = 100.

100 XYZ = np.array([79, 81, 62])

101 # L_a = 150

102 # Y_b = 60

103 surround = VIEWING_CONDITIONS_ZCAM["Dim"]

104 np.testing.assert_allclose(

105 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround),

106 np.array(

107 [

108 68.8890,

109 0.9774,

110 58.7532,

111 12.5916,

112 196.7686,

113 2.7918,

114 43.8258,

115 np.nan,

116 11.0371,

117 44.4143,

118 68.8737,

119 ]

120 ),

121 rtol=0.025,

122 atol=4,

123 )

124

125 XYZ = np.array([910, 1114, 500])

126 XYZ_w = np.array([2103, 2259, 1401])

127 L_a = 359

128 Y_b = 16

129 surround = VIEWING_CONDITIONS_ZCAM["Dark"]

130 np.testing.assert_allclose(

131 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround),

132 np.array(

133 [

134 82.6445,

135 13.0838,

136 123.9464,

137 44.7277,

138 114.7431,

139 18.1655,

140 178.6422,

141 np.nan,

142 34.4874,

143 26.8778,

144 78.2653,

145 ]

146 ),

147 rtol=0.025,

148 atol=0.025,

149 )

150

151 XYZ = np.array([96, 67, 28])

152 np.testing.assert_allclose(

153 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround),

154 np.array(

155 [

156 33.0139,

157 19.4070,

158 389.7720 % 360,

159 86.1882,

160 45.8363,

161 26.9446,

162 397.3301,

163 np.nan,

164 43.6447,

165 47.9942,

166 30.2593,

167 ]

168 ),

169 rtol=0.025,

170 atol=0.025,

171 )

172

173 def test_n_dimensional_XYZ_to_ZCAM(self) -> None:

174 """

175 Tests :func:`colour.appearance.zcam.XYZ_to_ZCAM` definition

176 n-dimensional support.

177 """

178

179 XYZ = np.array([185, 206, 163])

180 XYZ_w = np.array([256, 264, 202])

181 L_a = 264

182 Y_b = 100

183 surround = VIEWING_CONDITIONS_ZCAM["Average"]

184 specification = XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround)

185

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

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

188 np.testing.assert_almost_equal(

189 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround), specification, decimal=7

190 )

191

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

193 np.testing.assert_almost_equal(

194 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround), specification, decimal=7

195 )

196

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

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

199 specification = np.reshape(specification, (2, 3, 11))

200 np.testing.assert_almost_equal(

201 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround), specification, decimal=7

202 )

203

204 @ignore_numpy_errors

205 def test_domain_range_scale_XYZ_to_ZCAM(self) -> None:

206 """

207 Tests :func:`colour.appearance.zcam.XYZ_to_ZCAM` definition

208 domain and range scale support.

209 """

210

211 XYZ = np.array([185, 206, 163])

212 XYZ_w = np.array([256, 264, 202])

213 L_a = 264

214 Y_b = 100

215 surround = VIEWING_CONDITIONS_ZCAM["Average"]

216 specification = XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround)

217

218 d_r = (

219 ("reference", 1, 1),

220 ("1", 1, np.array([1, 1, 1 / 360, 1, 1, 1, 1 / 400, np.nan, 1, 1, 1])),

221 (

222 "100",

223 100,

224 np.array(

225 [

226 100,

227 100,

228 100 / 360,

229 100,

230 100,

231 100,

232 100 / 400,

233 np.nan,

234 100,

235 100,

236 100,

237 ]

238 ),

239 ),

240 )

241 for scale, factor_a, factor_b in d_r:

242 with domain_range_scale(scale):

243 np.testing.assert_almost_equal(

244 XYZ_to_ZCAM(XYZ * factor_a, XYZ_w * factor_a, L_a, Y_b, surround),

245 as_float_array(specification) * factor_b,

246 decimal=7,

247 )

248

249 @ignore_numpy_errors

250 def test_nan_XYZ_to_ZCAM(self) -> None:

251 """

252 Tests :func:`colour.appearance.zcam.XYZ_to_ZCAM` definition

253 nan support.

254 """

255

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

257 cases = set(permutations(cases * 3, r=3))

258 for case in cases:

259 XYZ = np.array(case)

260 XYZ_w = np.array(case)

261 L_a = case[0]

262 Y_b = 100

263 surround = InductionFactors_ZCAM(case[0], case[0], case[0], case[0])

264 XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround)

265

266

267 class TestZCAM_to_XYZ:

268 """

269 Defines :func:`colour.appearance.zcam.ZCAM_to_XYZ` definition unit

270 tests methods.

271 """

272

273 def test_ZCAM_to_XYZ(self) -> None:

274 """

275 Tests :func:`colour.appearance.zcam.ZCAM_to_XYZ` definition.

276 """

277

278 specification = CAM_Specification_ZCAM(

279 92.2520,

280 3.0216,

281 196.3524,

282 19.1314,

283 321.3464,

284 10.5252,

285 237.6401,

286 np.nan,

287 34.7022,

288 25.2994,

289 91.6837,

290 )

291 XYZ_w = np.array([256, 264, 202])

292 L_a = 264

293 Y_b = 100

294 surround = VIEWING_CONDITIONS_ZCAM["Average"]

295 np.testing.assert_allclose(

296 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround),

297 np.array([185, 206, 163]),

298 atol=0.01,

299 rtol=0.01,

300 )

301

302 specification = CAM_Specification_ZCAM(

303 71.2071,

304 6.8539,

305 250.6422,

306 32.7963,

307 248.0394,

308 23.8744,

309 307.0595,

310 np.nan,

311 18.2796,

312 40.4621,

313 70.4026,

314 )

315 np.testing.assert_allclose(

316 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround),

317 np.array([89, 96, 120]),

318 atol=0.01,

319 rtol=0.01,

320 )

321

322 specification = CAM_Specification_ZCAM(

323 68.8890,

324 0.9774,

325 58.7532,

326 12.5916,

327 196.7686,

328 2.7918,

329 43.8258,

330 np.nan,

331 11.0371,

332 44.4143,

333 68.8737,

334 )

335 surround = VIEWING_CONDITIONS_ZCAM["Dim"]

336 np.testing.assert_allclose(

337 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround),

338 np.array([79, 81, 62]),

339 atol=0.01,

340 rtol=0.01,

341 )

342

343 specification = CAM_Specification_ZCAM(

344 82.6445,

345 13.0838,

346 123.9464,

347 44.7277,

348 114.7431,

349 18.1655,

350 178.6422,

351 np.nan,

352 34.4874,

353 26.8778,

354 78.2653,

355 )

356 XYZ_w = np.array([2103, 2259, 1401])

357 L_a = 359

358 Y_b = 16

359 surround = VIEWING_CONDITIONS_ZCAM["Dark"]

360 np.testing.assert_allclose(

361 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround),

362 np.array([910, 1114, 500]),

363 atol=0.01,

364 rtol=0.01,

365 )

366

367 specification = CAM_Specification_ZCAM(

368 33.0139,

369 19.4070,

370 389.7720 % 360,

371 86.1882,

372 45.8363,

373 26.9446,

374 397.3301,

375 np.nan,

376 43.6447,

377 47.9942,

378 30.2593,

379 )

380 np.testing.assert_allclose(

381 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround),

382 np.array([96, 67, 28]),

383 atol=0.01,

384 rtol=0.01,

385 )

386

387 # Test using C instead of M

388 specification = CAM_Specification_ZCAM(

389 J=82.61980483202505, C=13.194790413382647, h=123.77987744640157

390 )

391 XYZ_w = np.array([2103, 2259, 1401])

392 L_a = 359

393 Y_b = 16

394 surround = VIEWING_CONDITIONS_ZCAM["Dark"]

395 np.testing.assert_allclose(

396 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround),

397 np.array([910, 1114, 500]),

398 atol=0.01,

399 rtol=0.01,

400 )

401

402 def test_n_dimensional_ZCAM_to_XYZ(self) -> None:

403 """

404 Tests :func:`colour.appearance.zcam.ZCAM_to_XYZ` definition

405 n-dimensional support.

406 """

407

408 XYZ = np.array([185, 206, 163])

409 XYZ_w = np.array([256, 264, 202])

410 L_a = 264

411 Y_b = 100

412 surround = VIEWING_CONDITIONS_ZCAM["Average"]

413 specification = XYZ_to_ZCAM(XYZ, XYZ_w, L_a, Y_b, surround)

414 XYZ = ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround)

415

416 specification = CAM_Specification_ZCAM(

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

418 )

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

420 np.testing.assert_almost_equal(

421 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround), XYZ, decimal=7

422 )

423

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

425 np.testing.assert_almost_equal(

426 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround), XYZ, decimal=7

427 )

428

429 specification = CAM_Specification_ZCAM(

430 *tsplit(np.reshape(specification, (2, 3, 11))).tolist()

431 )

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

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

434 np.testing.assert_almost_equal(

435 ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround), XYZ, decimal=7

436 )

437

438 @ignore_numpy_errors

439 def test_domain_range_scale_ZCAM_to_XYZ(self) -> None:

440 """

441 Tests :func:`colour.appearance.zcam.ZCAM_to_XYZ` definition

442 domain and range scale support.

443 """

444

445 XYZ_i = np.array([185, 206, 163])

446 XYZ_w = np.array([256, 264, 202])

447 L_a = 264

448 Y_b = 100

449 surround = VIEWING_CONDITIONS_ZCAM["Average"]

450 specification = XYZ_to_ZCAM(XYZ_i, XYZ_w, L_a, Y_b, surround)

451 XYZ = ZCAM_to_XYZ(specification, XYZ_w, L_a, Y_b, surround)

452

453 d_r = (

454 ("reference", 1, 1),

455 ("1", np.array([1, 1, 1 / 360, 1, 1, 1, 1 / 400, np.nan, 1, 1, 1]), 1),

456 (

457 "100",

458 np.array(

459 [

460 100,

461 100,

462 100 / 360,

463 100,

464 100,

465 100,

466 100 / 400,

467 np.nan,

468 100,

469 100,

470 100,

471 ]

472 ),

473 100,

474 ),

475 )

476 for scale, factor_a, factor_b in d_r:

477 with domain_range_scale(scale):

478 np.testing.assert_almost_equal(

479 ZCAM_to_XYZ(

480 specification * factor_a, XYZ_w * factor_b, L_a, Y_b, surround

481 ),

482 XYZ * factor_b,

483 decimal=7,

484 )

485

486 @ignore_numpy_errors

487 def test_raise_exception_ZCAM_to_XYZ(self) -> None:

488 """

489 Tests :func:`colour.appearance.zcam.ZCAM_to_XYZ` definition

490 raised exception.

491 """

492

493 pytest.raises(

494 ValueError,

495 ZCAM_to_XYZ,

496 CAM_Specification_ZCAM(

497 41.731091132513917,

498 None,

499 219.04843265831178,

500 ),

501 np.array([256, 264, 202]),

502 318.31,

503 20.0,

504 VIEWING_CONDITIONS_ZCAM["Average"],

505 )

506

507 @ignore_numpy_errors

508 def test_nan_ZCAM_to_XYZ(self) -> None:

509 """

510 Tests :func:`colour.appearance.zcam.ZCAM_to_XYZ` definition nan

511 support.

512 """

513

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

515 cases = set(permutations(cases * 3, r=3))

516 for case in cases:

517 J = case[0]

518 C = case[0]

519 h = case[0]

520 XYZ_w = np.array(case)

521 L_a = case[0]

522 Y_b = 100

523 surround = InductionFactors_ZCAM(case[0], case[0], case[0], case[0])

524 ZCAM_to_XYZ(

525 CAM_Specification_ZCAM(J, C, h, M=50), XYZ_w, L_a, Y_b, surround

526 )