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

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

2

3 from __future__ import annotations

4

5 import os

6 import shutil

7 import tempfile

8

9 import numpy as np

10

11 from colour.characterisation import SDS_COLOURCHECKERS

12 from colour.colorimetry import (

13 MSDS_CMFS,

14 SDS_ILLUMINANTS,

15 SpectralShape,

16 sds_and_msds_to_msds,

17)

18 from colour.constants import CONSTANT_LIGHT_SPEED, TOLERANCE_ABSOLUTE_TESTS

19 from colour.hints import NDArrayFloat, cast

20 from 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)

27 from 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

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

52

53

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

82 class 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.\

91 sd_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

100 class 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.\

109 spectrum_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

129 class 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.\

138 sds_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

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

287 def _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

345 def _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

378 def _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

392 def _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

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

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