Coverage for io/tests/test_tm2714.py: 100%
101 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« 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.tm2714` module."""
3from __future__ import annotations
5import os
6import re
7import shutil
8import tempfile
9import textwrap
10import typing
11from copy import deepcopy
13import numpy as np
14import pytest
16from colour.colorimetry import SpectralDistribution
17from colour.constants import TOLERANCE_ABSOLUTE_TESTS
19if typing.TYPE_CHECKING:
20 from colour.hints import List, Tuple
22from colour.hints import cast
23from colour.io.tm2714 import Header_IESTM2714, SpectralDistribution_IESTM2714
24from colour.utilities import is_scipy_installed, optional
26__author__ = "Colour Developers"
27__copyright__ = "Copyright 2013 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"
33__all__ = [
34 "ROOT_RESOURCES",
35 "FLUORESCENT_FILE_HEADER",
36 "FLUORESCENT_FILE_SPECTRAL_DESCRIPTION",
37 "FLUORESCENT_FILE_SPECTRAL_DATA",
38 "TestIES_TM2714_Header",
39 "TestIES_TM2714_Sd",
40]
42ROOT_RESOURCES: str = os.path.join(os.path.dirname(__file__), "resources")
44FLUORESCENT_FILE_HEADER: dict = {
45 "Manufacturer": "Unknown",
46 "CatalogNumber": "N/A",
47 "Description": "Rare earth fluorescent lamp",
48 "DocumentCreator": "byHeart Consultants",
49 "Laboratory": "N/A",
50 "UniqueIdentifier": "C3567553-C75B-4354-961E-35CEB9FEB42C",
51 "ReportNumber": "N/A",
52 "ReportDate": "N/A",
53 "DocumentCreationDate": "2014-06-23",
54 "Comments": "Ambient temperature 25 degrees C.",
55}
57FLUORESCENT_FILE_SPECTRAL_DESCRIPTION: dict = {
58 "SpectralQuantity": "relative",
59 "BandwidthFWHM": 2.0,
60 "BandwidthCorrected": True,
61}
63FLUORESCENT_FILE_SPECTRAL_DATA: dict = {
64 400.0: 0.034,
65 403.1: 0.037,
66 405.5: 0.069,
67 407.5: 0.037,
68 420.6: 0.042,
69 431.0: 0.049,
70 433.7: 0.060,
71 437.0: 0.357,
72 438.9: 0.060,
73 460.0: 0.068,
74 477.0: 0.075,
75 481.0: 0.085,
76 488.2: 0.204,
77 492.6: 0.166,
78 501.7: 0.095,
79 507.6: 0.078,
80 517.6: 0.071,
81 529.9: 0.076,
82 535.4: 0.099,
83 539.9: 0.423,
84 543.2: 0.802,
85 544.4: 0.713,
86 547.2: 0.999,
87 548.7: 0.573,
88 550.2: 0.340,
89 553.8: 0.208,
90 557.3: 0.139,
91 563.7: 0.129,
92 574.8: 0.131,
93 578.0: 0.198,
94 579.2: 0.190,
95 580.4: 0.205,
96 584.8: 0.244,
97 585.9: 0.236,
98 587.5: 0.256,
99 590.3: 0.180,
100 593.5: 0.218,
101 595.5: 0.159,
102 597.0: 0.147,
103 599.4: 0.170,
104 602.2: 0.134,
105 604.6: 0.121,
106 607.4: 0.140,
107 609.4: 0.229,
108 610.2: 0.465,
109 612.0: 0.952,
110 614.6: 0.477,
111 616.9: 0.208,
112 618.5: 0.135,
113 622.1: 0.150,
114 625.6: 0.155,
115 628.4: 0.134,
116 631.2: 0.168,
117 633.2: 0.087,
118 635.6: 0.068,
119 642.7: 0.058,
120 648.7: 0.058,
121 650.7: 0.074,
122 652.6: 0.063,
123 656.2: 0.053,
124 657.0: 0.056,
125 660.6: 0.049,
126 662.6: 0.059,
127 664.2: 0.048,
128 686.0: 0.041,
129 687.6: 0.048,
130 689.2: 0.039,
131 692.4: 0.038,
132 693.5: 0.044,
133 695.5: 0.034,
134 702.3: 0.036,
135 706.7: 0.042,
136 707.1: 0.061,
137 710.2: 0.061,
138 711.0: 0.041,
139 712.2: 0.052,
140 714.2: 0.033,
141 748.4: 0.034,
142 757.9: 0.031,
143 760.7: 0.039,
144 763.9: 0.029,
145 808.8: 0.029,
146 810.7: 0.039,
147 812.7: 0.030,
148 850.1: 0.030,
149}
152class TestIES_TM2714_Header:
153 """
154 Define :class:`colour.io.tm2714.Header_IESTM2714` class unit tests
155 methods.
156 """
158 def setup_method(self) -> None:
159 """Initialise the common tests attributes."""
161 self._header = Header_IESTM2714(
162 manufacturer="a",
163 catalog_number="b",
164 description="c",
165 document_creator="d",
166 unique_identifier="e",
167 measurement_equipment="f",
168 laboratory="g",
169 report_number="h",
170 report_date="i",
171 document_creation_date="j",
172 comments="k",
173 )
175 def test_required_attributes(self) -> None:
176 """Test the presence of required attributes."""
178 required_attributes = (
179 "mapping",
180 "manufacturer",
181 "catalog_number",
182 "description",
183 "document_creator",
184 "unique_identifier",
185 "measurement_equipment",
186 "laboratory",
187 "report_number",
188 "report_date",
189 "document_creation_date",
190 "comments",
191 )
193 for attribute in required_attributes:
194 assert attribute in dir(Header_IESTM2714)
196 def test_required_methods(self) -> None:
197 """Test the presence of required methods."""
199 required_methods = (
200 "__init__",
201 "__str__",
202 "__repr__",
203 "__hash__",
204 "__eq__",
205 "__ne__",
206 )
208 for method in required_methods:
209 assert method in dir(Header_IESTM2714)
211 def test__str__(self) -> None:
212 """Test :meth:`colour.io.tm2714.Header_IESTM2714.__str__` method."""
214 assert str(self._header) == (
215 textwrap.dedent(
216 """
217 Manufacturer : a
218 Catalog Number : b
219 Description : c
220 Document Creator : d
221 Unique Identifier : e
222 Measurement Equipment : f
223 Laboratory : g
224 Report Number : h
225 Report Date : i
226 Document Creation Date : j
227 Comments : k
228 """
229 ).strip()
230 )
232 def test__repr__(self) -> None:
233 """Test :meth:`colour.io.tm2714.Header_IESTM2714.__repr__` method."""
235 if not is_scipy_installed(): # pragma: no cover
236 return
238 assert repr(self._header) == (
239 textwrap.dedent(
240 """
241 Header_IESTM2714('a',
242 'b',
243 'c',
244 'd',
245 'e',
246 'f',
247 'g',
248 'h',
249 'i',
250 'j',
251 'k')
252 """
253 ).strip()
254 )
256 def test__eq__(self) -> None:
257 """Test :meth:`colour.io.tm2714.Header_IESTM2714.__eq__` method."""
259 header = deepcopy(self._header)
261 assert self._header == header
263 assert self._header != ()
265 def test__ne__(self) -> None:
266 """Test :meth:`colour.io.tm2714.Header_IESTM2714.__ne__` method."""
268 header = deepcopy(self._header)
270 header.manufacturer = "aa"
271 assert self._header != header
273 header.manufacturer = "a"
274 assert self._header == header
276 def test__hash__(self) -> None:
277 """Test :meth:`colour.io.tm2714.Header_IESTM2714.__hash__` method."""
279 assert isinstance(hash(self._header), int)
282class TestIES_TM2714_Sd:
283 """
284 Define :class:`colour.io.tm2714.SpectralDistribution_IESTM2714` class unit
285 tests methods.
286 """
288 def setup_method(self) -> None:
289 """Initialise the common tests attributes."""
291 self._temporary_directory = tempfile.mkdtemp()
293 self._sd = SpectralDistribution_IESTM2714(
294 os.path.join(ROOT_RESOURCES, "Fluorescent.spdx")
295 ).read()
297 def teardown_method(self) -> None:
298 """After tests actions."""
300 shutil.rmtree(self._temporary_directory)
302 def test_required_attributes(self) -> None:
303 """Test the presence of required attributes."""
305 required_attributes = (
306 "mapping",
307 "path",
308 "header",
309 "spectral_quantity",
310 "reflection_geometry",
311 "transmission_geometry",
312 "bandwidth_FWHM",
313 "bandwidth_corrected",
314 )
316 for attribute in required_attributes:
317 assert attribute in dir(SpectralDistribution_IESTM2714)
319 def test_required_methods(self) -> None:
320 """Test the presence of required methods."""
322 required_methods = ("__init__", "__str__", "__repr__", "read", "write")
324 for method in required_methods:
325 assert method in dir(SpectralDistribution_IESTM2714)
327 def test__str__(self) -> None:
328 """
329 Test :meth:`colour.io.tm2714.SpectralDistribution_IESTM2714.__str__`
330 method.
331 """
333 assert re.sub(
334 "Path :.*",
335 "Path :",
336 str(self._sd),
337 ) == (
338 textwrap.dedent(
339 """
340 IES TM-27-14 Spectral Distribution
341 ==================================
343 Path :
344 Spectral Quantity : relative
345 Reflection Geometry : other
346 Transmission Geometry : other
347 Bandwidth (FWHM) : 2.0
348 Bandwidth Corrected : True
350 Header
351 ------
353 Manufacturer : Unknown
354 Catalog Number : N/A
355 Description : Rare earth fluorescent lamp
356 Document Creator : byHeart Consultants
357 Unique Identifier : C3567553-C75B-4354-961E-35CEB9FEB42C
358 Measurement Equipment : None
359 Laboratory : N/A
360 Report Number : N/A
361 Report Date : N/A
362 Document Creation Date : 2014-06-23
363 Comments : Ambient temperature 25 degrees C.
365 Spectral Data
366 -------------
368 [[ 4.00000000e+02 3.40000000e-02]
369 [ 4.03100000e+02 3.70000000e-02]
370 [ 4.05500000e+02 6.90000000e-02]
371 [ 4.07500000e+02 3.70000000e-02]
372 [ 4.20600000e+02 4.20000000e-02]
373 [ 4.31000000e+02 4.90000000e-02]
374 [ 4.33700000e+02 6.00000000e-02]
375 [ 4.37000000e+02 3.57000000e-01]
376 [ 4.38900000e+02 6.00000000e-02]
377 [ 4.60000000e+02 6.80000000e-02]
378 [ 4.77000000e+02 7.50000000e-02]
379 [ 4.81000000e+02 8.50000000e-02]
380 [ 4.88200000e+02 2.04000000e-01]
381 [ 4.92600000e+02 1.66000000e-01]
382 [ 5.01700000e+02 9.50000000e-02]
383 [ 5.07600000e+02 7.80000000e-02]
384 [ 5.17600000e+02 7.10000000e-02]
385 [ 5.29900000e+02 7.60000000e-02]
386 [ 5.35400000e+02 9.90000000e-02]
387 [ 5.39900000e+02 4.23000000e-01]
388 [ 5.43200000e+02 8.02000000e-01]
389 [ 5.44400000e+02 7.13000000e-01]
390 [ 5.47200000e+02 9.99000000e-01]
391 [ 5.48700000e+02 5.73000000e-01]
392 [ 5.50200000e+02 3.40000000e-01]
393 [ 5.53800000e+02 2.08000000e-01]
394 [ 5.57300000e+02 1.39000000e-01]
395 [ 5.63700000e+02 1.29000000e-01]
396 [ 5.74800000e+02 1.31000000e-01]
397 [ 5.78000000e+02 1.98000000e-01]
398 [ 5.79200000e+02 1.90000000e-01]
399 [ 5.80400000e+02 2.05000000e-01]
400 [ 5.84800000e+02 2.44000000e-01]
401 [ 5.85900000e+02 2.36000000e-01]
402 [ 5.87500000e+02 2.56000000e-01]
403 [ 5.90300000e+02 1.80000000e-01]
404 [ 5.93500000e+02 2.18000000e-01]
405 [ 5.95500000e+02 1.59000000e-01]
406 [ 5.97000000e+02 1.47000000e-01]
407 [ 5.99400000e+02 1.70000000e-01]
408 [ 6.02200000e+02 1.34000000e-01]
409 [ 6.04600000e+02 1.21000000e-01]
410 [ 6.07400000e+02 1.40000000e-01]
411 [ 6.09400000e+02 2.29000000e-01]
412 [ 6.10200000e+02 4.65000000e-01]
413 [ 6.12000000e+02 9.52000000e-01]
414 [ 6.14600000e+02 4.77000000e-01]
415 [ 6.16900000e+02 2.08000000e-01]
416 [ 6.18500000e+02 1.35000000e-01]
417 [ 6.22100000e+02 1.50000000e-01]
418 [ 6.25600000e+02 1.55000000e-01]
419 [ 6.28400000e+02 1.34000000e-01]
420 [ 6.31200000e+02 1.68000000e-01]
421 [ 6.33200000e+02 8.70000000e-02]
422 [ 6.35600000e+02 6.80000000e-02]
423 [ 6.42700000e+02 5.80000000e-02]
424 [ 6.48700000e+02 5.80000000e-02]
425 [ 6.50700000e+02 7.40000000e-02]
426 [ 6.52600000e+02 6.30000000e-02]
427 [ 6.56200000e+02 5.30000000e-02]
428 [ 6.57000000e+02 5.60000000e-02]
429 [ 6.60600000e+02 4.90000000e-02]
430 [ 6.62600000e+02 5.90000000e-02]
431 [ 6.64200000e+02 4.80000000e-02]
432 [ 6.86000000e+02 4.10000000e-02]
433 [ 6.87600000e+02 4.80000000e-02]
434 [ 6.89200000e+02 3.90000000e-02]
435 [ 6.92400000e+02 3.80000000e-02]
436 [ 6.93500000e+02 4.40000000e-02]
437 [ 6.95500000e+02 3.40000000e-02]
438 [ 7.02300000e+02 3.60000000e-02]
439 [ 7.06700000e+02 4.20000000e-02]
440 [ 7.07100000e+02 6.10000000e-02]
441 [ 7.10200000e+02 6.10000000e-02]
442 [ 7.11000000e+02 4.10000000e-02]
443 [ 7.12200000e+02 5.20000000e-02]
444 [ 7.14200000e+02 3.30000000e-02]
445 [ 7.48400000e+02 3.40000000e-02]
446 [ 7.57900000e+02 3.10000000e-02]
447 [ 7.60700000e+02 3.90000000e-02]
448 [ 7.63900000e+02 2.90000000e-02]
449 [ 8.08800000e+02 2.90000000e-02]
450 [ 8.10700000e+02 3.90000000e-02]
451 [ 8.12700000e+02 3.00000000e-02]
452 [ 8.50100000e+02 3.00000000e-02]]
453 """
454 ).strip()
455 )
457 def test__repr__(self) -> None:
458 """
459 Test :meth:`colour.io.tm2714.SpectralDistribution_IESTM2714.__repr__`
460 method.
461 """
463 if not is_scipy_installed(): # pragma: no cover
464 return
466 assert re.sub(
467 "SpectralDistribution_IESTM2714.*",
468 "SpectralDistribution_IESTM2714(...,",
469 repr(self._sd),
470 ) == (
471 textwrap.dedent(
472 """
473SpectralDistribution_IESTM2714(...,
474 Header_IESTM2714('Unknown',
475 'N/A',
476 'Rare earth fluorescent lamp',
477 'byHeart Consultants',
478 'C3567553-C75B-4354-961E-35CEB9FEB42C',
479 None,
480 'N/A',
481 'N/A',
482 'N/A',
483 '2014-06-23',
484 'Ambient temperature 25 degrees C.'),
485 'relative',
486 'other',
487 'other',
488 2.0,
489 True,
490 [[ 4.00000000e+02, 3.40000000e-02],
491 [ 4.03100000e+02, 3.70000000e-02],
492 [ 4.05500000e+02, 6.90000000e-02],
493 [ 4.07500000e+02, 3.70000000e-02],
494 [ 4.20600000e+02, 4.20000000e-02],
495 [ 4.31000000e+02, 4.90000000e-02],
496 [ 4.33700000e+02, 6.00000000e-02],
497 [ 4.37000000e+02, 3.57000000e-01],
498 [ 4.38900000e+02, 6.00000000e-02],
499 [ 4.60000000e+02, 6.80000000e-02],
500 [ 4.77000000e+02, 7.50000000e-02],
501 [ 4.81000000e+02, 8.50000000e-02],
502 [ 4.88200000e+02, 2.04000000e-01],
503 [ 4.92600000e+02, 1.66000000e-01],
504 [ 5.01700000e+02, 9.50000000e-02],
505 [ 5.07600000e+02, 7.80000000e-02],
506 [ 5.17600000e+02, 7.10000000e-02],
507 [ 5.29900000e+02, 7.60000000e-02],
508 [ 5.35400000e+02, 9.90000000e-02],
509 [ 5.39900000e+02, 4.23000000e-01],
510 [ 5.43200000e+02, 8.02000000e-01],
511 [ 5.44400000e+02, 7.13000000e-01],
512 [ 5.47200000e+02, 9.99000000e-01],
513 [ 5.48700000e+02, 5.73000000e-01],
514 [ 5.50200000e+02, 3.40000000e-01],
515 [ 5.53800000e+02, 2.08000000e-01],
516 [ 5.57300000e+02, 1.39000000e-01],
517 [ 5.63700000e+02, 1.29000000e-01],
518 [ 5.74800000e+02, 1.31000000e-01],
519 [ 5.78000000e+02, 1.98000000e-01],
520 [ 5.79200000e+02, 1.90000000e-01],
521 [ 5.80400000e+02, 2.05000000e-01],
522 [ 5.84800000e+02, 2.44000000e-01],
523 [ 5.85900000e+02, 2.36000000e-01],
524 [ 5.87500000e+02, 2.56000000e-01],
525 [ 5.90300000e+02, 1.80000000e-01],
526 [ 5.93500000e+02, 2.18000000e-01],
527 [ 5.95500000e+02, 1.59000000e-01],
528 [ 5.97000000e+02, 1.47000000e-01],
529 [ 5.99400000e+02, 1.70000000e-01],
530 [ 6.02200000e+02, 1.34000000e-01],
531 [ 6.04600000e+02, 1.21000000e-01],
532 [ 6.07400000e+02, 1.40000000e-01],
533 [ 6.09400000e+02, 2.29000000e-01],
534 [ 6.10200000e+02, 4.65000000e-01],
535 [ 6.12000000e+02, 9.52000000e-01],
536 [ 6.14600000e+02, 4.77000000e-01],
537 [ 6.16900000e+02, 2.08000000e-01],
538 [ 6.18500000e+02, 1.35000000e-01],
539 [ 6.22100000e+02, 1.50000000e-01],
540 [ 6.25600000e+02, 1.55000000e-01],
541 [ 6.28400000e+02, 1.34000000e-01],
542 [ 6.31200000e+02, 1.68000000e-01],
543 [ 6.33200000e+02, 8.70000000e-02],
544 [ 6.35600000e+02, 6.80000000e-02],
545 [ 6.42700000e+02, 5.80000000e-02],
546 [ 6.48700000e+02, 5.80000000e-02],
547 [ 6.50700000e+02, 7.40000000e-02],
548 [ 6.52600000e+02, 6.30000000e-02],
549 [ 6.56200000e+02, 5.30000000e-02],
550 [ 6.57000000e+02, 5.60000000e-02],
551 [ 6.60600000e+02, 4.90000000e-02],
552 [ 6.62600000e+02, 5.90000000e-02],
553 [ 6.64200000e+02, 4.80000000e-02],
554 [ 6.86000000e+02, 4.10000000e-02],
555 [ 6.87600000e+02, 4.80000000e-02],
556 [ 6.89200000e+02, 3.90000000e-02],
557 [ 6.92400000e+02, 3.80000000e-02],
558 [ 6.93500000e+02, 4.40000000e-02],
559 [ 6.95500000e+02, 3.40000000e-02],
560 [ 7.02300000e+02, 3.60000000e-02],
561 [ 7.06700000e+02, 4.20000000e-02],
562 [ 7.07100000e+02, 6.10000000e-02],
563 [ 7.10200000e+02, 6.10000000e-02],
564 [ 7.11000000e+02, 4.10000000e-02],
565 [ 7.12200000e+02, 5.20000000e-02],
566 [ 7.14200000e+02, 3.30000000e-02],
567 [ 7.48400000e+02, 3.40000000e-02],
568 [ 7.57900000e+02, 3.10000000e-02],
569 [ 7.60700000e+02, 3.90000000e-02],
570 [ 7.63900000e+02, 2.90000000e-02],
571 [ 8.08800000e+02, 2.90000000e-02],
572 [ 8.10700000e+02, 3.90000000e-02],
573 [ 8.12700000e+02, 3.00000000e-02],
574 [ 8.50100000e+02, 3.00000000e-02]],
575 CubicSplineInterpolator,
576 {},
577 Extrapolator,
578 {'method': 'Constant', 'left': None, 'right': None})
579 """
580 ).strip()
581 )
583 def test_read(self, sd: SpectralDistribution | None = None) -> None:
584 """
585 Test :meth:`colour.io.tm2714.SpectralDistribution_IESTM2714.read`
586 method.
588 Parameters
589 ----------
590 sd
591 Optional *IES TM-27-14* spectral distribution for read tests.
592 """
594 sd = cast(
595 "SpectralDistribution_IESTM2714",
596 optional(
597 sd,
598 SpectralDistribution_IESTM2714(
599 os.path.join(ROOT_RESOURCES, "Fluorescent.spdx")
600 ).read(),
601 ),
602 )
604 sd_r = SpectralDistribution(FLUORESCENT_FILE_SPECTRAL_DATA)
606 np.testing.assert_array_equal(sd_r.domain, sd.domain)
607 np.testing.assert_allclose(
608 sd_r.values, sd.values, atol=TOLERANCE_ABSOLUTE_TESTS
609 )
611 test_read: List[
612 Tuple[dict, Header_IESTM2714 | SpectralDistribution_IESTM2714]
613 ] = [
614 (FLUORESCENT_FILE_HEADER, sd.header),
615 (FLUORESCENT_FILE_SPECTRAL_DESCRIPTION, sd),
616 ]
617 for test, read in test_read:
618 for key, value in test.items():
619 for specification in read.mapping.elements:
620 if key == specification.element:
621 assert getattr(read, specification.attribute) == value
623 def test_raise_exception_read(self) -> None:
624 """
625 Test :func:`colour.io.tm2714.SpectralDistribution_IESTM2714.read`
626 method raised exception.
627 """
629 sd = SpectralDistribution_IESTM2714()
630 pytest.raises(ValueError, sd.read)
632 with pytest.raises(ValueError):
633 sd = SpectralDistribution_IESTM2714(
634 os.path.join(ROOT_RESOURCES, "Invalid.spdx")
635 )
637 def test_write(self) -> None:
638 """
639 Test :meth:`colour.io.tm2714.SpectralDistribution_IESTM2714.write`
640 method.
641 """
643 sd_r = self._sd
645 sd_r.path = os.path.join(self._temporary_directory, "Fluorescent.spdx")
646 assert sd_r.write()
647 sd_t = SpectralDistribution_IESTM2714(sd_r.path).read()
649 self.test_read(sd_t)
650 assert sd_r == sd_t
652 for attribute in (
653 "manufacturer",
654 "catalog_number",
655 "description",
656 "document_creator",
657 "unique_identifier",
658 "measurement_equipment",
659 "laboratory",
660 "report_number",
661 "report_date",
662 "document_creation_date",
663 "comments",
664 ):
665 assert getattr(sd_r.header, attribute) == getattr(sd_t.header, attribute)
667 for attribute in (
668 "spectral_quantity",
669 "reflection_geometry",
670 "transmission_geometry",
671 "bandwidth_FWHM",
672 "bandwidth_corrected",
673 ):
674 assert getattr(sd_r, attribute) == getattr(sd_t, attribute)
676 def test_raise_exception_write(self) -> None:
677 """
678 Test :func:`colour.io.tm2714.SpectralDistribution_IESTM2714.write`
679 method raised exception.
680 """
682 sd = SpectralDistribution_IESTM2714()
683 pytest.raises(ValueError, sd.write)