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

1"""

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

3"""

4

5 from __future__ import annotations

6

7 from itertools import product

8

9 import numpy as np

10 import pytest

11

12 from colour.appearance import (

13 CAM_Specification_sCAM,

14 VIEWING_CONDITIONS_sCAM,

15 XYZ_to_sCAM,

16 sCAM_to_XYZ,

17)

18 from colour.constants import TOLERANCE_ABSOLUTE_TESTS

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, UltraMo114(Molin Li)"

27 __copyright__ = "Copyright 2024 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_sCAM", "TestsCAM_to_XYZ"]

34

35

36 class TestXYZ_to_sCAM:

37 """

38 Define :func:`colour.appearance.scam.XYZ_to_sCAM` definition unit

39 tests methods.

40 """

41

42 def test_XYZ_to_sCAM(self) -> None:

43 """

44 Test :func:`colour.appearance.scam.XYZ_to_sCAM` definition.

45 """

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_sCAM["Average"]

52 np.testing.assert_allclose(

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

54 np.array(

55 [

56 49.97956680,

57 0.01405311,

58 328.27249244,

59 195.23024234,

60 0.00502448,

61 363.60134377,

62 np.nan,

63 49.97957273,

64 50.02042727,

65 34.97343274,

66 65.02656726,

67 ]

68 ),

69 atol=TOLERANCE_ABSOLUTE_TESTS,

70 )

71

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

73 L_A = 31.83

74 np.testing.assert_allclose(

75 XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround),

76 np.array(

77 [

78 71.63079886,

79 37.33838127,

80 18.75135858,

81 259.13174065,

82 10.66713872,

83 4.20415978,

84 np.nan,

85 96.50614225,

86 3.49385775,

87 28.37649889,

88 71.62350111,

89 ]

90 ),

91 atol=TOLERANCE_ABSOLUTE_TESTS,

92 )

93

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

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

96 L_A = 318.31

97 np.testing.assert_allclose(

98 XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround),

99 np.array(

100 [

101 29.61821869,

102 25.97461207,

103 178.56952253,

104 115.69472052,

105 10.76901611,

106 227.46922207,

107 np.nan,

108 53.86353400,

109 46.13646600,

110 -0.97480767,

111 100.97480767,

112 ]

113 ),

114 atol=TOLERANCE_ABSOLUTE_TESTS,

115 )

116

117 def test_n_dimensional_XYZ_to_sCAM(self) -> None:

118 """

119 Test :func:`colour.appearance.scam.XYZ_to_sCAM` definition

120 n-dimensional support.

121 """

122

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

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

125 L_A = 318.31

126 Y_b = 20

127 surround = VIEWING_CONDITIONS_sCAM["Average"]

128 specification = XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround)

129

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

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

132 np.testing.assert_allclose(

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

134 specification,

135 atol=TOLERANCE_ABSOLUTE_TESTS,

136 )

137

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

139 np.testing.assert_allclose(

140 XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround),

141 specification,

142 atol=TOLERANCE_ABSOLUTE_TESTS,

143 )

144

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

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

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

148 np.testing.assert_allclose(

149 XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround),

150 specification,

151 atol=TOLERANCE_ABSOLUTE_TESTS,

152 )

153

154 @ignore_numpy_errors

155 def test_domain_range_scale_XYZ_to_sCAM(self) -> None:

156 """

157 Test :func:`colour.appearance.scam.XYZ_to_sCAM` definition

158 domain and range scale support.

159 """

160

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

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

163 L_A = 318.31

164 Y_b = 20

165 surround = VIEWING_CONDITIONS_sCAM["Average"]

166

167 specification = XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround)

168

169 d_r = (

170 ("reference", 1, 1),

171 (

172 "1",

173 0.01,

174 np.array(

175 [

176 1 / 100,

177 1 / 100,

178 1 / 360,

179 1 / 100,

180 1 / 100,

181 1 / 400,

182 1,

183 1 / 100,

184 1 / 100,

185 1 / 100,

186 1 / 100,

187 ]

188 ),

189 ),

190 (

191 "100",

192 1,

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

194 ),

195 )

196

197 for scale, factor_a, factor_b in d_r:

198 with domain_range_scale(scale):

199 np.testing.assert_allclose(

200 XYZ_to_sCAM(XYZ * factor_a, XYZ_w * factor_a, L_A, Y_b, surround),

201 as_float_array(specification) * factor_b,

202 atol=TOLERANCE_ABSOLUTE_TESTS,

203 )

204

205 @ignore_numpy_errors

206 def test_nan_XYZ_to_sCAM(self) -> None:

207 """

208 Test :func:`colour.appearance.scam.XYZ_to_sCAM` definition

209 nan support.

210 """

211

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

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

214 surround = VIEWING_CONDITIONS_sCAM["Average"]

215 XYZ_to_sCAM(cases, cases, cases[..., 0], cases[..., 0], surround)

216

217

218 class TestsCAM_to_XYZ:

219 """

220 Define :func:`colour.appearance.scam.sCAM_to_XYZ` definition unit

221 tests methods.

222 """

223

224 def test_sCAM_to_XYZ(self) -> None:

225 """

226 Test :func:`colour.appearance.scam.sCAM_to_XYZ` definition.

227 """

228

229 specification = CAM_Specification_sCAM(49.97956680, 0.01405311, 328.27249244)

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

231 L_A = 318.31

232 Y_b = 20

233 surround = VIEWING_CONDITIONS_sCAM["Average"]

234 np.testing.assert_allclose(

235 sCAM_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

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

237 atol=TOLERANCE_ABSOLUTE_TESTS,

238 )

239

240 specification = CAM_Specification_sCAM(71.63079886, 37.33838127, 18.75135858)

241 L_A = 31.83

242 np.testing.assert_allclose(

243 sCAM_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

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

245 atol=TOLERANCE_ABSOLUTE_TESTS,

246 )

247

248 specification = CAM_Specification_sCAM(29.61821869, 25.97461207, 178.56952253)

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

250 L_A = 318.31

251 np.testing.assert_allclose(

252 sCAM_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

253 np.array([3.53256359, 6.56009775, 2.15585716]),

254 atol=TOLERANCE_ABSOLUTE_TESTS,

255 )

256

257 def test_n_dimensional_sCAM_to_XYZ(self) -> None:

258 """

259 Test :func:`colour.appearance.scam.sCAM_to_XYZ` definition

260 n-dimensional support.

261 """

262

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

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

265 L_A = 318.31

266 Y_b = 20

267 surround = VIEWING_CONDITIONS_sCAM["Average"]

268 specification = XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround)

269 XYZ = sCAM_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)

270

271 specification = CAM_Specification_sCAM(

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

273 )

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

275 np.testing.assert_allclose(

276 sCAM_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

277 XYZ,

278 atol=TOLERANCE_ABSOLUTE_TESTS,

279 )

280

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

282 np.testing.assert_allclose(

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

284 XYZ,

285 atol=TOLERANCE_ABSOLUTE_TESTS,

286 )

287

288 specification = CAM_Specification_sCAM(

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

290 )

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

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

293 np.testing.assert_allclose(

294 sCAM_to_XYZ(specification, XYZ_w, L_A, Y_b, surround),

295 XYZ,

296 atol=TOLERANCE_ABSOLUTE_TESTS,

297 )

298

299 @ignore_numpy_errors

300 def test_domain_range_scale_sCAM_to_XYZ(self) -> None:

301 """

302 Test :func:`colour.appearance.scam.sCAM_to_XYZ` definition

303 domain and range scale support.

304 """

305

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

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

308 L_A = 318.31

309 Y_b = 20

310 surround = VIEWING_CONDITIONS_sCAM["Average"]

311 specification = XYZ_to_sCAM(XYZ, XYZ_w, L_A, Y_b, surround)

312 XYZ = sCAM_to_XYZ(specification, XYZ_w, L_A, Y_b, surround)

313

314 d_r = (

315 ("reference", 1, 1),

316 (

317 "1",

318 np.array(

319 [

320 1 / 100,

321 1 / 100,

322 1 / 360,

323 1 / 100,

324 1 / 100,

325 1 / 400,

326 1,

327 1 / 100,

328 1 / 100,

329 1 / 100,

330 1 / 100,

331 ]

332 ),

333 0.01,

334 ),

335 (

336 "100",

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

338 1,

339 ),

340 )

341 for scale, factor_a, factor_b in d_r:

342 with domain_range_scale(scale):

343 np.testing.assert_allclose(

344 sCAM_to_XYZ(

345 specification * factor_a,

346 XYZ_w * factor_b,

347 L_A,

348 Y_b,

349 surround,

350 ),

351 XYZ * factor_b,

352 atol=TOLERANCE_ABSOLUTE_TESTS,

353 )

354

355 @ignore_numpy_errors

356 def test_raise_exception_sCAM_to_XYZ(self) -> None:

357 """

358 Test :func:`colour.appearance.scam.sCAM_to_XYZ` definition

359 raised exception.

360 """

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

362 L_A = 318.31

363 Y_b = 20

364 surround = VIEWING_CONDITIONS_sCAM["Average"]

365

366 with pytest.raises(ValueError):

367 sCAM_to_XYZ(

368 CAM_Specification_sCAM(J=None, C=20.0, h=210.0),

369 XYZ_w,

370 L_A,

371 Y_b,

372 surround,

373 )

374

375 with pytest.raises(ValueError):

376 sCAM_to_XYZ(

377 CAM_Specification_sCAM(J=40.0, C=20.0, h=None),

378 XYZ_w,

379 L_A,

380 Y_b,

381 surround,

382 )

383

384 with pytest.raises(ValueError):

385 sCAM_to_XYZ(

386 CAM_Specification_sCAM(J=40.0, C=None, h=210.0, M=None),

387 XYZ_w,

388 L_A,

389 Y_b,

390 surround,

391 )

392

393 @ignore_numpy_errors

394 def test_nan_sCAM_to_XYZ(self) -> None:

395 """

396 Test :func:`colour.appearance.scam.sCAM_to_XYZ` definition nan

397 support.

398 """

399

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

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

402 surround = VIEWING_CONDITIONS_sCAM["Average"]

403 sCAM_to_XYZ(

404 CAM_Specification_sCAM(cases[..., 0], cases[..., 0], cases[..., 0], M=50),

405 cases,

406 cases[..., 0],

407 cases[..., 0],

408 surround,

409 )