Coverage for colour/io/tm2714.py: 100%
296 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2IES TM-27-14 Data Input / Output
3================================
5Define classes and utilities for handling *IES TM-27-14* spectral data *XML*
6files.
8This module provides the :class:`colour.SpectralDistribution_IESTM2714` class
9for reading and writing spectral distribution data according to the *IES
10TM-27-14* standard format for electronic transfer of spectral data.
12References
13----------
14- :cite:`IESComputerCommittee2014a` : IES Computer Committee, & TM-27-14
15 Working Group. (2014). IES Standard Format for the Electronic Transfer of
16 Spectral Data Electronic Transfer of Spectral Data. Illuminating
17 Engineering Society. ISBN:978-0-87995-295-2
18"""
20from __future__ import annotations
22import os
23import re
24import typing
25from dataclasses import dataclass, field
26from pathlib import Path
27from xml.dom import minidom
28from xml.etree import ElementTree as ET
30from colour.colorimetry import SpectralDistribution
32if typing.TYPE_CHECKING:
33 from colour.hints import Any, Callable, Literal, PathLike
35from colour.utilities import (
36 Structure,
37 as_float_array,
38 as_float_scalar,
39 attest,
40 is_numeric,
41 multiline_repr,
42 multiline_str,
43 optional,
44 tstack,
45)
47__author__ = "Colour Developers"
48__copyright__ = "Copyright 2013 Colour Developers"
49__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
50__maintainer__ = "Colour Developers"
51__email__ = "colour-developers@colour-science.org"
52__status__ = "Production"
54__all__ = [
55 "VERSION_IESTM2714",
56 "NAMESPACE_IESTM2714",
57 "Element_Specification_IESTM2714",
58 "Header_IESTM2714",
59 "SpectralDistribution_IESTM2714",
60]
62VERSION_IESTM2714: str = "1.0"
64NAMESPACE_IESTM2714: str = "http://www.ies.org/iestm2714"
67@dataclass
68class Element_Specification_IESTM2714:
69 """
70 Define *IES TM-27-14* spectral data *XML* file element specification.
72 Parameters
73 ----------
74 element
75 Element name.
76 attribute
77 Associated attribute name.
78 type_
79 Element type.
80 required
81 Is element required.
82 read_conversion
83 Method to convert from *XML* to type on reading.
84 write_conversion
85 Method to convert from type to *XML* on writing.
86 """
88 element: str
89 attribute: str
90 type_: Any = field(default_factory=str)
91 required: bool = field(default_factory=lambda: False)
92 read_conversion: Callable = field(
93 default_factory=lambda: lambda x: None if x == "None" else str(x)
94 )
95 write_conversion: Callable = field(default_factory=lambda: str)
98class Header_IESTM2714:
99 """
100 Define the header object for an *IES TM-27-14* spectral distribution.
102 Parameters
103 ----------
104 manufacturer
105 Manufacturer of the device under test.
106 catalog_number
107 Manufacturer's product catalog number.
108 description
109 Description of the spectral data in the spectral data *XML* file.
110 document_creator
111 Creator of the spectral data *XML* file, which may be a test lab,
112 a research group, a standard body, a company or an individual.
113 unique_identifier
114 Unique identifier to the product under test or the spectral data
115 in the document.
116 measurement_equipment
117 Description of the equipment used to measure the spectral data.
118 laboratory
119 Testing laboratory name that performed the spectral data
120 measurements.
121 report_number
122 Testing laboratory report number.
123 report_date
124 Testing laboratory report date using the *XML DateTime Data Type*,
125 *YYYY-MM-DDThh:mm:ss*.
126 document_creation_date
127 Spectral data *XML* file creation date using the
128 *XML DateTime Data Type*, *YYYY-MM-DDThh:mm:ss*.
129 comments
130 Additional information relating to the tested and reported data.
132 Attributes
133 ----------
134 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.mapping`
135 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.manufacturer`
136 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.catalog_number`
137 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.description`
138 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.document_creator`
139 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.unique_identifier`
140 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.measurement_equipment`
141 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.laboratory`
142 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.report_number`
143 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.report_date`
144 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.document_creation_date`
145 - :attr:`~colour.io.ies_tm2714.Header_IESTM2714.comments`
147 Methods
148 -------
149 - :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__init__`
150 - :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__str__`
151 - :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__repr__`
152 - :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__hash__`
153 - :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__eq__`
154 - :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__ne__`
156 Examples
157 --------
158 >>> Header_IESTM2714("colour-science") # doctest: +ELLIPSIS
159 Header_IESTM2714('colour-science',
160 None,
161 None,
162 None,
163 None,
164 None,
165 None,
166 None,
167 None,
168 None,
169 None)
170 >>> Header_IESTM2714("colour-science").manufacturer # doctest: +SKIP
171 'colour-science'
172 """
174 def __init__(
175 self,
176 manufacturer: str | None = None,
177 catalog_number: str | None = None,
178 description: str | None = None,
179 document_creator: str | None = None,
180 unique_identifier: str | None = None,
181 measurement_equipment: str | None = None,
182 laboratory: str | None = None,
183 report_number: str | None = None,
184 report_date: str | None = None,
185 document_creation_date: str | None = None,
186 comments: str | None = None,
187 ) -> None:
188 self._mapping: Structure = Structure(
189 element="Header",
190 elements=(
191 Element_Specification_IESTM2714("Manufacturer", "manufacturer"),
192 Element_Specification_IESTM2714("CatalogNumber", "catalog_number"),
193 Element_Specification_IESTM2714(
194 "Description", "description", required=True
195 ),
196 Element_Specification_IESTM2714(
197 "DocumentCreator", "document_creator", required=True
198 ),
199 Element_Specification_IESTM2714(
200 "UniqueIdentifier", "unique_identifier"
201 ),
202 Element_Specification_IESTM2714(
203 "MeasurementEquipment", "measurement_equipment"
204 ),
205 Element_Specification_IESTM2714("Laboratory", "laboratory"),
206 Element_Specification_IESTM2714("ReportNumber", "report_number"),
207 Element_Specification_IESTM2714("ReportDate", "report_date"),
208 Element_Specification_IESTM2714(
209 "DocumentCreationDate",
210 "document_creation_date",
211 required=True,
212 ),
213 Element_Specification_IESTM2714("Comments", "comments", False),
214 ),
215 )
217 self._manufacturer: str | None = None
218 self.manufacturer = manufacturer
219 self._catalog_number: str | None = None
220 self.catalog_number = catalog_number
221 self._description: str | None = None
222 self.description = description
223 self._document_creator: str | None = None
224 self.document_creator = document_creator
225 self._unique_identifier: str | None = None
226 self.unique_identifier = unique_identifier
227 self._measurement_equipment: str | None = None
228 self.measurement_equipment = measurement_equipment
229 self._laboratory: str | None = None
230 self.laboratory = laboratory
231 self._report_number: str | None = None
232 self.report_number = report_number
233 self._report_date: str | None = None
234 self.report_date = report_date
235 self._document_creation_date: str | None = None
236 self.document_creation_date = document_creation_date
237 self._comments: str | None = None
238 self.comments = comments
240 @property
241 def mapping(self) -> Structure:
242 """
243 Getter for the mapping structure.
245 Returns
246 -------
247 :class:`colour.utilities.Structure`
248 Mapping structure.
249 """
251 return self._mapping
253 @property
254 def manufacturer(self) -> str | None:
255 """
256 Getter and setter for the manufacturer name.
258 Parameters
259 ----------
260 value
261 Value to set the manufacturer with.
263 Returns
264 -------
265 :class:`str` or :py:data:`None`
266 Manufacturer name.
267 """
269 return self._manufacturer
271 @manufacturer.setter
272 def manufacturer(self, value: str | None) -> None:
273 """Setter for the **self.manufacturer** property."""
275 if value is not None:
276 attest(
277 isinstance(value, str),
278 f'"manufacturer" property: "{value}" type is not "str"!',
279 )
281 self._manufacturer = value
283 @property
284 def catalog_number(self) -> str | None:
285 """
286 Getter and setter for the catalog number.
288 Parameters
289 ----------
290 value
291 Value to set the catalog number with.
293 Returns
294 -------
295 :class:`str` or :py:data:`None`
296 Catalog number.
297 """
299 return self._catalog_number
301 @catalog_number.setter
302 def catalog_number(self, value: str | None) -> None:
303 """Setter for the **self.catalog_number** property."""
305 if value is not None:
306 attest(
307 isinstance(value, str),
308 f'"catalog_number" property: "{value}" type is not "str"!',
309 )
311 self._catalog_number = value
313 @property
314 def description(self) -> str | None:
315 """
316 Getter and setter for the description.
318 Parameters
319 ----------
320 value
321 Value to set the description with.
323 Returns
324 -------
325 :class:`str` or :py:data:`None`
326 Description.
327 """
329 return self._description
331 @description.setter
332 def description(self, value: str | None) -> None:
333 """Setter for the **self.description** property."""
335 if value is not None:
336 attest(
337 isinstance(value, str),
338 f'"description" property: "{value}" type is not "str"!',
339 )
341 self._description = value
343 @property
344 def document_creator(self) -> str | None:
345 """
346 Getter and setter for the document creator.
348 Parameters
349 ----------
350 value
351 Value to set the document creator with.
353 Returns
354 -------
355 :class:`str` or :py:data:`None`
356 Document creator.
357 """
359 return self._document_creator
361 @document_creator.setter
362 def document_creator(self, value: str | None) -> None:
363 """Setter for the **self.document_creator** property."""
365 if value is not None:
366 attest(
367 isinstance(value, str),
368 f'"document_creator" property: "{value}" type is not "str"!',
369 )
371 self._document_creator = value
373 @property
374 def unique_identifier(self) -> str | None:
375 """
376 Getter and setter for the unique identifier.
378 Parameters
379 ----------
380 value
381 Value to set the unique identifier with.
383 Returns
384 -------
385 :class:`str` or :py:data:`None`
386 Unique identifier.
387 """
389 return self._unique_identifier
391 @unique_identifier.setter
392 def unique_identifier(self, value: str | None) -> None:
393 """Setter for the **self.unique_identifier** property."""
395 if value is not None:
396 attest(
397 isinstance(value, str),
398 f'"unique_identifier" property: "{value}" type is not "str"!',
399 )
401 self._unique_identifier = value
403 @property
404 def measurement_equipment(self) -> str | None:
405 """
406 Getter and setter for the measurement equipment.
408 Parameters
409 ----------
410 value
411 Value to set the measurement equipment with.
413 Returns
414 -------
415 :class:`str` or :py:data:`None`
416 Measurement equipment.
417 """
419 return self._measurement_equipment
421 @measurement_equipment.setter
422 def measurement_equipment(self, value: str | None) -> None:
423 """Setter for the **self.measurement_equipment** property."""
425 if value is not None:
426 attest(
427 isinstance(value, str),
428 f'"measurement_equipment" property: "{value}" type is not "str"!',
429 )
431 self._measurement_equipment = value
433 @property
434 def laboratory(self) -> str | None:
435 """
436 Getter and setter for the laboratory information.
438 Parameters
439 ----------
440 value
441 Value to set the laboratory with.
443 Returns
444 -------
445 :class:`str` or :py:data:`None`
446 Laboratory.
447 """
449 return self._laboratory
451 @laboratory.setter
452 def laboratory(self, value: str | None) -> None:
453 """Setter for the **self.laboratory** property."""
455 if value is not None:
456 attest(
457 isinstance(value, str),
458 f'"laboratory" property: "{value}" type is not "str"!',
459 )
461 self._laboratory = value
463 @property
464 def report_number(self) -> str | None:
465 """
466 Getter and setter for the report number.
468 Parameters
469 ----------
470 value
471 Value to set the report number with.
473 Returns
474 -------
475 :class:`str` or :py:data:`None`
476 Report number.
477 """
479 return self._report_number
481 @report_number.setter
482 def report_number(self, value: str | None) -> None:
483 """Setter for the **self.report_number** property."""
485 if value is not None:
486 attest(
487 isinstance(value, str),
488 f'"report_number" property: "{value}" type is not "str"!',
489 )
491 self._report_number = value
493 @property
494 def report_date(self) -> str | None:
495 """
496 Getter and setter for the report date.
498 Parameters
499 ----------
500 value
501 Value to set the report date with.
503 Returns
504 -------
505 :class:`str` or :py:data:`None`
506 Report date.
507 """
509 return self._report_date
511 @report_date.setter
512 def report_date(self, value: str | None) -> None:
513 """Setter for the **self.report_date** property."""
515 if value is not None:
516 attest(
517 isinstance(value, str),
518 f'"report_date" property: "{value}" type is not "str"!',
519 )
521 self._report_date = value
523 @property
524 def document_creation_date(self) -> str | None:
525 """
526 Getter and setter for the document creation date.
528 Parameters
529 ----------
530 value
531 Value to set the document creation date with.
533 Returns
534 -------
535 :class:`str` or :py:data:`None`
536 Document creation date.
537 """
539 return self._document_creation_date
541 @document_creation_date.setter
542 def document_creation_date(self, value: str | None) -> None:
543 """Setter for the **self.document_creation_date** property."""
545 if value is not None:
546 attest(
547 isinstance(value, str),
548 f'"document_creation_date" property: "{value}" type is not "str"!',
549 )
551 self._document_creation_date = value
553 @property
554 def comments(self) -> str | None:
555 """
556 Getter and setter for the comments associated with the object.
558 Parameters
559 ----------
560 value
561 Value to set the comments with.
563 Returns
564 -------
565 :class:`str` or :py:data:`None`
566 Comments.
567 """
569 return self._comments
571 @comments.setter
572 def comments(self, value: str | None) -> None:
573 """Setter for the **self.comments** property."""
575 if value is not None:
576 attest(
577 isinstance(value, str),
578 f'"comments" property: "{value}" type is not "str"!',
579 )
581 self._comments = value
583 def __str__(self) -> str:
584 """
585 Return a formatted string representation of the *IES TM-27-14* header.
587 Returns
588 -------
589 :class:`str`
590 Formatted string representation displaying the header attributes.
592 Examples
593 --------
594 >>> print(Header_IESTM2714("colour-science"))
595 Manufacturer : colour-science
596 Catalog Number : None
597 Description : None
598 Document Creator : None
599 Unique Identifier : None
600 Measurement Equipment : None
601 Laboratory : None
602 Report Number : None
603 Report Date : None
604 Document Creation Date : None
605 Comments : None
606 """
608 return multiline_str(
609 self,
610 [
611 {"name": "_manufacturer", "label": "Manufacturer"},
612 {"name": "_catalog_number", "label": "Catalog Number"},
613 {"name": "_description", "label": "Description"},
614 {"name": "_document_creator", "label": "Document Creator"},
615 {"name": "_unique_identifier", "label": "Unique Identifier"},
616 {
617 "name": "_measurement_equipment",
618 "label": "Measurement Equipment",
619 },
620 {"name": "_laboratory", "label": "Laboratory"},
621 {"name": "_report_number", "label": "Report Number"},
622 {"name": "_report_date", "label": "Report Date"},
623 {
624 "name": "_document_creation_date",
625 "label": "Document Creation Date",
626 },
627 {"name": "_comments", "label": "Comments"},
628 ],
629 )
631 def __repr__(self) -> str:
632 """
633 Return an evaluable string representation of the header.
635 Returns
636 -------
637 :class:`str`
638 Evaluable string representation.
640 Examples
641 --------
642 >>> Header_IESTM2714("colour-science")
643 Header_IESTM2714('colour-science',
644 None,
645 None,
646 None,
647 None,
648 None,
649 None,
650 None,
651 None,
652 None,
653 None)
654 """
656 return multiline_repr(
657 self,
658 [
659 {"name": "_manufacturer"},
660 {"name": "_catalog_number"},
661 {"name": "_description"},
662 {"name": "_document_creator"},
663 {"name": "_unique_identifier"},
664 {"name": "_measurement_equipment"},
665 {"name": "_laboratory"},
666 {"name": "_report_number"},
667 {"name": "_report_date"},
668 {"name": "_document_creation_date"},
669 {"name": "_comments"},
670 ],
671 )
673 def __hash__(self) -> int:
674 """
675 Return the header hash.
677 Returns
678 -------
679 :class:`int`
680 Object hash.
681 """
683 return hash(
684 (
685 self._manufacturer,
686 self._catalog_number,
687 self._description,
688 self._document_creator,
689 self._unique_identifier,
690 self._measurement_equipment,
691 self._laboratory,
692 self._report_number,
693 self._report_date,
694 self._document_creation_date,
695 self._comments,
696 )
697 )
699 def __eq__(self, other: object) -> bool:
700 """
701 Determine whether the header is equal to the specified other object.
703 Compare all header attributes to determine equality between this header
704 and the specified object. Two headers are considered equal when all
705 their attributes match exactly.
707 Parameters
708 ----------
709 other
710 Object to test whether it is equal to the header.
712 Returns
713 -------
714 :class:`bool`
715 Whether the specified object is equal to the header.
717 Examples
718 --------
719 >>> Header_IESTM2714("Foo") == Header_IESTM2714("Foo")
720 True
721 >>> Header_IESTM2714("Foo") == Header_IESTM2714("Bar")
722 False
723 """
725 if isinstance(other, Header_IESTM2714):
726 return all(
727 [
728 self._manufacturer == other.manufacturer,
729 self._catalog_number == other.catalog_number,
730 self._description == other.description,
731 self._document_creator == other.document_creator,
732 self._unique_identifier == other.unique_identifier,
733 self._measurement_equipment == other.measurement_equipment,
734 self._laboratory == other.laboratory,
735 self._report_number == other.report_number,
736 self._report_date == other.report_date,
737 self._document_creation_date == other.document_creation_date,
738 self._comments == other.comments,
739 ]
740 )
742 return False
744 def __ne__(self, other: object) -> bool:
745 """
746 Determine whether the header is not equal to the specified other object.
748 Parameters
749 ----------
750 other
751 Object to test whether it is not equal to the header.
753 Returns
754 -------
755 :class:`bool`
756 Whether specified object is not equal to the header.
758 Examples
759 --------
760 >>> Header_IESTM2714("Foo") != Header_IESTM2714("Foo")
761 False
762 >>> Header_IESTM2714("Foo") != Header_IESTM2714("Bar")
763 True
764 """
766 return not (self == other)
769class SpectralDistribution_IESTM2714(SpectralDistribution):
770 """
771 Define an *IES TM-27-14* spectral distribution for electronic transfer of
772 spectral data.
774 This class provides functionality to read and write spectral distribution
775 data using the *IES TM-27-14* standard format. The standard defines
776 an *XML* schema for exchanging spectral measurement data between systems,
777 ensuring consistent representation of wavelength-dependent measurements
778 across different applications and instruments.
780 Parameters
781 ----------
782 path
783 Spectral data *XML* file path.
784 header
785 *IES TM-27-14* spectral distribution header containing metadata.
786 spectral_quantity
787 Quantity of measurement for each element of the spectral data (e.g.,
788 "reflectance", "transmittance", "radiance").
789 reflection_geometry
790 Spectral reflectance factors geometric conditions.
791 transmission_geometry
792 Spectral transmittance factors geometric conditions.
793 bandwidth_FWHM
794 Spectroradiometer full-width half-maximum bandwidth in nanometers.
795 bandwidth_corrected
796 Specifies if bandwidth correction has been applied to the measured
797 data.
799 Other Parameters
800 ----------------
801 data
802 Data to be stored in the spectral distribution.
803 domain
804 Values to initialise the
805 :attr:`colour.SpectralDistribution.wavelength` property with.
806 If both ``data`` and ``domain`` arguments are defined, the latter will
807 be used to initialise the
808 :attr:`colour.SpectralDistribution.wavelength` property.
809 extrapolator
810 Extrapolator class type to use as extrapolating function.
811 extrapolator_kwargs
812 Arguments to use when instantiating the extrapolating function.
813 interpolator
814 Interpolator class type to use as interpolating function.
815 interpolator_kwargs
816 Arguments to use when instantiating the interpolating function.
817 name
818 Spectral distribution name.
819 display_name
820 Spectral distribution name for figures, default to
821 :attr:`colour.SpectralDistribution.name` property value.
823 Notes
824 -----
825 *Reflection Geometry*
827 - di:8: Diffuse / eight-degree, specular component included.
828 - de:8: Diffuse / eight-degree, specular component excluded.
829 - 8:di: Eight-degree / diffuse, specular component included.
830 - 8:de: Eight-degree / diffuse, specular component excluded.
831 - d:d: Diffuse / diffuse.
832 - d:0: Alternative diffuse.
833 - 45a:0: Forty-five degree annular / normal.
834 - 45c:0: Forty-five degree circumferential / normal.
835 - 0:45a: Normal / forty-five degree annular.
836 - 45x:0: Forty-five degree directional / normal.
837 - 0:45x: Normal / forty-five degree directional.
838 - other: User-specified in comments.
840 *Transmission Geometry*
842 - 0:0: Normal / normal.
843 - di:0: Diffuse / normal, regular component included.
844 - de:0: Diffuse / normal, regular component excluded.
845 - 0:di: Normal / diffuse, regular component included.
846 - 0:de: Normal / diffuse, regular component excluded.
847 - d:d: Diffuse / diffuse.
848 - other: User-specified in comments.
850 Attributes
851 ----------
852 - :attr:`~colour.SpectralDistribution_IESTM2714.mapping`
853 - :attr:`~colour.SpectralDistribution_IESTM2714.path`
854 - :attr:`~colour.SpectralDistribution_IESTM2714.header`
855 - :attr:`~colour.SpectralDistribution_IESTM2714.spectral_quantity`
856 - :attr:`~colour.SpectralDistribution_IESTM2714.reflection_geometry`
857 - :attr:`~colour.SpectralDistribution_IESTM2714.transmission_geometry`
858 - :attr:`~colour.SpectralDistribution_IESTM2714.bandwidth_FWHM`
859 - :attr:`~colour.SpectralDistribution_IESTM2714.bandwidth_corrected`
861 Methods
862 -------
863 - :meth:`~colour.SpectralDistribution_IESTM2714.__init__`
864 - :meth:`~colour.SpectralDistribution_IESTM2714.__str__`
865 - :meth:`~colour.SpectralDistribution_IESTM2714.__repr__`
866 - :meth:`~colour.SpectralDistribution_IESTM2714.read`
867 - :meth:`~colour.SpectralDistribution_IESTM2714.write`
869 References
870 ----------
871 :cite:`IESComputerCommittee2014a`
873 Examples
874 --------
875 >>> from os.path import dirname, join
876 >>> directory = join(dirname(__file__), "tests", "resources")
877 >>> sd = SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
878 >>> sd.name # doctest: +SKIP
879 'Unknown - N/A - Rare earth fluorescent lamp'
880 >>> sd.header.comments
881 'Ambient temperature 25 degrees C.'
882 >>> sd[501.7] # doctest: +ELLIPSIS
883 0.0950000...
884 """
886 def __init__(
887 self,
888 path: str | PathLike | None = None,
889 header: Header_IESTM2714 | None = None,
890 spectral_quantity: (
891 Literal[
892 "absorptance",
893 "exitance",
894 "flux",
895 "intensity",
896 "irradiance",
897 "radiance",
898 "reflectance",
899 "relative",
900 "transmittance",
901 "R-Factor",
902 "T-Factor",
903 "other",
904 ]
905 | None
906 ) = None,
907 reflection_geometry: (
908 Literal[
909 "di:8",
910 "de:8",
911 "8:di",
912 "8:de",
913 "d:d",
914 "d:0",
915 "45a:0",
916 "45c:0",
917 "0:45a",
918 "45x:0",
919 "0:45x",
920 "other",
921 ]
922 | None
923 ) = None,
924 transmission_geometry: (
925 Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None
926 ) = None,
927 bandwidth_FWHM: float | None = None,
928 bandwidth_corrected: bool | None = None,
929 **kwargs: Any,
930 ) -> None:
931 super().__init__(**kwargs)
933 self._mapping: Structure = Structure(
934 element="SpectralDistribution",
935 elements=(
936 Element_Specification_IESTM2714(
937 "SpectralQuantity", "spectral_quantity", required=True
938 ),
939 Element_Specification_IESTM2714(
940 "ReflectionGeometry", "reflection_geometry"
941 ),
942 Element_Specification_IESTM2714(
943 "TransmissionGeometry", "transmission_geometry"
944 ),
945 Element_Specification_IESTM2714(
946 "BandwidthFWHM",
947 "bandwidth_FWHM",
948 read_conversion=(
949 lambda x: (None if x == "None" else as_float_scalar(x))
950 ),
951 ),
952 Element_Specification_IESTM2714(
953 "BandwidthCorrected",
954 "bandwidth_corrected",
955 read_conversion=(lambda x: bool(x == "true")),
956 write_conversion=(lambda x: "true" if x is True else "false"),
957 ),
958 ),
959 data=Element_Specification_IESTM2714(
960 "SpectralData", "wavelength", required=True
961 ),
962 )
964 self._path: str | None = None
965 self.path = path
966 self._header: Header_IESTM2714 = Header_IESTM2714()
967 self.header = optional(header, self._header)
968 self._spectral_quantity: (
969 Literal[
970 "absorptance",
971 "exitance",
972 "flux",
973 "intensity",
974 "irradiance",
975 "radiance",
976 "reflectance",
977 "relative",
978 "transmittance",
979 "R-Factor",
980 "T-Factor",
981 "other",
982 ]
983 | None
984 ) = None
985 self.spectral_quantity = spectral_quantity
986 self._reflection_geometry: (
987 Literal[
988 "di:8",
989 "de:8",
990 "8:di",
991 "8:de",
992 "d:d",
993 "d:0",
994 "45a:0",
995 "45c:0",
996 "0:45a",
997 "45x:0",
998 "0:45x",
999 "other",
1000 ]
1001 | None
1002 ) = None
1003 self.reflection_geometry = reflection_geometry
1004 self._transmission_geometry: (
1005 Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None
1006 ) = None
1007 self.transmission_geometry = transmission_geometry
1008 self._bandwidth_FWHM: float | None = None
1009 self.bandwidth_FWHM = bandwidth_FWHM
1010 self._bandwidth_corrected: bool | None = None
1011 self.bandwidth_corrected = bandwidth_corrected
1013 if self.path is not None and os.path.exists(
1014 self.path # pyright: ignore
1015 ):
1016 self.read()
1018 @property
1019 def mapping(self) -> Structure:
1020 """
1021 Getter for the structure containing file mappings.
1023 Returns
1024 -------
1025 :class:`colour.utilities.Structure`
1026 Mapping structure.
1027 """
1029 return self._mapping
1031 @property
1032 def path(self) -> str | None:
1033 """
1034 Getter and setter for the resource path.
1036 Parameters
1037 ----------
1038 value
1039 Value to set the path with.
1041 Returns
1042 -------
1043 :class:`str` or :py:data:`None`
1044 Path to the resource.
1045 """
1047 return self._path
1049 @path.setter
1050 def path(self, value: str | PathLike | None) -> None:
1051 """Setter for the **self.path** property."""
1053 if value is not None:
1054 attest(
1055 isinstance(value, (str, Path)),
1056 f'"path" property: "{value}" type is not "str" or "Path"!',
1057 )
1059 value = str(value)
1061 self._path = value
1063 @property
1064 def header(self) -> Header_IESTM2714:
1065 """
1066 Getter and setter for the *IES TM-27-14* spectral distribution header.
1068 Parameters
1069 ----------
1070 value
1071 Value to set the header with.
1073 Returns
1074 -------
1075 :class:`colour.io.tm2714.Header_IESTM2714`
1076 Header object containing spectral distribution metadata.
1077 """
1079 return self._header
1081 @header.setter
1082 def header(self, value: Header_IESTM2714) -> None:
1083 """Setter for the **self.header** property."""
1085 attest(
1086 isinstance(value, Header_IESTM2714),
1087 f'"header" property: "{value}" type is not "Header_IESTM2714"!',
1088 )
1090 self._header = value
1092 @property
1093 def spectral_quantity(
1094 self,
1095 ) -> (
1096 Literal[
1097 "absorptance",
1098 "exitance",
1099 "flux",
1100 "intensity",
1101 "irradiance",
1102 "radiance",
1103 "reflectance",
1104 "relative",
1105 "transmittance",
1106 "R-Factor",
1107 "T-Factor",
1108 "other",
1109 ]
1110 | None
1111 ):
1112 """
1113 Getter and setter for the spectral quantity.
1115 Parameters
1116 ----------
1117 value
1118 Value to set the spectral quantity with.
1120 Returns
1121 -------
1122 :class:`str` or :py:data:`None`
1123 Spectral quantity.
1124 """
1126 return self._spectral_quantity
1128 @spectral_quantity.setter
1129 def spectral_quantity(
1130 self,
1131 value: (
1132 Literal[
1133 "absorptance",
1134 "exitance",
1135 "flux",
1136 "intensity",
1137 "irradiance",
1138 "radiance",
1139 "reflectance",
1140 "relative",
1141 "transmittance",
1142 "R-Factor",
1143 "T-Factor",
1144 "other",
1145 ]
1146 | None
1147 ),
1148 ) -> None:
1149 """Setter for the **self.spectral_quantity** property."""
1151 if value is not None:
1152 attest(
1153 isinstance(value, str),
1154 f'"spectral_quantity" property: "{value}" type is not "str"!',
1155 )
1157 self._spectral_quantity = value
1159 @property
1160 def reflection_geometry(
1161 self,
1162 ) -> (
1163 Literal[
1164 "di:8",
1165 "de:8",
1166 "8:di",
1167 "8:de",
1168 "d:d",
1169 "d:0",
1170 "45a:0",
1171 "45c:0",
1172 "0:45a",
1173 "45x:0",
1174 "0:45x",
1175 "other",
1176 ]
1177 | None
1178 ):
1179 """
1180 Getter and setter for the reflection geometry.
1182 Parameters
1183 ----------
1184 value
1185 Value to set the reflection geometry with.
1187 Returns
1188 -------
1189 :class:`str` or :py:data:`None`
1190 Reflection geometry.
1191 """
1193 return self._reflection_geometry
1195 @reflection_geometry.setter
1196 def reflection_geometry(
1197 self,
1198 value: (
1199 Literal[
1200 "di:8",
1201 "de:8",
1202 "8:di",
1203 "8:de",
1204 "d:d",
1205 "d:0",
1206 "45a:0",
1207 "45c:0",
1208 "0:45a",
1209 "45x:0",
1210 "0:45x",
1211 "other",
1212 ]
1213 | None
1214 ),
1215 ) -> None:
1216 """Setter for the **self.reflection_geometry** property."""
1218 if value is not None:
1219 attest(
1220 isinstance(value, str),
1221 f'"reflection_geometry" property: "{value}" type is not "str"!',
1222 )
1224 self._reflection_geometry = value
1226 @property
1227 def transmission_geometry(
1228 self,
1229 ) -> Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None:
1230 """
1231 Getter and setter for the transmission geometry.
1233 Parameters
1234 ----------
1235 value
1236 Value to set the transmission geometry with.
1238 Returns
1239 -------
1240 :class:`str` or :py:data:`None`
1241 Transmission geometry.
1242 """
1244 return self._transmission_geometry
1246 @transmission_geometry.setter
1247 def transmission_geometry(
1248 self,
1249 value: (Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None),
1250 ) -> None:
1251 """Setter for the **self.transmission_geometry** property."""
1253 if value is not None:
1254 attest(
1255 isinstance(value, str),
1256 f'"transmission_geometry" property: "{value}" type is not "str"!',
1257 )
1259 self._transmission_geometry = value
1261 @property
1262 def bandwidth_FWHM(self) -> float | None:
1263 """
1264 Getter and setter for the full-width half-maximum (FWHM) bandwidth of
1265 the spectral measurement.
1267 Parameters
1268 ----------
1269 value
1270 Value to set the full-width half-maximum bandwidth with.
1272 Returns
1273 -------
1274 :class:`float` or :py:data:`None`
1275 Full-width half-maximum bandwidth.
1276 """
1278 return self._bandwidth_FWHM
1280 @bandwidth_FWHM.setter
1281 def bandwidth_FWHM(self, value: float | None) -> None:
1282 """Setter for the **self.bandwidth_FWHM** property."""
1284 if value is not None:
1285 attest(
1286 is_numeric(value),
1287 f'"bandwidth_FWHM" property: "{value}" is not a "number"!',
1288 )
1290 value = as_float_scalar(value)
1292 self._bandwidth_FWHM = value
1294 @property
1295 def bandwidth_corrected(self) -> bool | None:
1296 """
1297 Getter and setter for whether bandwidth correction has been applied to
1298 the measured data.
1300 Parameters
1301 ----------
1302 value
1303 Whether bandwidth correction has been applied to the measured
1304 data.
1306 Returns
1307 -------
1308 :class:`bool` or :py:data:`None`
1309 Whether bandwidth correction has been applied to the measured
1310 data.
1311 """
1313 return self._bandwidth_corrected
1315 @bandwidth_corrected.setter
1316 def bandwidth_corrected(self, value: bool | None) -> None:
1317 """Setter for the **self.bandwidth_corrected** property."""
1319 if value is not None:
1320 attest(
1321 isinstance(value, bool),
1322 f'"bandwidth_corrected" property: "{value}" type is not "bool"!',
1323 )
1325 self._bandwidth_corrected = value
1327 def __str__(self) -> str:
1328 """
1329 Generate a formatted string representation of the *IES TM-27-14*
1330 spectral distribution.
1332 Returns
1333 -------
1334 :class:`str`
1335 Formatted string representation containing path, spectral
1336 metadata, header details, and spectral data values.
1338 Examples
1339 --------
1340 >>> from os.path import dirname, join
1341 >>> directory = join(dirname(__file__), "tests", "resources")
1342 >>> print(SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx")))
1343 ... # doctest: +ELLIPSIS
1344 IES TM-27-14 Spectral Distribution
1345 ==================================
1346 <BLANKLINE>
1347 Path : ...
1348 Spectral Quantity : relative
1349 Reflection Geometry : other
1350 Transmission Geometry : other
1351 Bandwidth (FWHM) : 2.0
1352 Bandwidth Corrected : True
1353 <BLANKLINE>
1354 Header
1355 ------
1356 <BLANKLINE>
1357 Manufacturer : Unknown
1358 Catalog Number : N/A
1359 Description : Rare earth fluorescent lamp
1360 Document Creator : byHeart Consultants
1361 Unique Identifier : C3567553-C75B-4354-961E-35CEB9FEB42C
1362 Measurement Equipment : None
1363 Laboratory : N/A
1364 Report Number : N/A
1365 Report Date : N/A
1366 Document Creation Date : 2014-06-23
1367 Comments : Ambient temperature 25 degrees C.
1368 <BLANKLINE>
1369 Spectral Data
1370 -------------
1371 <BLANKLINE>
1372 [[ 4.00000000e+02 3.40000000e-02]
1373 [ 4.03100000e+02 3.70000000e-02]
1374 [ 4.05500000e+02 6.90000000e-02]
1375 [ 4.07500000e+02 3.70000000e-02]
1376 [ 4.20600000e+02 4.20000000e-02]
1377 [ 4.31000000e+02 4.90000000e-02]
1378 [ 4.33700000e+02 6.00000000e-02]
1379 [ 4.37000000e+02 3.57000000e-01]
1380 [ 4.38900000e+02 6.00000000e-02]
1381 [ 4.60000000e+02 6.80000000e-02]
1382 [ 4.77000000e+02 7.50000000e-02]
1383 [ 4.81000000e+02 8.50000000e-02]
1384 [ 4.88200000e+02 2.04000000e-01]
1385 [ 4.92600000e+02 1.66000000e-01]
1386 [ 5.01700000e+02 9.50000000e-02]
1387 [ 5.07600000e+02 7.80000000e-02]
1388 [ 5.17600000e+02 7.10000000e-02]
1389 [ 5.29900000e+02 7.60000000e-02]
1390 [ 5.35400000e+02 9.90000000e-02]
1391 [ 5.39900000e+02 4.23000000e-01]
1392 [ 5.43200000e+02 8.02000000e-01]
1393 [ 5.44400000e+02 7.13000000e-01]
1394 [ 5.47200000e+02 9.99000000e-01]
1395 [ 5.48700000e+02 5.73000000e-01]
1396 [ 5.50200000e+02 3.40000000e-01]
1397 [ 5.53800000e+02 2.08000000e-01]
1398 [ 5.57300000e+02 1.39000000e-01]
1399 [ 5.63700000e+02 1.29000000e-01]
1400 [ 5.74800000e+02 1.31000000e-01]
1401 [ 5.78000000e+02 1.98000000e-01]
1402 [ 5.79200000e+02 1.90000000e-01]
1403 [ 5.80400000e+02 2.05000000e-01]
1404 [ 5.84800000e+02 2.44000000e-01]
1405 [ 5.85900000e+02 2.36000000e-01]
1406 [ 5.87500000e+02 2.56000000e-01]
1407 [ 5.90300000e+02 1.80000000e-01]
1408 [ 5.93500000e+02 2.18000000e-01]
1409 [ 5.95500000e+02 1.59000000e-01]
1410 [ 5.97000000e+02 1.47000000e-01]
1411 [ 5.99400000e+02 1.70000000e-01]
1412 [ 6.02200000e+02 1.34000000e-01]
1413 [ 6.04600000e+02 1.21000000e-01]
1414 [ 6.07400000e+02 1.40000000e-01]
1415 [ 6.09400000e+02 2.29000000e-01]
1416 [ 6.10200000e+02 4.65000000e-01]
1417 [ 6.12000000e+02 9.52000000e-01]
1418 [ 6.14600000e+02 4.77000000e-01]
1419 [ 6.16900000e+02 2.08000000e-01]
1420 [ 6.18500000e+02 1.35000000e-01]
1421 [ 6.22100000e+02 1.50000000e-01]
1422 [ 6.25600000e+02 1.55000000e-01]
1423 [ 6.28400000e+02 1.34000000e-01]
1424 [ 6.31200000e+02 1.68000000e-01]
1425 [ 6.33200000e+02 8.70000000e-02]
1426 [ 6.35600000e+02 6.80000000e-02]
1427 [ 6.42700000e+02 5.80000000e-02]
1428 [ 6.48700000e+02 5.80000000e-02]
1429 [ 6.50700000e+02 7.40000000e-02]
1430 [ 6.52600000e+02 6.30000000e-02]
1431 [ 6.56200000e+02 5.30000000e-02]
1432 [ 6.57000000e+02 5.60000000e-02]
1433 [ 6.60600000e+02 4.90000000e-02]
1434 [ 6.62600000e+02 5.90000000e-02]
1435 [ 6.64200000e+02 4.80000000e-02]
1436 [ 6.86000000e+02 4.10000000e-02]
1437 [ 6.87600000e+02 4.80000000e-02]
1438 [ 6.89200000e+02 3.90000000e-02]
1439 [ 6.92400000e+02 3.80000000e-02]
1440 [ 6.93500000e+02 4.40000000e-02]
1441 [ 6.95500000e+02 3.40000000e-02]
1442 [ 7.02300000e+02 3.60000000e-02]
1443 [ 7.06700000e+02 4.20000000e-02]
1444 [ 7.07100000e+02 6.10000000e-02]
1445 [ 7.10200000e+02 6.10000000e-02]
1446 [ 7.11000000e+02 4.10000000e-02]
1447 [ 7.12200000e+02 5.20000000e-02]
1448 [ 7.14200000e+02 3.30000000e-02]
1449 [ 7.48400000e+02 3.40000000e-02]
1450 [ 7.57900000e+02 3.10000000e-02]
1451 [ 7.60700000e+02 3.90000000e-02]
1452 [ 7.63900000e+02 2.90000000e-02]
1453 [ 8.08800000e+02 2.90000000e-02]
1454 [ 8.10700000e+02 3.90000000e-02]
1455 [ 8.12700000e+02 3.00000000e-02]
1456 [ 8.50100000e+02 3.00000000e-02]]
1457 """
1459 try:
1460 str_parent = super().__str__()
1462 return multiline_str(
1463 self,
1464 [
1465 {
1466 "label": "IES TM-27-14 Spectral Distribution",
1467 "header": True,
1468 },
1469 {"line_break": True},
1470 {"name": "path", "label": "Path"},
1471 {
1472 "name": "spectral_quantity",
1473 "label": "Spectral Quantity",
1474 },
1475 {
1476 "name": "reflection_geometry",
1477 "label": "Reflection Geometry",
1478 },
1479 {
1480 "name": "transmission_geometry",
1481 "label": "Transmission Geometry",
1482 },
1483 {"name": "bandwidth_FWHM", "label": "Bandwidth (FWHM)"},
1484 {
1485 "name": "bandwidth_corrected",
1486 "label": "Bandwidth Corrected",
1487 },
1488 {"line_break": True},
1489 {"label": "Header", "section": True},
1490 {"line_break": True},
1491 {"formatter": lambda x: str(self.header)}, # noqa: ARG005
1492 {"line_break": True},
1493 {"label": "Spectral Data", "section": True},
1494 {"line_break": True},
1495 {"formatter": lambda x: str_parent}, # noqa: ARG005
1496 ],
1497 )
1498 except TypeError: # pragma: no cover
1499 return super().__str__()
1501 def __repr__(self) -> str:
1502 """
1503 Return an evaluable string representation of the *IES TM-27-14*
1504 spectral distribution.
1506 Returns
1507 -------
1508 :class:`str`
1509 Evaluable string representation.
1511 Examples
1512 --------
1513 >>> from os.path import dirname, join
1514 >>> directory = join(dirname(__file__), "tests", "resources")
1515 >>> SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
1516 ... # doctest: +ELLIPSIS
1517 SpectralDistribution_IESTM2714('...',
1518 Header_IESTM2714('Unknown',
1519 'N/A',
1520 'Rare earth ...',
1521 'byHeart Consultants',
1522 'C3567553-C75B-...',
1523 None,
1524 'N/A',
1525 'N/A',
1526 'N/A',
1527 '2014-06-23',
1528 'Ambient ...'),
1529 'relative',
1530 'other',
1531 'other',
1532 2.0,
1533 True,
1534 [[ 4.00000000e+02, 3.40000000e-02],
1535 [ 4.03100000e+02, 3.70000000e-02],
1536 [ 4.05500000e+02, 6.90000000e-02],
1537 [ 4.07500000e+02, 3.70000000e-02],
1538 [ 4.20600000e+02, 4.20000000e-02],
1539 [ 4.31000000e+02, 4.90000000e-02],
1540 [ 4.33700000e+02, 6.00000000e-02],
1541 [ 4.37000000e+02, 3.57000000e-01],
1542 [ 4.38900000e+02, 6.00000000e-02],
1543 [ 4.60000000e+02, 6.80000000e-02],
1544 [ 4.77000000e+02, 7.50000000e-02],
1545 [ 4.81000000e+02, 8.50000000e-02],
1546 [ 4.88200000e+02, 2.04000000e-01],
1547 [ 4.92600000e+02, 1.66000000e-01],
1548 [ 5.01700000e+02, 9.50000000e-02],
1549 [ 5.07600000e+02, 7.80000000e-02],
1550 [ 5.17600000e+02, 7.10000000e-02],
1551 [ 5.29900000e+02, 7.60000000e-02],
1552 [ 5.35400000e+02, 9.90000000e-02],
1553 [ 5.39900000e+02, 4.23000000e-01],
1554 [ 5.43200000e+02, 8.02000000e-01],
1555 [ 5.44400000e+02, 7.13000000e-01],
1556 [ 5.47200000e+02, 9.99000000e-01],
1557 [ 5.48700000e+02, 5.73000000e-01],
1558 [ 5.50200000e+02, 3.40000000e-01],
1559 [ 5.53800000e+02, 2.08000000e-01],
1560 [ 5.57300000e+02, 1.39000000e-01],
1561 [ 5.63700000e+02, 1.29000000e-01],
1562 [ 5.74800000e+02, 1.31000000e-01],
1563 [ 5.78000000e+02, 1.98000000e-01],
1564 [ 5.79200000e+02, 1.90000000e-01],
1565 [ 5.80400000e+02, 2.05000000e-01],
1566 [ 5.84800000e+02, 2.44000000e-01],
1567 [ 5.85900000e+02, 2.36000000e-01],
1568 [ 5.87500000e+02, 2.56000000e-01],
1569 [ 5.90300000e+02, 1.80000000e-01],
1570 [ 5.93500000e+02, 2.18000000e-01],
1571 [ 5.95500000e+02, 1.59000000e-01],
1572 [ 5.97000000e+02, 1.47000000e-01],
1573 [ 5.99400000e+02, 1.70000000e-01],
1574 [ 6.02200000e+02, 1.34000000e-01],
1575 [ 6.04600000e+02, 1.21000000e-01],
1576 [ 6.07400000e+02, 1.40000000e-01],
1577 [ 6.09400000e+02, 2.29000000e-01],
1578 [ 6.10200000e+02, 4.65000000e-01],
1579 [ 6.12000000e+02, 9.52000000e-01],
1580 [ 6.14600000e+02, 4.77000000e-01],
1581 [ 6.16900000e+02, 2.08000000e-01],
1582 [ 6.18500000e+02, 1.35000000e-01],
1583 [ 6.22100000e+02, 1.50000000e-01],
1584 [ 6.25600000e+02, 1.55000000e-01],
1585 [ 6.28400000e+02, 1.34000000e-01],
1586 [ 6.31200000e+02, 1.68000000e-01],
1587 [ 6.33200000e+02, 8.70000000e-02],
1588 [ 6.35600000e+02, 6.80000000e-02],
1589 [ 6.42700000e+02, 5.80000000e-02],
1590 [ 6.48700000e+02, 5.80000000e-02],
1591 [ 6.50700000e+02, 7.40000000e-02],
1592 [ 6.52600000e+02, 6.30000000e-02],
1593 [ 6.56200000e+02, 5.30000000e-02],
1594 [ 6.57000000e+02, 5.60000000e-02],
1595 [ 6.60600000e+02, 4.90000000e-02],
1596 [ 6.62600000e+02, 5.90000000e-02],
1597 [ 6.64200000e+02, 4.80000000e-02],
1598 [ 6.86000000e+02, 4.10000000e-02],
1599 [ 6.87600000e+02, 4.80000000e-02],
1600 [ 6.89200000e+02, 3.90000000e-02],
1601 [ 6.92400000e+02, 3.80000000e-02],
1602 [ 6.93500000e+02, 4.40000000e-02],
1603 [ 6.95500000e+02, 3.40000000e-02],
1604 [ 7.02300000e+02, 3.60000000e-02],
1605 [ 7.06700000e+02, 4.20000000e-02],
1606 [ 7.07100000e+02, 6.10000000e-02],
1607 [ 7.10200000e+02, 6.10000000e-02],
1608 [ 7.11000000e+02, 4.10000000e-02],
1609 [ 7.12200000e+02, 5.20000000e-02],
1610 [ 7.14200000e+02, 3.30000000e-02],
1611 [ 7.48400000e+02, 3.40000000e-02],
1612 [ 7.57900000e+02, 3.10000000e-02],
1613 [ 7.60700000e+02, 3.90000000e-02],
1614 [ 7.63900000e+02, 2.90000000e-02],
1615 [ 8.08800000e+02, 2.90000000e-02],
1616 [ 8.10700000e+02, 3.90000000e-02],
1617 [ 8.12700000e+02, 3.00000000e-02],
1618 [ 8.50100000e+02, 3.00000000e-02]],
1619 CubicSplineInterpolator,
1620 {},
1621 Extrapolator,
1622 {...})
1623 """
1625 try:
1626 return multiline_repr(
1627 self,
1628 [
1629 {"name": "path"},
1630 {"name": "header"},
1631 {"name": "spectral_quantity"},
1632 {"name": "reflection_geometry"},
1633 {"name": "transmission_geometry"},
1634 {"name": "bandwidth_FWHM"},
1635 {"name": "bandwidth_corrected"},
1636 {
1637 "formatter": lambda x: repr( # noqa: ARG005
1638 tstack([self.domain, self.range])
1639 ),
1640 },
1641 {
1642 "name": "interpolator",
1643 "formatter": lambda x: ( # noqa: ARG005
1644 self.interpolator.__name__
1645 ),
1646 },
1647 {"name": "interpolator_kwargs"},
1648 {
1649 "name": "extrapolator",
1650 "formatter": lambda x: ( # noqa: ARG005
1651 self.extrapolator.__name__
1652 ),
1653 },
1654 {"name": "extrapolator_kwargs"},
1655 ],
1656 )
1657 except TypeError: # pragma: no cover
1658 return super().__repr__()
1660 def read(self) -> SpectralDistribution_IESTM2714:
1661 """
1662 Read and parse the spectral data from the specified *XML* file path.
1664 Returns
1665 -------
1666 :class:`colour.SpectralDistribution_IESTM2714`
1667 *IES TM-27-14* spectral distribution.
1669 Raises
1670 ------
1671 ValueError
1672 If the *IES TM-27-14* spectral distribution path is undefined.
1674 Examples
1675 --------
1676 >>> from os.path import dirname, join
1677 >>> directory = join(dirname(__file__), "tests", "resources")
1678 >>> sd = SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
1679 >>> sd.name # doctest: +SKIP
1680 'Unknown - N/A - Rare earth fluorescent lamp'
1681 >>> sd.header.comments
1682 'Ambient temperature 25 degrees C.'
1683 >>> sd[400] # doctest: +ELLIPSIS
1684 0.0340000...
1685 """
1687 if self._path is not None:
1688 formatter = "./{{{0}}}{1}/{{{0}}}{2}"
1690 tree = ET.parse(self._path) # noqa: S314
1691 root = tree.getroot()
1693 match = re.match("{(.*)}", root.tag)
1694 if match:
1695 namespace = match.group(1)
1696 else:
1697 error = (
1698 'The "IES TM-27-14" spectral distribution namespace was not found!'
1699 )
1701 raise ValueError(error)
1703 self.name = os.path.splitext(os.path.basename(self._path))[0]
1705 iterator = root.iter
1707 for header_element in (self.header, self):
1708 mapping = header_element.mapping
1709 for specification in mapping.elements:
1710 element = root.find(
1711 formatter.format(
1712 namespace, mapping.element, specification.element
1713 )
1714 )
1715 if element is not None:
1716 setattr(
1717 header_element,
1718 specification.attribute,
1719 specification.read_conversion(element.text),
1720 )
1722 # Reading spectral data.
1723 wavelengths = []
1724 values = []
1725 for spectral_data in iterator(
1726 f"{{{namespace}}}{self.mapping.data.element}"
1727 ):
1728 wavelengths.append(spectral_data.attrib[self.mapping.data.attribute])
1729 values.append(spectral_data.text)
1731 components = [
1732 component
1733 for component in (
1734 self.header.manufacturer,
1735 self.header.catalog_number,
1736 self.header.description,
1737 )
1738 if component is not None
1739 ]
1740 self.name = "Undefined" if len(components) == 0 else " - ".join(components)
1742 self.wavelengths = as_float_array(wavelengths)
1743 self.values = as_float_array(values)
1745 return self
1747 error = 'The "IES TM-27-14" spectral distribution path is undefined!'
1749 raise ValueError(error)
1751 def write(self) -> bool:
1752 """
1753 Write the spectral distribution spectral data to the specified *XML*
1754 file path.
1756 Returns
1757 -------
1758 :class:`bool`
1759 Definition success.
1761 Raises
1762 ------
1763 ValueError
1764 If the *IES TM-27-14* spectral distribution path is undefined.
1766 Examples
1767 --------
1768 >>> from os.path import dirname, join
1769 >>> from shutil import rmtree
1770 >>> from tempfile import mkdtemp
1771 >>> directory = join(dirname(__file__), "tests", "resources")
1772 >>> sd = SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
1773 >>> temporary_directory = mkdtemp()
1774 >>> sd.path = join(temporary_directory, "Fluorescent.spdx")
1775 >>> sd.write()
1776 True
1777 >>> rmtree(temporary_directory)
1778 """
1780 if self._path is not None:
1781 root = ET.Element("IESTM2714")
1782 root.attrib = {
1783 "xmlns": NAMESPACE_IESTM2714,
1784 "version": VERSION_IESTM2714,
1785 }
1787 spectral_distribution = ET.Element("")
1788 for header_element in (self.header, self):
1789 mapping = header_element.mapping
1790 element = ET.SubElement(root, mapping.element)
1791 for specification in mapping.elements:
1792 element_child = ET.SubElement(element, specification.element)
1793 value = getattr(header_element, specification.attribute)
1794 element_child.text = specification.write_conversion(value)
1796 if header_element is self:
1797 spectral_distribution = element
1799 # Writing spectral data.
1800 for wavelength, value in tstack([self.wavelengths, self.values]):
1801 element_child = ET.SubElement(
1802 spectral_distribution, mapping.data.element
1803 )
1804 element_child.text = mapping.data.write_conversion(value)
1805 element_child.attrib = {
1806 mapping.data.attribute: mapping.data.write_conversion(wavelength)
1807 }
1809 xml = minidom.parseString( # noqa: S318
1810 ET.tostring(root)
1811 ).toprettyxml()
1813 with open(self._path, "w") as file:
1814 file.write(xml)
1816 return True
1818 error = 'The "IES TM-27-14" spectral distribution path is undefined!'
1820 raise ValueError(error)