"""
IES TM-27-14 Data Input / Output
================================
Define the :class:`colour.SpectralDistribution_IESTM2714` class handling *IES
TM-27-14* spectral data *XML* files.
References
----------
- :cite:`IESComputerCommittee2014a` : IES Computer Committee, & TM-27-14
Working Group. (2014). IES Standard Format for the Electronic Transfer of
Spectral Data Electronic Transfer of Spectral Data. Illuminating
Engineering Society. ISBN:978-0-87995-295-2
"""
from __future__ import annotations
import os
import re
from dataclasses import dataclass, field
from pathlib import Path
from xml.dom import minidom
from xml.etree import ElementTree
from colour.colorimetry import SpectralDistribution
from colour.hints import Any, Callable, Literal
from colour.utilities import (
Structure,
as_float_array,
as_float_scalar,
attest,
is_numeric,
multiline_repr,
multiline_str,
optional,
tstack,
)
__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"
__all__ = [
"VERSION_IESTM2714",
"NAMESPACE_IESTM2714",
"Element_Specification_IESTM2714",
"Header_IESTM2714",
"SpectralDistribution_IESTM2714",
]
VERSION_IESTM2714: str = "1.0"
NAMESPACE_IESTM2714: str = "http://www.ies.org/iestm2714"
@dataclass
class Element_Specification_IESTM2714:
"""
*IES TM-27-14* spectral data *XML* file element specification.
Parameters
----------
element
Element name.
attribute
Associated attribute name.
type_
Element type.
required
Is element required.
read_conversion
Method to convert from *XML* to type on reading.
write_conversion
Method to convert from type to *XML* on writing.
"""
element: str
attribute: str
type_: Any = field(default_factory=str)
required: bool = field(default_factory=lambda: False)
read_conversion: Callable = field(
default_factory=lambda: lambda x: None if x == "None" else str(x)
)
write_conversion: Callable = field(default_factory=lambda: str)
class Header_IESTM2714:
"""
Define the header object for a *IES TM-27-14* spectral distribution.
Parameters
----------
manufacturer
Manufacturer of the device under test.
catalog_number
Manufacturer's product catalog number.
description
Description of the spectral data in the spectral data *XML* file.
document_creator
Creator of the spectral data *XML* file, which may be a test lab, a
research group, a standard body, a company or an individual.
unique_identifier
Unique identifier to the product under test or the spectral data in the
document.
measurement_equipment
Description of the equipment used to measure the spectral data.
laboratory
Testing laboratory name that performed the spectral data measurements.
report_number
Testing laboratory report number.
report_date
Testing laboratory report date using the *XML DateTime Data Type*,
*YYYY-MM-DDThh:mm:ss*.
document_creation_date
Spectral data *XML* file creation date using the
*XML DateTime Data Type*, *YYYY-MM-DDThh:mm:ss*.
comments
Additional information relating to the tested and reported data.
Attributes
----------
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.mapping`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.manufacturer`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.catalog_number`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.description`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.document_creator`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.unique_identifier`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.measurement_equipment`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.laboratory`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.report_number`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.report_date`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.document_creation_date`
- :attr:`~colour.io.ies_tm2714.Header_IESTM2714.comments`
Methods
-------
- :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__init__`
- :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__str__`
- :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__repr__`
- :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__hash__`
- :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__eq__`
- :meth:`~colour.io.ies_tm2714.Header_IESTM2714.__ne__`
Examples
--------
>>> Header_IESTM2714("colour-science") # doctest: +ELLIPSIS
Header_IESTM2714('colour-science',
None,
None,
None,
None,
None,
None,
None,
None,
None,
None)
>>> Header_IESTM2714("colour-science").manufacturer # doctest: +SKIP
'colour-science'
"""
def __init__(
self,
manufacturer: str | None = None,
catalog_number: str | None = None,
description: str | None = None,
document_creator: str | None = None,
unique_identifier: str | None = None,
measurement_equipment: str | None = None,
laboratory: str | None = None,
report_number: str | None = None,
report_date: str | None = None,
document_creation_date: str | None = None,
comments: str | None = None,
) -> None:
self._mapping: Structure = Structure(
**{
"element": "Header",
"elements": (
Element_Specification_IESTM2714("Manufacturer", "manufacturer"),
Element_Specification_IESTM2714("CatalogNumber", "catalog_number"),
Element_Specification_IESTM2714(
"Description", "description", required=True
),
Element_Specification_IESTM2714(
"DocumentCreator", "document_creator", required=True
),
Element_Specification_IESTM2714(
"UniqueIdentifier", "unique_identifier"
),
Element_Specification_IESTM2714(
"MeasurementEquipment", "measurement_equipment"
),
Element_Specification_IESTM2714("Laboratory", "laboratory"),
Element_Specification_IESTM2714("ReportNumber", "report_number"),
Element_Specification_IESTM2714("ReportDate", "report_date"),
Element_Specification_IESTM2714(
"DocumentCreationDate",
"document_creation_date",
required=True,
),
Element_Specification_IESTM2714("Comments", "comments", False),
),
}
)
self._manufacturer: str | None = None
self.manufacturer = manufacturer
self._catalog_number: str | None = None
self.catalog_number = catalog_number
self._description: str | None = None
self.description = description
self._document_creator: str | None = None
self.document_creator = document_creator
self._unique_identifier: str | None = None
self.unique_identifier = unique_identifier
self._measurement_equipment: str | None = None
self.measurement_equipment = measurement_equipment
self._laboratory: str | None = None
self.laboratory = laboratory
self._report_number: str | None = None
self.report_number = report_number
self._report_date: str | None = None
self.report_date = report_date
self._document_creation_date: str | None = None
self.document_creation_date = document_creation_date
self._comments: str | None = None
self.comments = comments
@property
def mapping(self) -> Structure:
"""
Getter property for the mapping structure.
Returns
-------
:class:`colour.utilities.Structure`
Mapping structure.
"""
return self._mapping
@property
def manufacturer(self) -> str | None:
"""
Getter and setter property for the manufacturer.
Parameters
----------
value
Value to set the manufacturer with.
Returns
-------
:py:data:`None` or :class:`str`
Manufacturer.
"""
return self._manufacturer
@manufacturer.setter
def manufacturer(self, value: str | None):
"""Setter for the **self.manufacturer** property."""
if value is not None:
attest(
isinstance(value, str),
f'"manufacturer" property: "{value}" type is not "str"!',
)
self._manufacturer = value
@property
def catalog_number(self) -> str | None:
"""
Getter and setter property for the catalog number.
Parameters
----------
value
Value to set the catalog number with.
Returns
-------
:py:data:`None` or :class:`str`
Catalog number.
"""
return self._catalog_number
@catalog_number.setter
def catalog_number(self, value: str | None):
"""Setter for the **self.catalog_number** property."""
if value is not None:
attest(
isinstance(value, str),
f'"catalog_number" property: "{value}" type is not "str"!',
)
self._catalog_number = value
@property
def description(self) -> str | None:
"""
Getter and setter property for the description.
Parameters
----------
value
Value to set the description with.
Returns
-------
:py:data:`None` or :class:`str`
Description.
"""
return self._description
@description.setter
def description(self, value: str | None):
"""Setter for the **self.description** property."""
if value is not None:
attest(
isinstance(value, str),
f'"description" property: "{value}" type is not "str"!',
)
self._description = value
@property
def document_creator(self) -> str | None:
"""
Getter and setter property for the document creator.
Parameters
----------
value
Value to set the document creator with.
Returns
-------
:py:data:`None` or :class:`str`
Document creator.
"""
return self._document_creator
@document_creator.setter
def document_creator(self, value: str | None):
"""Setter for the **self.document_creator** property."""
if value is not None:
attest(
isinstance(value, str),
f'"document_creator" property: "{value}" type is not "str"!',
)
self._document_creator = value
@property
def unique_identifier(self) -> str | None:
"""
Getter and setter property for the unique identifier.
Parameters
----------
value
Value to set the unique identifier with.
Returns
-------
:py:data:`None` or :class:`str`
Unique identifier.
"""
return self._unique_identifier
@unique_identifier.setter
def unique_identifier(self, value: str | None):
"""Setter for the **self.unique_identifier** property."""
if value is not None:
attest(
isinstance(value, str),
f'"unique_identifier" property: "{value}" type is not "str"!',
)
self._unique_identifier = value
@property
def measurement_equipment(self) -> str | None:
"""
Getter and setter property for the measurement equipment.
Parameters
----------
value
Value to set the measurement equipment with.
Returns
-------
:py:data:`None` or :class:`str`
Measurement equipment.
"""
return self._measurement_equipment
@measurement_equipment.setter
def measurement_equipment(self, value: str | None):
"""Setter for the **self.measurement_equipment** property."""
if value is not None:
attest(
isinstance(value, str),
f'"measurement_equipment" property: "{value}" type is not "str"!',
)
self._measurement_equipment = value
@property
def laboratory(self) -> str | None:
"""
Getter and setter property for the laboratory.
Parameters
----------
value
Value to set the laboratory with.
Returns
-------
:py:data:`None` or :class:`str`
Laboratory.
"""
return self._laboratory
@laboratory.setter
def laboratory(self, value: str | None):
"""Setter for the **self.measurement_equipment** property."""
if value is not None:
attest(
isinstance(value, str),
f'"laboratory" property: "{value}" type is not "str"!',
)
self._laboratory = value
@property
def report_number(self) -> str | None:
"""
Getter and setter property for the report number.
Parameters
----------
value
Value to set the report number with.
Returns
-------
:py:data:`None` or :class:`str`
Report number.
"""
return self._report_number
@report_number.setter
def report_number(self, value: str | None):
"""Setter for the **self.report_number** property."""
if value is not None:
attest(
isinstance(value, str),
f'"report_number" property: "{value}" type is not "str"!',
)
self._report_number = value
@property
def report_date(self) -> str | None:
"""
Getter and setter property for the report date.
Parameters
----------
value
Value to set the report date with.
Returns
-------
:py:data:`None` or :class:`str`
Report date.
"""
return self._report_date
@report_date.setter
def report_date(self, value: str | None):
"""Setter for the **self.report_date** property."""
if value is not None:
attest(
isinstance(value, str),
f'"report_date" property: "{value}" type is not "str"!',
)
self._report_date = value
@property
def document_creation_date(self) -> str | None:
"""
Getter and setter property for the document creation date.
Parameters
----------
value
Value to set the document creation date with.
Returns
-------
:py:data:`None` or :class:`str`
Document creation date.
"""
return self._document_creation_date
@document_creation_date.setter
def document_creation_date(self, value: str | None):
"""Setter for the **self.document_creation_date** property."""
if value is not None:
attest(
isinstance(value, str),
f'"document_creation_date" property: "{value}" type is not "str"!',
)
self._document_creation_date = value
@property
def comments(self) -> str | None:
"""
Getter and setter property for the comments.
Parameters
----------
value
Value to set the comments with.
Returns
-------
:py:data:`None` or :class:`str`
Comments.
"""
return self._comments
@comments.setter
def comments(self, value: str | None):
"""Setter for the **self.comments** property."""
if value is not None:
attest(
isinstance(value, str),
f'"comments" property: "{value}" type is not "str"!',
)
self._comments = value
def __str__(self) -> str:
"""
Return a formatted string representation of the header.
Returns
-------
:class:`str`
Formatted string representation.
Examples
--------
>>> print(Header_IESTM2714("colour-science"))
Manufacturer : colour-science
Catalog Number : None
Description : None
Document Creator : None
Unique Identifier : None
Measurement Equipment : None
Laboratory : None
Report Number : None
Report Date : None
Document Creation Date : None
Comments : None
"""
return multiline_str(
self,
[
{"name": "_manufacturer", "label": "Manufacturer"},
{"name": "_catalog_number", "label": "Catalog Number"},
{"name": "_description", "label": "Description"},
{"name": "_document_creator", "label": "Document Creator"},
{"name": "_unique_identifier", "label": "Unique Identifier"},
{
"name": "_measurement_equipment",
"label": "Measurement Equipment",
},
{"name": "_laboratory", "label": "Laboratory"},
{"name": "_report_number", "label": "Report Number"},
{"name": "_report_date", "label": "Report Date"},
{
"name": "_document_creation_date",
"label": "Document Creation Date",
},
{"name": "_comments", "label": "Comments"},
],
)
def __repr__(self) -> str:
"""
Return an evaluable string representation of the header.
Returns
-------
:class:`str`
Evaluable string representation.
Examples
--------
>>> Header_IESTM2714("colour-science")
Header_IESTM2714('colour-science',
None,
None,
None,
None,
None,
None,
None,
None,
None,
None)
"""
return multiline_repr(
self,
[
{"name": "_manufacturer"},
{"name": "_catalog_number"},
{"name": "_description"},
{"name": "_document_creator"},
{"name": "_unique_identifier"},
{"name": "_measurement_equipment"},
{"name": "_laboratory"},
{"name": "_report_number"},
{"name": "_report_date"},
{"name": "_document_creation_date"},
{"name": "_comments"},
],
)
def __hash__(self) -> int:
"""
Return the header hash.
Returns
-------
:class:`int`
Object hash.
"""
return hash(
(
self._manufacturer,
self._catalog_number,
self._description,
self._document_creator,
self._unique_identifier,
self._measurement_equipment,
self._laboratory,
self._report_number,
self._report_date,
self._document_creation_date,
self._comments,
)
)
def __eq__(self, other: Any) -> bool:
"""
Return whether the header is equal to given other object.
Parameters
----------
other
Object to test whether it is equal to the header.
Returns
-------
:class:`bool`
Whether given object is equal to the header.
Examples
--------
>>> Header_IESTM2714("Foo") == Header_IESTM2714("Foo")
True
>>> Header_IESTM2714("Foo") == Header_IESTM2714("Bar")
False
"""
if isinstance(other, Header_IESTM2714):
return all(
[
self._manufacturer == other.manufacturer,
self._catalog_number == other.catalog_number,
self._description == other.description,
self._document_creator == other.document_creator,
self._unique_identifier == other.unique_identifier,
self._measurement_equipment == other.measurement_equipment,
self._laboratory == other.laboratory,
self._report_number == other.report_number,
self._report_date == other.report_date,
self._document_creation_date == other.document_creation_date,
self._comments == other.comments,
]
)
return False
def __ne__(self, other: Any) -> bool:
"""
Return whether the header is not equal to given other object.
Parameters
----------
other
Object to test whether it is not equal to the header.
Returns
-------
:class:`bool`
Whether given object is not equal to the header.
Examples
--------
>>> Header_IESTM2714("Foo") != Header_IESTM2714("Foo")
False
>>> Header_IESTM2714("Foo") != Header_IESTM2714("Bar")
True
"""
return not (self == other)
[docs]
class SpectralDistribution_IESTM2714(SpectralDistribution):
"""
Define a *IES TM-27-14* spectral distribution.
This class can read and write *IES TM-27-14* spectral data *XML* files.
Parameters
----------
path
Spectral data *XML* file path.
header
*IES TM-27-14* spectral distribution header.
spectral_quantity
Quantity of measurement for each element of the spectral data.
reflection_geometry
Spectral reflectance factors geometric conditions.
transmission_geometry
Spectral transmittance factors geometric conditions.
bandwidth_FWHM
Spectroradiometer full-width half-maximum bandwidth in nanometers.
bandwidth_corrected
Specifies if bandwidth correction has been applied to the measured
data.
Other Parameters
----------------
data
Data to be stored in the spectral distribution.
domain
Values to initialise the
:attr:`colour.SpectralDistribution.wavelength` property with.
If both ``data`` and ``domain`` arguments are defined, the latter will
be used to initialise the
:attr:`colour.SpectralDistribution.wavelength` property.
extrapolator
Extrapolator class type to use as extrapolating function.
extrapolator_kwargs
Arguments to use when instantiating the extrapolating function.
interpolator
Interpolator class type to use as interpolating function.
interpolator_kwargs
Arguments to use when instantiating the interpolating function.
name
Spectral distribution name.
display_name
Spectral distribution name for figures, default to
:attr:`colour.SpectralDistribution.name` property value.
Notes
-----
*Reflection Geometry*
- di:8: Diffuse / eight-degree, specular component included.
- de:8: Diffuse / eight-degree, specular component excluded.
- 8:di: Eight-degree / diffuse, specular component included.
- 8:de: Eight-degree / diffuse, specular component excluded.
- d:d: Diffuse / diffuse.
- d:0: Alternative diffuse.
- 45a:0: Forty-five degree annular / normal.
- 45c:0: Forty-five degree circumferential / normal.
- 0:45a: Normal / forty-five degree annular.
- 45x:0: Forty-five degree directional / normal.
- 0:45x: Normal / forty-five degree directional.
- other: User-specified in comments.
*Transmission Geometry*
- 0:0: Normal / normal.
- di:0: Diffuse / normal, regular component included.
- de:0: Diffuse / normal, regular component excluded.
- 0:di: Normal / diffuse, regular component included.
- 0:de: Normal / diffuse, regular component excluded.
- d:d: Diffuse / diffuse.
- other: User-specified in comments.
Attributes
----------
- :attr:`~colour.SpectralDistribution_IESTM2714.mapping`
- :attr:`~colour.SpectralDistribution_IESTM2714.path`
- :attr:`~colour.SpectralDistribution_IESTM2714.header`
- :attr:`~colour.SpectralDistribution_IESTM2714.spectral_quantity`
- :attr:`~colour.SpectralDistribution_IESTM2714.reflection_geometry`
- :attr:`~colour.SpectralDistribution_IESTM2714.transmission_geometry`
- :attr:`~colour.SpectralDistribution_IESTM2714.bandwidth_FWHM`
- :attr:`~colour.SpectralDistribution_IESTM2714.bandwidth_corrected`
Methods
-------
- :meth:`~colour.SpectralDistribution_IESTM2714.__init__`
- :meth:`~colour.SpectralDistribution_IESTM2714.__str__`
- :meth:`~colour.SpectralDistribution_IESTM2714.__repr__`
- :meth:`~colour.SpectralDistribution_IESTM2714.read`
- :meth:`~colour.SpectralDistribution_IESTM2714.write`
References
----------
:cite:`IESComputerCommittee2014a`
Examples
--------
>>> from os.path import dirname, join
>>> directory = join(dirname(__file__), "tests", "resources")
>>> sd = SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
>>> sd.name # doctest: +SKIP
'Unknown - N/A - Rare earth fluorescent lamp'
>>> sd.header.comments
'Ambient temperature 25 degrees C.'
>>> sd[501.7] # doctest: +ELLIPSIS
0.0950000...
"""
[docs]
def __init__(
self,
path: str | Path | None = None,
header: Header_IESTM2714 | None = None,
spectral_quantity: (
Literal[
"absorptance",
"exitance",
"flux",
"intensity",
"irradiance",
"radiance",
"reflectance",
"relative",
"transmittance",
"R-Factor",
"T-Factor",
"other",
]
| None
) = None,
reflection_geometry: (
Literal[
"di:8",
"de:8",
"8:di",
"8:de",
"d:d",
"d:0",
"45a:0",
"45c:0",
"0:45a",
"45x:0",
"0:45x",
"other",
]
| None
) = None,
transmission_geometry: (
Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None
) = None,
bandwidth_FWHM: float | None = None,
bandwidth_corrected: bool | None = None,
**kwargs,
) -> None:
super().__init__(**kwargs)
self._mapping: Structure = Structure(
**{
"element": "SpectralDistribution",
"elements": (
Element_Specification_IESTM2714(
"SpectralQuantity", "spectral_quantity", required=True
),
Element_Specification_IESTM2714(
"ReflectionGeometry", "reflection_geometry"
),
Element_Specification_IESTM2714(
"TransmissionGeometry", "transmission_geometry"
),
Element_Specification_IESTM2714(
"BandwidthFWHM",
"bandwidth_FWHM",
read_conversion=(
lambda x: (None if x == "None" else as_float_scalar(x))
),
),
Element_Specification_IESTM2714(
"BandwidthCorrected",
"bandwidth_corrected",
read_conversion=(lambda x: bool(x == "true")),
write_conversion=(lambda x: "true" if x is True else "false"),
),
),
"data": Element_Specification_IESTM2714(
"SpectralData", "wavelength", required=True
),
}
)
self._path: str | None = None
self.path = path
self._header: Header_IESTM2714 = Header_IESTM2714()
self.header = optional(header, self._header)
self._spectral_quantity: (
Literal[
"absorptance",
"exitance",
"flux",
"intensity",
"irradiance",
"radiance",
"reflectance",
"relative",
"transmittance",
"R-Factor",
"T-Factor",
"other",
]
| None
) = None
self.spectral_quantity = spectral_quantity
self._reflection_geometry: (
Literal[
"di:8",
"de:8",
"8:di",
"8:de",
"d:d",
"d:0",
"45a:0",
"45c:0",
"0:45a",
"45x:0",
"0:45x",
"other",
]
| None
) = None
self.reflection_geometry = reflection_geometry
self._transmission_geometry: (
Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None
) = None
self.transmission_geometry = transmission_geometry
self._bandwidth_FWHM: float | None = None
self.bandwidth_FWHM = bandwidth_FWHM
self._bandwidth_corrected: bool | None = None
self.bandwidth_corrected = bandwidth_corrected
if self.path is not None and os.path.exists(
self.path # pyright: ignore
):
self.read()
@property
def mapping(self) -> Structure:
"""
Getter property for the mapping structure.
Returns
-------
:class:`colour.utilities.Structure`
Mapping structure.
"""
return self._mapping
@property
def path(self) -> str | None:
"""
Getter and setter property for the path.
Parameters
----------
value
Value to set the path with.
Returns
-------
:py:data:`None` or :class:`str`
Path.
"""
return self._path
@path.setter
def path(self, value: str | Path | None):
"""Setter for the **self.path** property."""
if value is not None:
attest(
isinstance(value, (str, Path)),
f'"path" property: "{value}" type is not "str" or "Path"!',
)
value = str(value)
self._path = value
@property
def header(self) -> Header_IESTM2714:
"""
Getter and setter property for the header.
Parameters
----------
value
Value to set the header with.
Returns
-------
:class:`colour.io.tm2714.Header_IESTM2714`
Header.
"""
return self._header
@header.setter
def header(self, value: Header_IESTM2714):
"""Setter for the **self.header** property."""
attest(
isinstance(value, Header_IESTM2714),
f'"header" property: "{value}" type is not "Header_IESTM2714"!',
)
self._header = value
@property
def spectral_quantity(
self,
) -> (
Literal[
"absorptance",
"exitance",
"flux",
"intensity",
"irradiance",
"radiance",
"reflectance",
"relative",
"transmittance",
"R-Factor",
"T-Factor",
"other",
]
| None
):
"""
Getter and setter property for the spectral quantity.
Parameters
----------
value
Value to set the spectral quantity with.
Returns
-------
:py:data:`None` or :class:`str`
Spectral quantity.
"""
return self._spectral_quantity
@spectral_quantity.setter
def spectral_quantity(
self,
value: (
Literal[
"absorptance",
"exitance",
"flux",
"intensity",
"irradiance",
"radiance",
"reflectance",
"relative",
"transmittance",
"R-Factor",
"T-Factor",
"other",
]
| None
),
):
"""Setter for the **self.spectral_quantity** property."""
if value is not None:
attest(
isinstance(value, str),
f'"spectral_quantity" property: "{value}" type is not "str"!',
)
self._spectral_quantity = value
@property
def reflection_geometry(
self,
) -> (
Literal[
"di:8",
"de:8",
"8:di",
"8:de",
"d:d",
"d:0",
"45a:0",
"45c:0",
"0:45a",
"45x:0",
"0:45x",
"other",
]
| None
):
"""
Getter and setter property for the reflection geometry.
Parameters
----------
value
Value to set the reflection geometry with.
Returns
-------
:py:data:`None` or :class:`str`
Reflection geometry.
"""
return self._reflection_geometry
@reflection_geometry.setter
def reflection_geometry(
self,
value: (
Literal[
"di:8",
"de:8",
"8:di",
"8:de",
"d:d",
"d:0",
"45a:0",
"45c:0",
"0:45a",
"45x:0",
"0:45x",
"other",
]
| None
),
):
"""Setter for the **self.reflection_geometry** property."""
if value is not None:
attest(
isinstance(value, str),
f'"reflection_geometry" property: "{value}" type is not "str"!',
)
self._reflection_geometry = value
@property
def transmission_geometry(
self,
) -> Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None:
"""
Getter and setter property for the transmission geometry.
Parameters
----------
value
Value to set the transmission geometry with.
Returns
-------
:py:data:`None` or :class:`str`
Transmission geometry.
"""
return self._transmission_geometry
@transmission_geometry.setter
def transmission_geometry(
self,
value: (Literal["0:0", "di:0", "de:0", "0:di", "0:de", "d:d", "other"] | None),
):
"""Setter for the **self.transmission_geometry** property."""
if value is not None:
attest(
isinstance(value, str),
f'"transmission_geometry" property: "{value}" type is not "str"!',
)
self._transmission_geometry = value
@property
def bandwidth_FWHM(self) -> float | None:
"""
Getter and setter property for the full-width half-maximum bandwidth.
Parameters
----------
value
Value to set the full-width half-maximum bandwidth with.
Returns
-------
:py:data:`None` or :class:`float`
Full-width half-maximum bandwidth.
"""
return self._bandwidth_FWHM
@bandwidth_FWHM.setter
def bandwidth_FWHM(self, value: float | None):
"""Setter for the **self.bandwidth_FWHM** property."""
if value is not None:
attest(
is_numeric(value),
f'"bandwidth_FWHM" property: "{value}" is not a "number"!',
)
value = as_float_scalar(value)
self._bandwidth_FWHM = value
@property
def bandwidth_corrected(self) -> bool | None:
"""
Getter and setter property for whether bandwidth correction has been
applied to the measured data.
Parameters
----------
value
Whether bandwidth correction has been applied to the measured data.
Returns
-------
:py:data:`None` or :class:`bool`
Whether bandwidth correction has been applied to the measured data.
"""
return self._bandwidth_corrected
@bandwidth_corrected.setter
def bandwidth_corrected(self, value: bool | None):
"""Setter for the **self.bandwidth_corrected** property."""
if value is not None:
attest(
isinstance(value, bool),
f'"bandwidth_corrected" property: "{value}" type is not "bool"!',
)
self._bandwidth_corrected = value
[docs]
def __str__(self) -> str:
"""
Return a formatted string representation of the *IES TM-27-14*
spectral distribution.
Returns
-------
:class:`str`
Formatted string representation.
Examples
--------
>>> from os.path import dirname, join
>>> directory = join(dirname(__file__), "tests", "resources")
>>> print(SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx")))
... # doctest: +ELLIPSIS
IES TM-27-14 Spectral Distribution
==================================
<BLANKLINE>
Path : ...
Spectral Quantity : relative
Reflection Geometry : other
Transmission Geometry : other
Bandwidth (FWHM) : 2.0
Bandwidth Corrected : True
<BLANKLINE>
Header
------
<BLANKLINE>
Manufacturer : Unknown
Catalog Number : N/A
Description : Rare earth fluorescent lamp
Document Creator : byHeart Consultants
Unique Identifier : C3567553-C75B-4354-961E-35CEB9FEB42C
Measurement Equipment : None
Laboratory : N/A
Report Number : N/A
Report Date : N/A
Document Creation Date : 2014-06-23
Comments : Ambient temperature 25 degrees C.
<BLANKLINE>
Spectral Data
-------------
<BLANKLINE>
[[ 4.00000000e+02 3.40000000e-02]
[ 4.03100000e+02 3.70000000e-02]
[ 4.05500000e+02 6.90000000e-02]
[ 4.07500000e+02 3.70000000e-02]
[ 4.20600000e+02 4.20000000e-02]
[ 4.31000000e+02 4.90000000e-02]
[ 4.33700000e+02 6.00000000e-02]
[ 4.37000000e+02 3.57000000e-01]
[ 4.38900000e+02 6.00000000e-02]
[ 4.60000000e+02 6.80000000e-02]
[ 4.77000000e+02 7.50000000e-02]
[ 4.81000000e+02 8.50000000e-02]
[ 4.88200000e+02 2.04000000e-01]
[ 4.92600000e+02 1.66000000e-01]
[ 5.01700000e+02 9.50000000e-02]
[ 5.07600000e+02 7.80000000e-02]
[ 5.17600000e+02 7.10000000e-02]
[ 5.29900000e+02 7.60000000e-02]
[ 5.35400000e+02 9.90000000e-02]
[ 5.39900000e+02 4.23000000e-01]
[ 5.43200000e+02 8.02000000e-01]
[ 5.44400000e+02 7.13000000e-01]
[ 5.47200000e+02 9.99000000e-01]
[ 5.48700000e+02 5.73000000e-01]
[ 5.50200000e+02 3.40000000e-01]
[ 5.53800000e+02 2.08000000e-01]
[ 5.57300000e+02 1.39000000e-01]
[ 5.63700000e+02 1.29000000e-01]
[ 5.74800000e+02 1.31000000e-01]
[ 5.78000000e+02 1.98000000e-01]
[ 5.79200000e+02 1.90000000e-01]
[ 5.80400000e+02 2.05000000e-01]
[ 5.84800000e+02 2.44000000e-01]
[ 5.85900000e+02 2.36000000e-01]
[ 5.87500000e+02 2.56000000e-01]
[ 5.90300000e+02 1.80000000e-01]
[ 5.93500000e+02 2.18000000e-01]
[ 5.95500000e+02 1.59000000e-01]
[ 5.97000000e+02 1.47000000e-01]
[ 5.99400000e+02 1.70000000e-01]
[ 6.02200000e+02 1.34000000e-01]
[ 6.04600000e+02 1.21000000e-01]
[ 6.07400000e+02 1.40000000e-01]
[ 6.09400000e+02 2.29000000e-01]
[ 6.10200000e+02 4.65000000e-01]
[ 6.12000000e+02 9.52000000e-01]
[ 6.14600000e+02 4.77000000e-01]
[ 6.16900000e+02 2.08000000e-01]
[ 6.18500000e+02 1.35000000e-01]
[ 6.22100000e+02 1.50000000e-01]
[ 6.25600000e+02 1.55000000e-01]
[ 6.28400000e+02 1.34000000e-01]
[ 6.31200000e+02 1.68000000e-01]
[ 6.33200000e+02 8.70000000e-02]
[ 6.35600000e+02 6.80000000e-02]
[ 6.42700000e+02 5.80000000e-02]
[ 6.48700000e+02 5.80000000e-02]
[ 6.50700000e+02 7.40000000e-02]
[ 6.52600000e+02 6.30000000e-02]
[ 6.56200000e+02 5.30000000e-02]
[ 6.57000000e+02 5.60000000e-02]
[ 6.60600000e+02 4.90000000e-02]
[ 6.62600000e+02 5.90000000e-02]
[ 6.64200000e+02 4.80000000e-02]
[ 6.86000000e+02 4.10000000e-02]
[ 6.87600000e+02 4.80000000e-02]
[ 6.89200000e+02 3.90000000e-02]
[ 6.92400000e+02 3.80000000e-02]
[ 6.93500000e+02 4.40000000e-02]
[ 6.95500000e+02 3.40000000e-02]
[ 7.02300000e+02 3.60000000e-02]
[ 7.06700000e+02 4.20000000e-02]
[ 7.07100000e+02 6.10000000e-02]
[ 7.10200000e+02 6.10000000e-02]
[ 7.11000000e+02 4.10000000e-02]
[ 7.12200000e+02 5.20000000e-02]
[ 7.14200000e+02 3.30000000e-02]
[ 7.48400000e+02 3.40000000e-02]
[ 7.57900000e+02 3.10000000e-02]
[ 7.60700000e+02 3.90000000e-02]
[ 7.63900000e+02 2.90000000e-02]
[ 8.08800000e+02 2.90000000e-02]
[ 8.10700000e+02 3.90000000e-02]
[ 8.12700000e+02 3.00000000e-02]
[ 8.50100000e+02 3.00000000e-02]]
"""
try:
str_parent = super().__str__()
return multiline_str(
self,
[
{
"label": "IES TM-27-14 Spectral Distribution",
"header": True,
},
{"line_break": True},
{"name": "path", "label": "Path"},
{
"name": "spectral_quantity",
"label": "Spectral Quantity",
},
{
"name": "reflection_geometry",
"label": "Reflection Geometry",
},
{
"name": "transmission_geometry",
"label": "Transmission Geometry",
},
{"name": "bandwidth_FWHM", "label": "Bandwidth (FWHM)"},
{
"name": "bandwidth_corrected",
"label": "Bandwidth Corrected",
},
{"line_break": True},
{"label": "Header", "section": True},
{"line_break": True},
{"formatter": lambda x: str(self.header)}, # noqa: ARG005
{"line_break": True},
{"label": "Spectral Data", "section": True},
{"line_break": True},
{"formatter": lambda x: str_parent}, # noqa: ARG005
],
)
except TypeError: # pragma: no cover
return super().__str__()
[docs]
def __repr__(self) -> str:
"""
Return an evaluable string representation of the *IES TM-27-14*
spectral distribution.
Returns
-------
:class:`str`
Evaluable string representation.
Examples
--------
>>> from os.path import dirname, join
>>> directory = join(dirname(__file__), "tests", "resources")
>>> SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
... # doctest: +ELLIPSIS
SpectralDistribution_IESTM2714('...',
Header_IESTM2714('Unknown',
'N/A',
'Rare earth ...',
'byHeart Consultants',
'C3567553-C75B-...',
None,
'N/A',
'N/A',
'N/A',
'2014-06-23',
'Ambient ...'),
'relative',
'other',
'other',
2.0,
True,
[[ 4.00000000e+02, 3.40000000e-02],
[ 4.03100000e+02, 3.70000000e-02],
[ 4.05500000e+02, 6.90000000e-02],
[ 4.07500000e+02, 3.70000000e-02],
[ 4.20600000e+02, 4.20000000e-02],
[ 4.31000000e+02, 4.90000000e-02],
[ 4.33700000e+02, 6.00000000e-02],
[ 4.37000000e+02, 3.57000000e-01],
[ 4.38900000e+02, 6.00000000e-02],
[ 4.60000000e+02, 6.80000000e-02],
[ 4.77000000e+02, 7.50000000e-02],
[ 4.81000000e+02, 8.50000000e-02],
[ 4.88200000e+02, 2.04000000e-01],
[ 4.92600000e+02, 1.66000000e-01],
[ 5.01700000e+02, 9.50000000e-02],
[ 5.07600000e+02, 7.80000000e-02],
[ 5.17600000e+02, 7.10000000e-02],
[ 5.29900000e+02, 7.60000000e-02],
[ 5.35400000e+02, 9.90000000e-02],
[ 5.39900000e+02, 4.23000000e-01],
[ 5.43200000e+02, 8.02000000e-01],
[ 5.44400000e+02, 7.13000000e-01],
[ 5.47200000e+02, 9.99000000e-01],
[ 5.48700000e+02, 5.73000000e-01],
[ 5.50200000e+02, 3.40000000e-01],
[ 5.53800000e+02, 2.08000000e-01],
[ 5.57300000e+02, 1.39000000e-01],
[ 5.63700000e+02, 1.29000000e-01],
[ 5.74800000e+02, 1.31000000e-01],
[ 5.78000000e+02, 1.98000000e-01],
[ 5.79200000e+02, 1.90000000e-01],
[ 5.80400000e+02, 2.05000000e-01],
[ 5.84800000e+02, 2.44000000e-01],
[ 5.85900000e+02, 2.36000000e-01],
[ 5.87500000e+02, 2.56000000e-01],
[ 5.90300000e+02, 1.80000000e-01],
[ 5.93500000e+02, 2.18000000e-01],
[ 5.95500000e+02, 1.59000000e-01],
[ 5.97000000e+02, 1.47000000e-01],
[ 5.99400000e+02, 1.70000000e-01],
[ 6.02200000e+02, 1.34000000e-01],
[ 6.04600000e+02, 1.21000000e-01],
[ 6.07400000e+02, 1.40000000e-01],
[ 6.09400000e+02, 2.29000000e-01],
[ 6.10200000e+02, 4.65000000e-01],
[ 6.12000000e+02, 9.52000000e-01],
[ 6.14600000e+02, 4.77000000e-01],
[ 6.16900000e+02, 2.08000000e-01],
[ 6.18500000e+02, 1.35000000e-01],
[ 6.22100000e+02, 1.50000000e-01],
[ 6.25600000e+02, 1.55000000e-01],
[ 6.28400000e+02, 1.34000000e-01],
[ 6.31200000e+02, 1.68000000e-01],
[ 6.33200000e+02, 8.70000000e-02],
[ 6.35600000e+02, 6.80000000e-02],
[ 6.42700000e+02, 5.80000000e-02],
[ 6.48700000e+02, 5.80000000e-02],
[ 6.50700000e+02, 7.40000000e-02],
[ 6.52600000e+02, 6.30000000e-02],
[ 6.56200000e+02, 5.30000000e-02],
[ 6.57000000e+02, 5.60000000e-02],
[ 6.60600000e+02, 4.90000000e-02],
[ 6.62600000e+02, 5.90000000e-02],
[ 6.64200000e+02, 4.80000000e-02],
[ 6.86000000e+02, 4.10000000e-02],
[ 6.87600000e+02, 4.80000000e-02],
[ 6.89200000e+02, 3.90000000e-02],
[ 6.92400000e+02, 3.80000000e-02],
[ 6.93500000e+02, 4.40000000e-02],
[ 6.95500000e+02, 3.40000000e-02],
[ 7.02300000e+02, 3.60000000e-02],
[ 7.06700000e+02, 4.20000000e-02],
[ 7.07100000e+02, 6.10000000e-02],
[ 7.10200000e+02, 6.10000000e-02],
[ 7.11000000e+02, 4.10000000e-02],
[ 7.12200000e+02, 5.20000000e-02],
[ 7.14200000e+02, 3.30000000e-02],
[ 7.48400000e+02, 3.40000000e-02],
[ 7.57900000e+02, 3.10000000e-02],
[ 7.60700000e+02, 3.90000000e-02],
[ 7.63900000e+02, 2.90000000e-02],
[ 8.08800000e+02, 2.90000000e-02],
[ 8.10700000e+02, 3.90000000e-02],
[ 8.12700000e+02, 3.00000000e-02],
[ 8.50100000e+02, 3.00000000e-02]],
CubicSplineInterpolator,
{},
Extrapolator,
{...})
"""
try:
return multiline_repr(
self,
[
{"name": "path"},
{"name": "header"},
{"name": "spectral_quantity"},
{"name": "reflection_geometry"},
{"name": "transmission_geometry"},
{"name": "bandwidth_FWHM"},
{"name": "bandwidth_corrected"},
{
"formatter": lambda x: repr( # noqa: ARG005
tstack([self.domain, self.range])
),
},
{
"name": "interpolator",
"formatter": lambda x: ( # noqa: ARG005
self.interpolator.__name__
),
},
{"name": "interpolator_kwargs"},
{
"name": "extrapolator",
"formatter": lambda x: ( # noqa: ARG005
self.extrapolator.__name__
),
},
{"name": "extrapolator_kwargs"},
],
)
except TypeError: # pragma: no cover
return super().__repr__()
[docs]
def read(self) -> SpectralDistribution_IESTM2714:
"""
Read and parses the spectral data *XML* file path.
Returns
-------
:class:`colour.SpectralDistribution_IESTM2714`
*IES TM-27-14* spectral distribution.
Raises
------
ValueError
If the *IES TM-27-14* spectral distribution path is undefined.
Examples
--------
>>> from os.path import dirname, join
>>> directory = join(dirname(__file__), "tests", "resources")
>>> sd = SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
>>> sd.name # doctest: +SKIP
'Unknown - N/A - Rare earth fluorescent lamp'
>>> sd.header.comments
'Ambient temperature 25 degrees C.'
>>> sd[400] # doctest: +ELLIPSIS
0.0340000...
"""
if self._path is not None:
formatter = "./{{{0}}}{1}/{{{0}}}{2}"
tree = ElementTree.parse(self._path) # noqa: S314
root = tree.getroot()
match = re.match("{(.*)}", root.tag)
if match:
namespace = match.group(1)
else:
raise ValueError(
'The "IES TM-27-14" spectral distribution namespace '
"was not found!"
)
self.name = os.path.splitext(os.path.basename(self._path))[0]
iterator = root.iter
for header_element in (self.header, self):
mapping = header_element.mapping
for specification in mapping.elements:
element = root.find(
formatter.format(
namespace, mapping.element, specification.element
)
)
if element is not None:
setattr(
header_element,
specification.attribute,
specification.read_conversion(element.text),
)
# Reading spectral data.
wavelengths = []
values = []
for spectral_data in iterator(
f"{{{namespace}}}{self.mapping.data.element}"
):
wavelengths.append(spectral_data.attrib[self.mapping.data.attribute])
values.append(spectral_data.text)
components = [
component
for component in (
self.header.manufacturer,
self.header.catalog_number,
self.header.description,
)
if component is not None
]
self.name = "Undefined" if len(components) == 0 else " - ".join(components)
self.wavelengths = as_float_array(wavelengths)
self.values = as_float_array(values)
return self
else:
raise ValueError(
'The "IES TM-27-14" spectral distribution path is undefined!'
)
[docs]
def write(self) -> bool:
"""
Write the spectral distribution spectral data to *XML* file path.
Returns
-------
:class:`bool`
Definition success.
Examples
--------
>>> from os.path import dirname, join
>>> from shutil import rmtree
>>> from tempfile import mkdtemp
>>> directory = join(dirname(__file__), "tests", "resources")
>>> sd = SpectralDistribution_IESTM2714(join(directory, "Fluorescent.spdx"))
>>> temporary_directory = mkdtemp()
>>> sd.path = join(temporary_directory, "Fluorescent.spdx")
>>> sd.write()
True
>>> rmtree(temporary_directory)
"""
if self._path is not None:
root = ElementTree.Element("IESTM2714")
root.attrib = {
"xmlns": NAMESPACE_IESTM2714,
"version": VERSION_IESTM2714,
}
spectral_distribution = ElementTree.Element("")
for header_element in (self.header, self):
mapping = header_element.mapping
element = ElementTree.SubElement(root, mapping.element)
for specification in mapping.elements:
element_child = ElementTree.SubElement(
element, specification.element
)
value = getattr(header_element, specification.attribute)
element_child.text = specification.write_conversion(value)
if header_element is self:
spectral_distribution = element
# Writing spectral data.
for wavelength, value in tstack([self.wavelengths, self.values]):
element_child = ElementTree.SubElement(
spectral_distribution, mapping.data.element
)
element_child.text = mapping.data.write_conversion(value)
element_child.attrib = {
mapping.data.attribute: mapping.data.write_conversion(wavelength)
}
xml = minidom.parseString( # noqa: S318
ElementTree.tostring(root)
).toprettyxml()
with open(self._path, "w") as file:
file.write(xml)
return True
else:
raise ValueError(
'The "IES TM-27-14" spectral distribution path is undefined!'
)