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

1"""Define the unit tests for the :mod:`colour.appearance.cam16` 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_CAM16,

12 CAM16_to_XYZ,

13 CAM_Specification_CAM16,

14 InductionFactors_CAM16,

15 XYZ_to_CAM16,

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

34 "TestCAM16_to_XYZ",

35]

36

37

38 class TestXYZ_to_CAM16:

39 """

40 Define :func:`colour.appearance.cam16.XYZ_to_CAM16` definition unit

41 tests methods.

42 """

43

44 def test_XYZ_to_CAM16(self) -> None:

45 """Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition."""

46

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

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

49 L_A = 318.31

50 Y_b = 20

51 surround = VIEWING_CONDITIONS_CAM16["Average"]

52 np.testing.assert_allclose(

53 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

54 np.array(

55 [

56 41.73120791,

57 0.10335574,

58 217.06795977,

59 2.34501507,

60 195.37170899,

61 0.10743677,

62 275.59498615,

63 np.nan,

64 ]

65 ),

66 atol=TOLERANCE_ABSOLUTE_TESTS,

67 )

68

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

70 L_A = 31.83

71 np.testing.assert_allclose(

72 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

73 np.array(

74 [

75 65.42828069,

76 49.67956420,

77 17.48659243,

78 52.94308868,

79 152.06985268,

80 42.62473321,

81 398.03047943,

82 np.nan,

83 ]

84 ),

85 atol=TOLERANCE_ABSOLUTE_TESTS,

86 )

87

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

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

90 L_A = 318.31

91 np.testing.assert_allclose(

92 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

93 np.array(

94 [

95 21.36052893,

96 50.99381895,

97 178.86724266,

98 61.57953092,

99 139.78582768,

100 53.00732582,

101 223.01823806,

102 np.nan,

103 ]

104 ),

105 atol=TOLERANCE_ABSOLUTE_TESTS,

106 )

107

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

109 L_A = 318.31

110 np.testing.assert_allclose(

111 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

112 np.array(

113 [

114 41.36326063,

115 52.81154022,

116 258.88676291,

117 53.12406914,

118 194.52011798,

119 54.89682038,

120 311.24768647,

121 np.nan,

122 ]

123 ),

124 atol=TOLERANCE_ABSOLUTE_TESTS,

125 )

126

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

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

129 L_A = 4.074366543152521

130 np.testing.assert_allclose(

131 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

132 np.array(

133 [

134 21.03801957,

135 457.78881613,

136 350.06445098,

137 241.50642846,

138 56.74143988,

139 330.94646237,

140 376.43915877,

141 np.nan,

142 ]

143 ),

144 atol=TOLERANCE_ABSOLUTE_TESTS,

145 )

146

147 def test_n_dimensional_XYZ_to_CAM16(self) -> None:

148 """

149 Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition

150 n-dimensional support.

151 """

152

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

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

155 L_A = 318.31

156 Y_b = 20

157 surround = VIEWING_CONDITIONS_CAM16["Average"]

158 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)

159

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

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

162 np.testing.assert_allclose(

163 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

164 specification,

165 atol=TOLERANCE_ABSOLUTE_TESTS,

166 )

167

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

169 np.testing.assert_allclose(

170 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

171 specification,

172 atol=TOLERANCE_ABSOLUTE_TESTS,

173 )

174

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

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

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

178 np.testing.assert_allclose(

179 XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround),

180 specification,

181 atol=TOLERANCE_ABSOLUTE_TESTS,

182 )

183

184 @ignore_numpy_errors

185 def test_domain_range_scale_XYZ_to_CAM16(self) -> None:

186 """

187 Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition domain

188 and range scale support.

189 """

190

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

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

193 L_A = 318.31

194 Y_b = 20

195 surround = VIEWING_CONDITIONS_CAM16["Average"]

196 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)

197

198 d_r = (

199 ("reference", 1, 1),

200 (

201 "1",

202 0.01,

203 np.array(

204 [

205 1 / 100,

206 1 / 100,

207 1 / 360,

208 1 / 100,

209 1 / 100,

210 1 / 100,

211 1 / 400,

212 np.nan,

213 ]

214 ),

215 ),

216 (

217 "100",

218 1,

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

220 ),

221 )

222 for scale, factor_a, factor_b in d_r:

223 with domain_range_scale(scale):

224 np.testing.assert_allclose(

225 XYZ_to_CAM16(XYZ * factor_a, XYZ_w * factor_a, L_A, Y_b, surround),

226 as_float_array(specification) * factor_b,

227 atol=TOLERANCE_ABSOLUTE_TESTS,

228 )

229

230 @ignore_numpy_errors

231 def test_nan_XYZ_to_CAM16(self) -> None:

232 """

233 Test :func:`colour.appearance.cam16.XYZ_to_CAM16` definition

234 nan support.

235 """

236

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

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

239 surround = InductionFactors_CAM16(cases[0, 0], cases[0, 0], cases[0, 0])

240 XYZ_to_CAM16(cases, cases, cases[..., 0], cases[..., 0], surround)

241

242

243 class TestCAM16_to_XYZ:

244 """

245 Define :func:`colour.appearance.cam16.CAM16_to_XYZ` definition unit tests

246 methods.

247 """

248

249 def test_CAM16_to_XYZ(self) -> None:

250 """Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition."""

251

252 specification = CAM_Specification_CAM16(41.73120791, 0.10335574, 217.06795977)

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

254 L_A = 318.31

255 Y_b = 20

256 surround = VIEWING_CONDITIONS_CAM16["Average"]

257 np.testing.assert_allclose(

258 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

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

260 atol=TOLERANCE_ABSOLUTE_TESTS,

261 )

262

263 specification = CAM_Specification_CAM16(65.42828069, 49.67956420, 17.48659243)

264 L_A = 31.83

265 np.testing.assert_allclose(

266 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

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

268 atol=TOLERANCE_ABSOLUTE_TESTS,

269 )

270

271 specification = CAM_Specification_CAM16(21.36052893, 50.99381895, 178.86724266)

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

273 L_A = 318.31

274 np.testing.assert_allclose(

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

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

277 atol=TOLERANCE_ABSOLUTE_TESTS,

278 )

279

280 specification = CAM_Specification_CAM16(41.36326063, 52.81154022, 258.88676291)

281 L_A = 318.31

282 np.testing.assert_allclose(

283 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

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

285 atol=TOLERANCE_ABSOLUTE_TESTS,

286 )

287

288 specification = CAM_Specification_CAM16(21.03801957, 457.78881613, 350.06445098)

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

290 L_A = 4.074366543152521

291 np.testing.assert_allclose(

292 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

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

294 atol=TOLERANCE_ABSOLUTE_TESTS,

295 )

296

297 def test_n_dimensional_CAM16_to_XYZ(self) -> None:

298 """

299 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition

300 n-dimensional support.

301 """

302

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

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

305 L_A = 318.31

306 Y_b = 20

307 surround = VIEWING_CONDITIONS_CAM16["Average"]

308 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)

309 XYZ = CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)

310

311 specification = CAM_Specification_CAM16(

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

313 )

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

315 np.testing.assert_allclose(

316 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

317 XYZ,

318 atol=TOLERANCE_ABSOLUTE_TESTS,

319 )

320

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

322 np.testing.assert_allclose(

323 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

324 XYZ,

325 atol=TOLERANCE_ABSOLUTE_TESTS,

326 )

327

328 specification = CAM_Specification_CAM16(

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

330 )

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

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

333 np.testing.assert_allclose(

334 CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

335 XYZ,

336 atol=TOLERANCE_ABSOLUTE_TESTS,

337 )

338

339 @ignore_numpy_errors

340 def test_domain_range_scale_CAM16_to_XYZ(self) -> None:

341 """

342 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition domain

343 and range scale support.

344 """

345

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

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

348 L_A = 318.31

349 Y_b = 20

350 surround = VIEWING_CONDITIONS_CAM16["Average"]

351 specification = XYZ_to_CAM16(XYZ, XYZ_w, L_A, Y_b, surround)

352 XYZ = CAM16_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)

353

354 d_r = (

355 ("reference", 1, 1),

356 (

357 "1",

358 np.array(

359 [

360 1 / 100,

361 1 / 100,

362 1 / 360,

363 1 / 100,

364 1 / 100,

365 1 / 100,

366 1 / 400,

367 np.nan,

368 ]

369 ),

370 0.01,

371 ),

372 (

373 "100",

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

375 1,

376 ),

377 )

378 for scale, factor_a, factor_b in d_r:

379 with domain_range_scale(scale):

380 np.testing.assert_allclose(

381 CAM16_to_XYZ(

382 specification * factor_a,

383 XYZ_w * factor_b,

384 L_A,

385 Y_b,

386 surround,

387 ),

388 XYZ * factor_b,

389 atol=TOLERANCE_ABSOLUTE_TESTS,

390 )

391

392 @ignore_numpy_errors

393 def test_raise_exception_CAM16_to_XYZ(self) -> None:

394 """

395 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition raised

396 exception.

397 """

398

399 pytest.raises(

400 ValueError,

401 CAM16_to_XYZ,

402 CAM_Specification_CAM16(41.731207905126638, None, 217.06795976739301),

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

404 318.31,

405 20.0,

406 VIEWING_CONDITIONS_CAM16["Average"],

407 )

408

409 @ignore_numpy_errors

410 def test_nan_CAM16_to_XYZ(self) -> None:

411 """

412 Test :func:`colour.appearance.cam16.CAM16_to_XYZ` definition nan

413 support.

414 """

415

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

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

418 surround = InductionFactors_CAM16(cases[0, 0], cases[0, 0], cases[0, 0])

419 CAM16_to_XYZ(

420 CAM_Specification_CAM16(cases[..., 0], cases[..., 0], cases[..., 0], M=50),

421 cases,

422 cases[..., 0],

423 cases[..., 0],

424 surround,

425 )