Coverage for io/tests/test_fichet2021.py: 100%

144 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 22:49 +1300

1"""Define the unit tests for the :mod:`colour.io.fichet2021` module.""" 

2 

3from __future__ import annotations 

4 

5import os 

6import shutil 

7import tempfile 

8 

9import numpy as np 

10 

11from colour.characterisation import SDS_COLOURCHECKERS 

12from colour.colorimetry import ( 

13 MSDS_CMFS, 

14 SDS_ILLUMINANTS, 

15 SpectralShape, 

16 sds_and_msds_to_msds, 

17) 

18from colour.constants import CONSTANT_LIGHT_SPEED, TOLERANCE_ABSOLUTE_TESTS 

19from colour.hints import NDArrayFloat, cast 

20from colour.io import ( 

21 Specification_Fichet2021, 

22 read_spectral_image_Fichet2021, 

23 sd_to_spectrum_attribute_Fichet2021, 

24 spectrum_attribute_to_sd_Fichet2021, 

25 write_spectral_image_Fichet2021, 

26) 

27from colour.io.fichet2021 import ( 

28 components_to_sRGB_Fichet2021, 

29 match_groups_to_nm, 

30 sds_and_msds_to_components_Fichet2021, 

31) 

32 

33__author__ = "Colour Developers" 

34__copyright__ = "Copyright 2013 Colour Developers" 

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

36__maintainer__ = "Colour Developers" 

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

38__status__ = "Production" 

39 

40__all__ = [ 

41 "ROOT_RESOURCES", 

42 "TestMatchGroupsToNm", 

43 "TestSdToSpectrumAttributeFichet2021", 

44 "TestSpectrumAttributeToSdFichet2021", 

45 "TestSdsAndMsdsToComponentsFichet2021", 

46 "TestComponentsToSRGBFichet2021", 

47 "TestReadSpectralImageFichet2021", 

48 "TestWriteSpectralImageFichet2021", 

49] 

50 

51ROOT_RESOURCES: str = os.path.join(os.path.dirname(__file__), "resources") 

52 

53 

54class TestMatchGroupsToNm: 

55 """ 

56 Define :func:`colour.io.fichet2021.match_groups_to_nm` definition unit 

57 tests methods. 

58 """ 

59 

60 def test_match_groups_to_nm(self) -> None: 

61 """Test :func:`colour.io.fichet2021.match_groups_to_nm` definition.""" 

62 

63 np.testing.assert_allclose( 

64 match_groups_to_nm("555.5", "n", "m"), 

65 555.5, 

66 atol=TOLERANCE_ABSOLUTE_TESTS, 

67 ) 

68 

69 np.testing.assert_allclose( 

70 match_groups_to_nm("555.5", "", "m"), 

71 555500000000.0, 

72 atol=TOLERANCE_ABSOLUTE_TESTS, 

73 ) 

74 

75 np.testing.assert_allclose( 

76 match_groups_to_nm(str(CONSTANT_LIGHT_SPEED / (555 * 1e-9)), "", "Hz"), 

77 555.0, 

78 atol=TOLERANCE_ABSOLUTE_TESTS, 

79 ) 

80 

81 

82class TestSdToSpectrumAttributeFichet2021: 

83 """ 

84 Define :func:`colour.io.fichet2021.sd_to_spectrum_attribute_Fichet2021` 

85 definition unit tests methods. 

86 """ 

87 

88 def test_sd_to_spectrum_attribute_Fichet2021(self) -> None: 

89 """ 

90 Test :func:`colour.io.fichet2021.\ 

91sd_to_spectrum_attribute_Fichet2021` definition. 

92 """ 

93 

94 assert ( 

95 sd_to_spectrum_attribute_Fichet2021(SDS_ILLUMINANTS["D65"], 2)[:56] 

96 == "300.00nm:0.03;305.00nm:1.66;310.00nm:3.29;315.00nm:11.77" 

97 ) 

98 

99 

100class TestSpectrumAttributeToSdFichet2021: 

101 """ 

102 Define :func:`colour.io.fichet2021.spectrum_attribute_to_sd_Fichet2021` 

103 definition unit tests methods. 

104 """ 

105 

106 def test_spectrum_attribute_to_sd_Fichet2021(self) -> None: 

107 """ 

108 Test :func:`colour.io.fichet2021.\ 

109spectrum_attribute_to_sd_Fichet2021` definition. 

110 """ 

111 

112 sd = spectrum_attribute_to_sd_Fichet2021( 

113 "300.00nm:0.03;305.00nm:1.66;310.00nm:3.29;315.00nm:11.77" 

114 ) 

115 

116 np.testing.assert_allclose( 

117 sd.wavelengths, 

118 np.array([300.0, 305.0, 310.0, 315.0]), 

119 atol=TOLERANCE_ABSOLUTE_TESTS, 

120 ) 

121 

122 np.testing.assert_allclose( 

123 sd.values, 

124 np.array([0.03, 1.66, 3.29, 11.77]), 

125 atol=TOLERANCE_ABSOLUTE_TESTS, 

126 ) 

127 

128 

129class TestSdsAndMsdsToComponentsFichet2021: 

130 """ 

131 Define :func:`colour.io.fichet2021.sds_and_msds_to_components_Fichet2021` 

132 definition unit tests methods. 

133 """ 

134 

135 def test_sds_and_msds_to_components_Fichet2021(self) -> None: 

136 """ 

137 Test :func:`colour.io.fichet2021.\ 

138sds_and_msds_to_components_Fichet2021` definition. 

139 """ 

140 

141 components = sds_and_msds_to_components_Fichet2021(SDS_ILLUMINANTS["D65"]) 

142 

143 assert "T" in components 

144 

145 components = sds_and_msds_to_components_Fichet2021( 

146 SDS_ILLUMINANTS["D65"], Specification_Fichet2021(is_emissive=True) 

147 ) 

148 

149 assert "S0" in components 

150 

151 np.testing.assert_allclose( 

152 components["S0"][0], 

153 SDS_ILLUMINANTS["D65"].wavelengths, 

154 atol=TOLERANCE_ABSOLUTE_TESTS, 

155 ) 

156 

157 np.testing.assert_allclose( 

158 components["S0"][1], 

159 np.reshape(SDS_ILLUMINANTS["D65"].values, (1, 1, -1)), 

160 atol=TOLERANCE_ABSOLUTE_TESTS, 

161 ) 

162 

163 components = sds_and_msds_to_components_Fichet2021( 

164 list(SDS_COLOURCHECKERS["ColorChecker N Ohta"].values()) 

165 ) 

166 

167 assert components["T"][1].shape == (1, 24, 81) 

168 

169 

170class TestComponentsToSRGBFichet2021: 

171 """ 

172 Define :func:`colour.io.fichet2021.components_to_sRGB_Fichet2021` 

173 definition unit tests methods. 

174 """ 

175 

176 def test_components_to_sRGB_Fichet2021(self) -> None: 

177 """ 

178 Test :func:`colour.io.fichet2021.components_to_sRGB_Fichet2021` 

179 definition. 

180 """ 

181 

182 specification = Specification_Fichet2021(is_emissive=True) 

183 components = sds_and_msds_to_components_Fichet2021( 

184 SDS_ILLUMINANTS["D65"], specification 

185 ) 

186 RGB, attributes = components_to_sRGB_Fichet2021(components, specification) 

187 

188 np.testing.assert_allclose( 

189 cast("NDArrayFloat", RGB), 

190 np.array([[[0.17998291, 0.18000802, 0.18000908]]]), 

191 atol=TOLERANCE_ABSOLUTE_TESTS, 

192 ) 

193 

194 assert [attribute.name for attribute in attributes] == [ 

195 "X", 

196 "Y", 

197 "Z", 

198 "illuminant", 

199 "chromaticities", 

200 "EV", 

201 ] 

202 

203 for attribute in attributes: 

204 if attribute.name == "X": 

205 sd_X = spectrum_attribute_to_sd_Fichet2021(attribute.value) 

206 np.testing.assert_allclose( 

207 sd_X.values, 

208 MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] 

209 .signals["x_bar"] 

210 .values, 

211 atol=TOLERANCE_ABSOLUTE_TESTS, 

212 ) 

213 elif attribute.name == "illuminant": 

214 sd_illuminant = spectrum_attribute_to_sd_Fichet2021(attribute.value) 

215 np.testing.assert_allclose( 

216 sd_illuminant.values, 

217 SDS_ILLUMINANTS["E"].values, 

218 atol=TOLERANCE_ABSOLUTE_TESTS, 

219 ) 

220 elif attribute.name == "chromaticities": 

221 assert attribute.value == [ 

222 0.64, 

223 0.33, 

224 0.3, 

225 0.6, 

226 0.15, 

227 0.06, 

228 0.3127, 

229 0.329, 

230 ] 

231 

232 specification = Specification_Fichet2021(is_emissive=False) 

233 components = sds_and_msds_to_components_Fichet2021( 

234 list(SDS_COLOURCHECKERS["ColorChecker N Ohta"].values()), specification 

235 ) 

236 RGB, attributes = components_to_sRGB_Fichet2021(components, specification) 

237 

238 np.testing.assert_allclose( 

239 cast("NDArrayFloat", RGB), 

240 np.array( 

241 [ 

242 [ 

243 [0.17617566, 0.07822266, 0.05031637], 

244 [0.55943028, 0.30875974, 0.22283237], 

245 [0.11315875, 0.19922170, 0.33614049], 

246 [0.09458646, 0.14840988, 0.04988729], 

247 [0.23628263, 0.22587419, 0.44382286], 

248 [0.13383963, 0.51702099, 0.40286142], 

249 [0.70140973, 0.19925074, 0.02292392], 

250 [0.06838428, 0.10600215, 0.37710859], 

251 [0.55811797, 0.09062764, 0.12199424], 

252 [0.10779019, 0.04434715, 0.14682113], 

253 [0.34888054, 0.50195490, 0.04773998], 

254 [0.79166868, 0.36502900, 0.02678776], 

255 [0.02722027, 0.04781536, 0.30913913], 

256 [0.06013188, 0.30558427, 0.06062012], 

257 [0.44611192, 0.02849786, 0.04207225], 

258 [0.85188200, 0.57960585, 0.01053590], 

259 [0.50608734, 0.08898812, 0.29720873], 

260 [-0.03338628, 0.24880620, 0.38541145], 

261 [0.88687341, 0.88867240, 0.87460352], 

262 [0.58637305, 0.58330907, 0.58216473], 

263 [0.35827233, 0.35810703, 0.35873042], 

264 [0.20316001, 0.20298624, 0.20353015], 

265 [0.09106388, 0.09288101, 0.09424415], 

266 [0.03266569, 0.03364008, 0.03526672], 

267 ] 

268 ] 

269 ), 

270 atol=TOLERANCE_ABSOLUTE_TESTS, 

271 ) 

272 

273 assert [attribute.name for attribute in attributes] == [ 

274 "X", 

275 "Y", 

276 "Z", 

277 "illuminant", 

278 "chromaticities", 

279 ] 

280 

281 components = {} 

282 RGB, attributes = components_to_sRGB_Fichet2021(components, specification) 

283 assert RGB is None 

284 assert attributes == [] 

285 

286 

287def _test_spectral_image_D65(path: str) -> None: 

288 """Test the *D65* spectral image.""" 

289 

290 components = read_spectral_image_Fichet2021(path, additional_data=False) 

291 

292 assert "S0" in components 

293 

294 np.testing.assert_allclose( 

295 components["S0"][0], 

296 SDS_ILLUMINANTS["D65"].wavelengths, 

297 atol=TOLERANCE_ABSOLUTE_TESTS, 

298 ) 

299 

300 np.testing.assert_allclose( 

301 components["S0"][1], 

302 np.reshape(SDS_ILLUMINANTS["D65"].values, (1, 1, -1)), 

303 atol=0.05, 

304 ) 

305 

306 components, specification = read_spectral_image_Fichet2021( 

307 os.path.join(ROOT_RESOURCES, "D65.exr"), additional_data=True 

308 ) 

309 

310 assert specification.is_emissive is True 

311 assert specification.is_polarised is False 

312 assert specification.is_bispectral is False 

313 

314 attribute_names = [attribute.name for attribute in specification.attributes] 

315 

316 for attribute_name in [ 

317 "EV", 

318 "X", 

319 "Y", 

320 "Z", 

321 "chromaticities", 

322 "emissiveUnits", 

323 "illuminant", 

324 "polarisationHandedness", 

325 "spectralLayoutVersion", 

326 ]: 

327 assert attribute_name in attribute_names 

328 

329 for attribute in specification.attributes: 

330 if attribute.name == "spectralLayoutVersion": 

331 assert attribute.value == "1.0" 

332 elif attribute.name == "polarisationHandedness": 

333 assert attribute.value == "right" 

334 elif attribute.name == "emissiveUnits": 

335 assert attribute.value == "W.m^-2.sr^-1" 

336 elif attribute.name == "illuminant": 

337 sd_illuminant = spectrum_attribute_to_sd_Fichet2021(attribute.value) 

338 np.testing.assert_allclose( 

339 sd_illuminant.values, 

340 SDS_ILLUMINANTS["D65"].values, 

341 atol=TOLERANCE_ABSOLUTE_TESTS, 

342 ) 

343 

344 

345def _test_spectral_image_Ohta1997(path: str) -> None: 

346 """Test the *Ohta (1997)* spectral image.""" 

347 

348 components, specification = read_spectral_image_Fichet2021( 

349 path, additional_data=True 

350 ) 

351 

352 assert "T" in components 

353 

354 msds = sds_and_msds_to_msds( 

355 [ 

356 sd.copy().align(SpectralShape(400, 700, 20)) 

357 for sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].values() 

358 ] 

359 ) 

360 

361 np.testing.assert_allclose( 

362 components["T"][0], 

363 msds.wavelengths, 

364 atol=TOLERANCE_ABSOLUTE_TESTS, 

365 ) 

366 

367 np.testing.assert_allclose( 

368 components["T"][1], 

369 np.reshape(np.transpose(msds.values), (4, 6, -1)), 

370 atol=0.0005, 

371 ) 

372 

373 assert specification.is_emissive is False 

374 assert specification.is_polarised is False 

375 assert specification.is_bispectral is False 

376 

377 

378def _test_spectral_image_Polarised(path: str) -> None: 

379 """Test the *Polarised* spectral image.""" 

380 

381 components, specification = read_spectral_image_Fichet2021( 

382 path, additional_data=True 

383 ) 

384 

385 assert list(components.keys()) == ["S0", "S1", "S2", "S3"] 

386 

387 assert specification.is_emissive is True 

388 assert specification.is_polarised is True 

389 assert specification.is_bispectral is False 

390 

391 

392def _test_spectral_image_BiSpectral(path: str) -> None: 

393 """Test the *Bi-Spectral* image.""" 

394 

395 components, specification = read_spectral_image_Fichet2021( 

396 path, additional_data=True 

397 ) 

398 

399 assert list(components.keys()) == [ 

400 "T", 

401 380.0, 

402 390.0, 

403 400.0, 

404 410.0, 

405 420.0, 

406 430.0, 

407 440.0, 

408 450.0, 

409 460.0, 

410 470.0, 

411 480.0, 

412 490.0, 

413 500.0, 

414 510.0, 

415 520.0, 

416 530.0, 

417 540.0, 

418 550.0, 

419 560.0, 

420 570.0, 

421 580.0, 

422 590.0, 

423 600.0, 

424 610.0, 

425 620.0, 

426 630.0, 

427 640.0, 

428 650.0, 

429 660.0, 

430 670.0, 

431 680.0, 

432 690.0, 

433 700.0, 

434 710.0, 

435 720.0, 

436 730.0, 

437 740.0, 

438 750.0, 

439 760.0, 

440 770.0, 

441 ] 

442 

443 assert specification.is_emissive is False 

444 assert specification.is_polarised is False 

445 assert specification.is_bispectral is True 

446 

447 

448class TestReadSpectralImageFichet2021: 

449 """ 

450 Define :func:`colour.io.fichet2021.read_spectral_image_Fichet2021` 

451 definition unit tests methods. 

452 """ 

453 

454 def test_read_spectral_image_Fichet2021(self) -> None: 

455 """ 

456 Test :func:`colour.io.fichet2021.read_spectral_image_Fichet2021` 

457 definition. 

458 """ 

459 

460 _test_spectral_image_D65(os.path.join(ROOT_RESOURCES, "D65.exr")) 

461 

462 _test_spectral_image_Ohta1997(os.path.join(ROOT_RESOURCES, "Ohta1997.exr")) 

463 

464 _test_spectral_image_Polarised(os.path.join(ROOT_RESOURCES, "Polarised.exr")) 

465 

466 _test_spectral_image_BiSpectral(os.path.join(ROOT_RESOURCES, "BiSpectral.exr")) 

467 

468 

469class TestWriteSpectralImageFichet2021: 

470 """ 

471 Define :func:`colour.io.fichet2021.write_spectral_image_Fichet2021` 

472 definition unit tests methods. 

473 """ 

474 

475 def setup_method(self) -> None: 

476 """Initialise the common tests attributes.""" 

477 

478 self._temporary_directory = tempfile.mkdtemp() 

479 

480 def teardown_method(self) -> None: 

481 """After tests actions.""" 

482 

483 shutil.rmtree(self._temporary_directory) 

484 

485 def test_write_spectral_image_Fichet2021(self) -> None: 

486 """ 

487 Test :func:`colour.io.fichet2021.write_spectral_image_Fichet2021` 

488 definition. 

489 """ 

490 

491 path = os.path.join(self._temporary_directory, "D65.exr") 

492 specification = Specification_Fichet2021(is_emissive=True) 

493 write_spectral_image_Fichet2021( 

494 SDS_ILLUMINANTS["D65"], path, "float16", specification 

495 ) 

496 _test_spectral_image_D65(path) 

497 

498 path = os.path.join(self._temporary_directory, "D65.exr") 

499 msds = [ 

500 sd.copy().align(SpectralShape(400, 700, 20)) 

501 for sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].values() 

502 ] 

503 specification = Specification_Fichet2021(is_emissive=False) 

504 write_spectral_image_Fichet2021( 

505 msds, path, "float16", specification, shape=(4, 6, 16) 

506 ) 

507 _test_spectral_image_Ohta1997(path) 

508 

509 for basename, test_callable in [ 

510 ("Polarised.exr", _test_spectral_image_Polarised), 

511 ("BiSpectral.exr", _test_spectral_image_BiSpectral), 

512 ]: 

513 components, specification = read_spectral_image_Fichet2021( 

514 os.path.join(ROOT_RESOURCES, basename), additional_data=True 

515 ) 

516 path = os.path.join(self._temporary_directory, basename) 

517 write_spectral_image_Fichet2021(components, path, "float16", specification) 

518 test_callable(path) 

519 

520 # Test with specification.attributes = None for both emissive and non-emissive 

521 specification_emissive = Specification_Fichet2021(is_emissive=True) 

522 specification_emissive.attributes = None 

523 path = os.path.join(self._temporary_directory, "D65_no_attrs.exr") 

524 write_spectral_image_Fichet2021( 

525 SDS_ILLUMINANTS["D65"], path, "float16", specification_emissive 

526 ) 

527 

528 specification_non_emissive = Specification_Fichet2021(is_emissive=False) 

529 specification_non_emissive.attributes = None 

530 path = os.path.join(self._temporary_directory, "Ohta_no_attrs.exr") 

531 write_spectral_image_Fichet2021( 

532 msds, path, "float16", specification_non_emissive, shape=(4, 6, 16) 

533 )