Source code for colour.io.tm2714

"""
IES TM-27-14 Data Input / Output
================================

Defines 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,
    is_string,
    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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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(
                is_string(value),
                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( is_string(value) or isinstance(value, 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( is_string(value), 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( is_string(value), 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( is_string(value), 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: 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: 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!' )