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

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

12 CAM_Specification_CIECAM02,

13 CIECAM02_to_XYZ,

14 InductionFactors_CIECAM02,

15 XYZ_to_CIECAM02,

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

34 "TestCIECAM02_to_XYZ",

35]

36

37

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

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