Coverage for colour/models/tests/test_common.py: 100%

125 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1"""Define the unit tests for the :mod:`colour.models.common` module.""" 

2 

3from __future__ import annotations 

4 

5from itertools import product 

6 

7import numpy as np 

8 

9from colour.constants import TOLERANCE_ABSOLUTE_TESTS 

10from colour.models import Iab_to_XYZ, Jab_to_JCh, JCh_to_Jab, XYZ_to_Iab 

11from colour.utilities import domain_range_scale, ignore_numpy_errors 

12 

13__author__ = "Colour Developers" 

14__copyright__ = "Copyright 2013 Colour Developers" 

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

16__maintainer__ = "Colour Developers" 

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

18__status__ = "Production" 

19 

20__all__ = [ 

21 "TestJab_to_JCh", 

22 "TestJCh_to_Jab", 

23 "TestXYZ_to_Iab", 

24 "TestIab_to_XYZ", 

25] 

26 

27 

28class TestJab_to_JCh: 

29 """ 

30 Define :func:`colour.models.common.Jab_to_JCh` definition unit tests 

31 methods. 

32 """ 

33 

34 def test_Jab_to_JCh(self) -> None: 

35 """Test :func:`colour.models.common.Jab_to_JCh` definition.""" 

36 

37 np.testing.assert_allclose( 

38 Jab_to_JCh(np.array([41.52787529, 52.63858304, 26.92317922])), 

39 np.array([41.52787529, 59.12425901, 27.08848784]), 

40 atol=TOLERANCE_ABSOLUTE_TESTS, 

41 ) 

42 

43 np.testing.assert_allclose( 

44 Jab_to_JCh(np.array([55.11636304, -41.08791787, 30.91825778])), 

45 np.array([55.11636304, 51.42135412, 143.03889556]), 

46 atol=TOLERANCE_ABSOLUTE_TESTS, 

47 ) 

48 

49 np.testing.assert_allclose( 

50 Jab_to_JCh(np.array([29.80565520, 20.01830466, -48.34913874])), 

51 np.array([29.80565520, 52.32945383, 292.49133666]), 

52 atol=TOLERANCE_ABSOLUTE_TESTS, 

53 ) 

54 

55 def test_n_dimensional_Jab_to_JCh(self) -> None: 

56 """ 

57 Test :func:`colour.models.common.Jab_to_JCh` definition n-dimensional 

58 arrays support. 

59 """ 

60 

61 Lab = np.array([41.52787529, 52.63858304, 26.92317922]) 

62 LCHab = Jab_to_JCh(Lab) 

63 

64 Lab = np.tile(Lab, (6, 1)) 

65 LCHab = np.tile(LCHab, (6, 1)) 

66 np.testing.assert_allclose( 

67 Jab_to_JCh(Lab), LCHab, atol=TOLERANCE_ABSOLUTE_TESTS 

68 ) 

69 

70 Lab = np.reshape(Lab, (2, 3, 3)) 

71 LCHab = np.reshape(LCHab, (2, 3, 3)) 

72 np.testing.assert_allclose( 

73 Jab_to_JCh(Lab), LCHab, atol=TOLERANCE_ABSOLUTE_TESTS 

74 ) 

75 

76 def test_domain_range_scale_Jab_to_JCh(self) -> None: 

77 """ 

78 Test :func:`colour.models.common.Jab_to_JCh` definition domain and 

79 range scale support. 

80 """ 

81 

82 Lab = np.array([41.52787529, 52.63858304, 26.92317922]) 

83 LCHab = Jab_to_JCh(Lab) 

84 

85 d_r = ( 

86 ("reference", 1, 1), 

87 ("1", 0.01, np.array([0.01, 0.01, 1 / 360])), 

88 ("100", 1, np.array([1, 1, 1 / 3.6])), 

89 ) 

90 for scale, factor_a, factor_b in d_r: 

91 with domain_range_scale(scale): 

92 np.testing.assert_allclose( 

93 Jab_to_JCh(Lab * factor_a), 

94 LCHab * factor_b, 

95 atol=TOLERANCE_ABSOLUTE_TESTS, 

96 ) 

97 

98 @ignore_numpy_errors 

99 def test_nan_Jab_to_JCh(self) -> None: 

100 """Test :func:`colour.models.common.Jab_to_JCh` definition nan support.""" 

101 

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

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

104 Jab_to_JCh(cases) 

105 

106 

107class TestJCh_to_Jab: 

108 """ 

109 Define :func:`colour.models.common.JCh_to_Jab` definition unit tests 

110 methods. 

111 """ 

112 

113 def test_JCh_to_Jab(self) -> None: 

114 """Test :func:`colour.models.common.JCh_to_Jab` definition.""" 

115 

116 np.testing.assert_allclose( 

117 JCh_to_Jab(np.array([41.52787529, 59.12425901, 27.08848784])), 

118 np.array([41.52787529, 52.63858304, 26.92317922]), 

119 atol=TOLERANCE_ABSOLUTE_TESTS, 

120 ) 

121 

122 np.testing.assert_allclose( 

123 JCh_to_Jab(np.array([55.11636304, 51.42135412, 143.03889556])), 

124 np.array([55.11636304, -41.08791787, 30.91825778]), 

125 atol=TOLERANCE_ABSOLUTE_TESTS, 

126 ) 

127 

128 np.testing.assert_allclose( 

129 JCh_to_Jab(np.array([29.80565520, 52.32945383, 292.49133666])), 

130 np.array([29.80565520, 20.01830466, -48.34913874]), 

131 atol=TOLERANCE_ABSOLUTE_TESTS, 

132 ) 

133 

134 def test_n_dimensional_JCh_to_Jab(self) -> None: 

135 """ 

136 Test :func:`colour.models.common.JCh_to_Jab` definition n-dimensional 

137 arrays support. 

138 """ 

139 

140 LCHab = np.array([41.52787529, 59.12425901, 27.08848784]) 

141 Lab = JCh_to_Jab(LCHab) 

142 

143 LCHab = np.tile(LCHab, (6, 1)) 

144 Lab = np.tile(Lab, (6, 1)) 

145 np.testing.assert_allclose( 

146 JCh_to_Jab(LCHab), Lab, atol=TOLERANCE_ABSOLUTE_TESTS 

147 ) 

148 

149 LCHab = np.reshape(LCHab, (2, 3, 3)) 

150 Lab = np.reshape(Lab, (2, 3, 3)) 

151 np.testing.assert_allclose( 

152 JCh_to_Jab(LCHab), Lab, atol=TOLERANCE_ABSOLUTE_TESTS 

153 ) 

154 

155 def test_domain_range_scale_JCh_to_Jab(self) -> None: 

156 """ 

157 Test :func:`colour.models.common.JCh_to_Jab` definition domain and 

158 range scale support. 

159 """ 

160 

161 LCHab = np.array([41.52787529, 59.12425901, 27.08848784]) 

162 Lab = JCh_to_Jab(LCHab) 

163 

164 d_r = ( 

165 ("reference", 1, 1), 

166 ("1", np.array([0.01, 0.01, 1 / 360]), 0.01), 

167 ("100", np.array([1, 1, 1 / 3.6]), 1), 

168 ) 

169 for scale, factor_a, factor_b in d_r: 

170 with domain_range_scale(scale): 

171 np.testing.assert_allclose( 

172 JCh_to_Jab(LCHab * factor_a), 

173 Lab * factor_b, 

174 atol=TOLERANCE_ABSOLUTE_TESTS, 

175 ) 

176 

177 @ignore_numpy_errors 

178 def test_nan_JCh_to_Jab(self) -> None: 

179 """Test :func:`colour.models.common.JCh_to_Jab` definition nan support.""" 

180 

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

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

183 JCh_to_Jab(cases) 

184 

185 

186class TestXYZ_to_Iab: 

187 """Define :func:`colour.models.common.XYZ_to_Iab` definition unit tests methods.""" 

188 

189 def setup_method(self) -> None: 

190 """Initialise the common tests attributes.""" 

191 

192 self.LMS_to_LMS_p = lambda x: x**0.43 

193 self.M_XYZ_to_LMS = np.array( 

194 [ 

195 [0.4002, 0.7075, -0.0807], 

196 [-0.2280, 1.1500, 0.0612], 

197 [0.0000, 0.0000, 0.9184], 

198 ] 

199 ) 

200 self.M_LMS_p_to_Iab = np.array( 

201 [ 

202 [0.4000, 0.4000, 0.2000], 

203 [4.4550, -4.8510, 0.3960], 

204 [0.8056, 0.3572, -1.1628], 

205 ] 

206 ) 

207 

208 def test_XYZ_to_Iab(self) -> None: 

209 """Test :func:`colour.models.common.XYZ_to_Iab` definition.""" 

210 

211 np.testing.assert_allclose( 

212 XYZ_to_Iab( 

213 np.array([0.20654008, 0.12197225, 0.05136952]), 

214 self.LMS_to_LMS_p, 

215 self.M_XYZ_to_LMS, 

216 self.M_LMS_p_to_Iab, 

217 ), 

218 np.array([0.38426191, 0.38487306, 0.18886838]), 

219 atol=TOLERANCE_ABSOLUTE_TESTS, 

220 ) 

221 

222 np.testing.assert_allclose( 

223 XYZ_to_Iab( 

224 np.array([0.14222010, 0.23042768, 0.10495772]), 

225 self.LMS_to_LMS_p, 

226 self.M_XYZ_to_LMS, 

227 self.M_LMS_p_to_Iab, 

228 ), 

229 np.array([0.49437481, -0.19251742, 0.18080304]), 

230 atol=TOLERANCE_ABSOLUTE_TESTS, 

231 ) 

232 

233 np.testing.assert_allclose( 

234 XYZ_to_Iab( 

235 np.array([0.07818780, 0.06157201, 0.28099326]), 

236 self.LMS_to_LMS_p, 

237 self.M_XYZ_to_LMS, 

238 self.M_LMS_p_to_Iab, 

239 ), 

240 np.array([0.35167774, -0.07525627, -0.30921279]), 

241 atol=TOLERANCE_ABSOLUTE_TESTS, 

242 ) 

243 

244 def test_n_dimensional_XYZ_to_Iab(self) -> None: 

245 """ 

246 Test :func:`colour.models.common.XYZ_to_Iab` definition n-dimensional 

247 support. 

248 """ 

249 

250 XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) 

251 Iab = XYZ_to_Iab(XYZ, self.LMS_to_LMS_p, self.M_XYZ_to_LMS, self.M_LMS_p_to_Iab) 

252 

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

254 Iab = np.tile(Iab, (6, 1)) 

255 np.testing.assert_allclose( 

256 XYZ_to_Iab(XYZ, self.LMS_to_LMS_p, self.M_XYZ_to_LMS, self.M_LMS_p_to_Iab), 

257 Iab, 

258 atol=TOLERANCE_ABSOLUTE_TESTS, 

259 ) 

260 

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

262 Iab = np.reshape(Iab, (2, 3, 3)) 

263 np.testing.assert_allclose( 

264 XYZ_to_Iab(XYZ, self.LMS_to_LMS_p, self.M_XYZ_to_LMS, self.M_LMS_p_to_Iab), 

265 Iab, 

266 atol=TOLERANCE_ABSOLUTE_TESTS, 

267 ) 

268 

269 def test_domain_range_scale_XYZ_to_Iab(self) -> None: 

270 """ 

271 Test :func:`colour.models.common.XYZ_to_Iab` definition domain and 

272 range scale support. 

273 """ 

274 

275 XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) 

276 Iab = XYZ_to_Iab(XYZ, self.LMS_to_LMS_p, self.M_XYZ_to_LMS, self.M_LMS_p_to_Iab) 

277 

278 d_r = (("reference", 1), ("1", 1), ("100", 100)) 

279 for scale, factor in d_r: 

280 with domain_range_scale(scale): 

281 np.testing.assert_allclose( 

282 XYZ_to_Iab( 

283 XYZ * factor, 

284 self.LMS_to_LMS_p, 

285 self.M_XYZ_to_LMS, 

286 self.M_LMS_p_to_Iab, 

287 ), 

288 Iab * factor, 

289 atol=TOLERANCE_ABSOLUTE_TESTS, 

290 ) 

291 

292 @ignore_numpy_errors 

293 def test_nan_XYZ_to_Iab(self) -> None: 

294 """Test :func:`colour.models.common.XYZ_to_Iab` definition nan support.""" 

295 

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

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

298 XYZ_to_Iab(cases, self.LMS_to_LMS_p, self.M_XYZ_to_LMS, self.M_LMS_p_to_Iab) 

299 

300 

301class TestIab_to_XYZ: 

302 """ 

303 Define :func:`colour.models.common.Iab_to_XYZ` definition unit tests 

304 methods. 

305 """ 

306 

307 def setup_method(self) -> None: 

308 """Initialise the common tests attributes.""" 

309 

310 self.LMS_p_to_LMS = lambda x: x ** (1 / 0.43) 

311 self.M_Iab_to_LMS_p = np.linalg.inv( 

312 np.array( 

313 [ 

314 [0.4000, 0.4000, 0.2000], 

315 [4.4550, -4.8510, 0.3960], 

316 [0.8056, 0.3572, -1.1628], 

317 ] 

318 ) 

319 ) 

320 self.M_LMS_to_XYZ = np.linalg.inv( 

321 np.array( 

322 [ 

323 [0.4002, 0.7075, -0.0807], 

324 [-0.2280, 1.1500, 0.0612], 

325 [0.0000, 0.0000, 0.9184], 

326 ] 

327 ) 

328 ) 

329 

330 def test_Iab_to_XYZ(self) -> None: 

331 """Test :func:`colour.models.common.Iab_to_XYZ` definition.""" 

332 

333 np.testing.assert_allclose( 

334 Iab_to_XYZ( 

335 np.array([0.38426191, 0.38487306, 0.18886838]), 

336 self.LMS_p_to_LMS, 

337 self.M_Iab_to_LMS_p, 

338 self.M_LMS_to_XYZ, 

339 ), 

340 np.array([0.20654008, 0.12197225, 0.05136952]), 

341 atol=TOLERANCE_ABSOLUTE_TESTS, 

342 ) 

343 

344 np.testing.assert_allclose( 

345 Iab_to_XYZ( 

346 np.array([0.49437481, -0.19251742, 0.18080304]), 

347 self.LMS_p_to_LMS, 

348 self.M_Iab_to_LMS_p, 

349 self.M_LMS_to_XYZ, 

350 ), 

351 np.array([0.14222010, 0.23042768, 0.10495772]), 

352 atol=TOLERANCE_ABSOLUTE_TESTS, 

353 ) 

354 

355 np.testing.assert_allclose( 

356 Iab_to_XYZ( 

357 np.array([0.35167774, -0.07525627, -0.30921279]), 

358 self.LMS_p_to_LMS, 

359 self.M_Iab_to_LMS_p, 

360 self.M_LMS_to_XYZ, 

361 ), 

362 np.array([0.07818780, 0.06157201, 0.28099326]), 

363 atol=TOLERANCE_ABSOLUTE_TESTS, 

364 ) 

365 

366 def test_n_dimensional_Iab_to_XYZ(self) -> None: 

367 """ 

368 Test :func:`colour.models.common.Iab_to_XYZ` definition n-dimensional 

369 support. 

370 """ 

371 

372 Iab = np.array([0.38426191, 0.38487306, 0.18886838]) 

373 XYZ = Iab_to_XYZ(Iab, self.LMS_p_to_LMS, self.M_Iab_to_LMS_p, self.M_LMS_to_XYZ) 

374 

375 Iab = np.tile(Iab, (6, 1)) 

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

377 np.testing.assert_allclose( 

378 Iab_to_XYZ(Iab, self.LMS_p_to_LMS, self.M_Iab_to_LMS_p, self.M_LMS_to_XYZ), 

379 XYZ, 

380 atol=TOLERANCE_ABSOLUTE_TESTS, 

381 ) 

382 

383 Iab = np.reshape(Iab, (2, 3, 3)) 

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

385 np.testing.assert_allclose( 

386 Iab_to_XYZ(Iab, self.LMS_p_to_LMS, self.M_Iab_to_LMS_p, self.M_LMS_to_XYZ), 

387 XYZ, 

388 atol=TOLERANCE_ABSOLUTE_TESTS, 

389 ) 

390 

391 def test_domain_range_scale_Iab_to_XYZ(self) -> None: 

392 """ 

393 Test :func:`colour.models.common.Iab_to_XYZ` definition domain and 

394 range scale support. 

395 """ 

396 

397 Iab = np.array([0.38426191, 0.38487306, 0.18886838]) 

398 XYZ = Iab_to_XYZ(Iab, self.LMS_p_to_LMS, self.M_Iab_to_LMS_p, self.M_LMS_to_XYZ) 

399 

400 d_r = (("reference", 1), ("1", 1), ("100", 100)) 

401 for scale, factor in d_r: 

402 with domain_range_scale(scale): 

403 np.testing.assert_allclose( 

404 Iab_to_XYZ( 

405 Iab * factor, 

406 self.LMS_p_to_LMS, 

407 self.M_Iab_to_LMS_p, 

408 self.M_LMS_to_XYZ, 

409 ), 

410 XYZ * factor, 

411 atol=TOLERANCE_ABSOLUTE_TESTS, 

412 ) 

413 

414 @ignore_numpy_errors 

415 def test_nan_Iab_to_XYZ(self) -> None: 

416 """Test :func:`colour.models.common.Iab_to_XYZ` definition nan support.""" 

417 

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

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

420 Iab_to_XYZ(cases, self.LMS_p_to_LMS, self.M_Iab_to_LMS_p, self.M_LMS_to_XYZ)