Coverage for io/tm2714.py: 62%

1"""

2 IES TM-27-14 Data Input / Output

3================================

4

5 Define classes and utilities for handling *IES TM-27-14* spectral data *XML*

6 files.

7

8 This module provides the :class:`colour.SpectralDistribution_IESTM2714` class

9 for reading and writing spectral distribution data according to the *IES

10 TM-27-14* standard format for electronic transfer of spectral data.

11

12 References

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"""

19

20 from __future__ import annotations

21

22 import os

23 import re

24 import typing

25 from dataclasses import dataclass, field

26 from pathlib import Path

27 from xml.dom import minidom

28 from xml.etree import ElementTree as ET

29

30 from colour.colorimetry import SpectralDistribution

31

32 if typing.TYPE_CHECKING:

33 from colour.hints import Any, Callable, Literal, PathLike

34

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

46

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"

53

54 __all__ = [

55 "VERSION_IESTM2714",

56 "NAMESPACE_IESTM2714",

57 "Element_Specification_IESTM2714",

58 "Header_IESTM2714",

59 "SpectralDistribution_IESTM2714",

60]

61

62 VERSION_IESTM2714: str = "1.0"

63

64 NAMESPACE_IESTM2714: str = "http://www.ies.org/iestm2714"

65

66

67@dataclass

68 class Element_Specification_IESTM2714:

69 """

70 Define *IES TM-27-14* spectral data *XML* file element specification.

71

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 """

87

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)

96

97

98 class Header_IESTM2714:

99 """

100 Define the header object for an *IES TM-27-14* spectral distribution.

101

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.

131

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`

146

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__`

155

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 """

173

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 )

216

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

239

240 @property

241 def mapping(self) -> Structure:

242 """

243 Getter for the mapping structure.

244

245 Returns

246 -------

247 :class:`colour.utilities.Structure`

248 Mapping structure.

249 """

250

251 return self._mapping

252

253 @property

254 def manufacturer(self) -> str | None:

255 """

256 Getter and setter for the manufacturer name.

257

258 Parameters

259 ----------

260 value

261 Value to set the manufacturer with.

262

263 Returns

264 -------

265 :class:`str` or :py:data:`None`

266 Manufacturer name.

267 """

268

269 return self._manufacturer

270

271 @manufacturer.setter

272 def manufacturer(self, value: str | None) -> None:

273 """Setter for the **self.manufacturer** property."""

274

275 if value is not None:

276 attest(

277 isinstance(value, str),

278 f'"manufacturer" property: "{value}" type is not "str"!',

279 )

280

281 self._manufacturer = value

282

283 @property

284 def catalog_number(self) -> str | None:

285 """

286 Getter and setter for the catalog number.

287

288 Parameters

289 ----------

290 value

291 Value to set the catalog number with.

292

293 Returns

294 -------

295 :class:`str` or :py:data:`None`

296 Catalog number.

297 """

298

299 return self._catalog_number

300

301 @catalog_number.setter

302 def catalog_number(self, value: str | None) -> None:

303 """Setter for the **self.catalog_number** property."""

304

305 if value is not None:

306 attest(

307 isinstance(value, str),

308 f'"catalog_number" property: "{value}" type is not "str"!',

309 )

310

311 self._catalog_number = value

312

313 @property

314 def description(self) -> str | None:

315 """

316 Getter and setter for the description.

317

318 Parameters

319 ----------

320 value

321 Value to set the description with.

322

323 Returns

324 -------

325 :class:`str` or :py:data:`None`

326 Description.

327 """

328

329 return self._description

330

331 @description.setter

332 def description(self, value: str | None) -> None:

333 """Setter for the **self.description** property."""

334

335 if value is not None:

336 attest(

337 isinstance(value, str),

338 f'"description" property: "{value}" type is not "str"!',

339 )

340

341 self._description = value

342

343 @property

344 def document_creator(self) -> str | None:

345 """

346 Getter and setter for the document creator.

347

348 Parameters

349 ----------

350 value

351 Value to set the document creator with.

352

353 Returns

354 -------

355 :class:`str` or :py:data:`None`

356 Document creator.

357 """

358

359 return self._document_creator

360

361 @document_creator.setter

362 def document_creator(self, value: str | None) -> None:

363 """Setter for the **self.document_creator** property."""

364

365 if value is not None:

366 attest(

367 isinstance(value, str),

368 f'"document_creator" property: "{value}" type is not "str"!',

369 )

370

371 self._document_creator = value

372

373 @property

374 def unique_identifier(self) -> str | None:

375 """

376 Getter and setter for the unique identifier.

377

378 Parameters

379 ----------

380 value

381 Value to set the unique identifier with.

382

383 Returns

384 -------

385 :class:`str` or :py:data:`None`

386 Unique identifier.

387 """

388

389 return self._unique_identifier

390

391 @unique_identifier.setter

392 def unique_identifier(self, value: str | None) -> None:

393 """Setter for the **self.unique_identifier** property."""

394

395 if value is not None:

396 attest(

397 isinstance(value, str),

398 f'"unique_identifier" property: "{value}" type is not "str"!',

399 )

400

401 self._unique_identifier = value

402

403 @property

404 def measurement_equipment(self) -> str | None:

405 """

406 Getter and setter for the measurement equipment.

407

408 Parameters

409 ----------

410 value

411 Value to set the measurement equipment with.

412

413 Returns

414 -------

415 :class:`str` or :py:data:`None`

416 Measurement equipment.

417 """

418

419 return self._measurement_equipment

420

421 @measurement_equipment.setter

422 def measurement_equipment(self, value: str | None) -> None:

423 """Setter for the **self.measurement_equipment** property."""

424

425 if value is not None:

426 attest(

427 isinstance(value, str),

428 f'"measurement_equipment" property: "{value}" type is not "str"!',

429 )

430

431 self._measurement_equipment = value

432

433 @property

434 def laboratory(self) -> str | None:

435 """

436 Getter and setter for the laboratory information.

437

438 Parameters

439 ----------

440 value

441 Value to set the laboratory with.

442

443 Returns

444 -------

445 :class:`str` or :py:data:`None`

446 Laboratory.

447 """

448

449 return self._laboratory

450

451 @laboratory.setter

452 def laboratory(self, value: str | None) -> None:

453 """Setter for the **self.laboratory** property."""

454

455 if value is not None:

456 attest(

457 isinstance(value, str),

458 f'"laboratory" property: "{value}" type is not "str"!',

459 )

460

461 self._laboratory = value

462

463 @property

464 def report_number(self) -> str | None:

465 """

466 Getter and setter for the report number.

467

468 Parameters

469 ----------

470 value

471 Value to set the report number with.

472

473 Returns

474 -------

475 :class:`str` or :py:data:`None`

476 Report number.

477 """

478

479 return self._report_number

480

481 @report_number.setter

482 def report_number(self, value: str | None) -> None:

483 """Setter for the **self.report_number** property."""

484

485 if value is not None:

486 attest(

487 isinstance(value, str),

488 f'"report_number" property: "{value}" type is not "str"!',

489 )

490

491 self._report_number = value

492

493 @property

494 def report_date(self) -> str | None:

495 """

496 Getter and setter for the report date.

497

498 Parameters

499 ----------

500 value

501 Value to set the report date with.

502

503 Returns

504 -------

505 :class:`str` or :py:data:`None`

506 Report date.

507 """

508

509 return self._report_date

510

511 @report_date.setter

512 def report_date(self, value: str | None) -> None:

513 """Setter for the **self.report_date** property."""

514

515 if value is not None:

516 attest(

517 isinstance(value, str),

518 f'"report_date" property: "{value}" type is not "str"!',

519 )

520

521 self._report_date = value

522

523 @property

524 def document_creation_date(self) -> str | None:

525 """

526 Getter and setter for the document creation date.

527

528 Parameters

529 ----------

530 value

531 Value to set the document creation date with.

532

533 Returns

534 -------

535 :class:`str` or :py:data:`None`

536 Document creation date.

537 """

538

539 return self._document_creation_date

540

541 @document_creation_date.setter

542 def document_creation_date(self, value: str | None) -> None:

543 """Setter for the **self.document_creation_date** property."""

544

545 if value is not None:

546 attest(

547 isinstance(value, str),

548 f'"document_creation_date" property: "{value}" type is not "str"!',

549 )

550

551 self._document_creation_date = value

552

553 @property

554 def comments(self) -> str | None:

555 """

556 Getter and setter for the comments associated with the object.

557

558 Parameters

559 ----------

560 value

561 Value to set the comments with.

562

563 Returns

564 -------

565 :class:`str` or :py:data:`None`

566 Comments.

567 """

568

569 return self._comments

570

571 @comments.setter

572 def comments(self, value: str | None) -> None:

573 """Setter for the **self.comments** property."""

574

575 if value is not None:

576 attest(

577 isinstance(value, str),

578 f'"comments" property: "{value}" type is not "str"!',

579 )

580

581 self._comments = value

582

583 def __str__(self) -> str:

584 """

585 Return a formatted string representation of the *IES TM-27-14* header.

586

587 Returns

588 -------

589 :class:`str`

590 Formatted string representation displaying the header attributes.

591

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 """

607

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 )

630

631 def __repr__(self) -> str:

632 """

633 Return an evaluable string representation of the header.

634

635 Returns

636 -------

637 :class:`str`

638 Evaluable string representation.

639

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 """

655

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 )

672

673 def __hash__(self) -> int:

674 """

675 Return the header hash.

676

677 Returns

678 -------

679 :class:`int`

680 Object hash.

681 """

682

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 )

698

699 def __eq__(self, other: object) -> bool:

700 """

701 Determine whether the header is equal to the specified other object.

702

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.

706

707 Parameters

708 ----------

709 other

710 Object to test whether it is equal to the header.

711

712 Returns

713 -------

714 :class:`bool`

715 Whether the specified object is equal to the header.

716

717 Examples

718 --------

719 >>> Header_IESTM2714("Foo") == Header_IESTM2714("Foo")

720 True

721 >>> Header_IESTM2714("Foo") == Header_IESTM2714("Bar")

722 False

723 """

724

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 )

741

742 return False

743

744 def __ne__(self, other: object) -> bool:

745 """

746 Determine whether the header is not equal to the specified other object.

747

748 Parameters

749 ----------

750 other

751 Object to test whether it is not equal to the header.

752

753 Returns

754 -------

755 :class:`bool`

756 Whether specified object is not equal to the header.

757

758 Examples

759 --------

760 >>> Header_IESTM2714("Foo") != Header_IESTM2714("Foo")

761 False

762 >>> Header_IESTM2714("Foo") != Header_IESTM2714("Bar")

763 True

764 """

765

766 return not (self == other)

767

768

769 class SpectralDistribution_IESTM2714(SpectralDistribution):

770 """

771 Define an *IES TM-27-14* spectral distribution for electronic transfer of

772 spectral data.

773

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.

779

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.

798

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.

822

823 Notes

824 -----

825 *Reflection Geometry*

826

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.

839

840 *Transmission Geometry*

841

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.

849

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`

860

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`

868

869 References

870 ----------

871 :cite:`IESComputerCommittee2014a`

872

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 """

885

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)

932

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 )

963

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

1012

1013 if self.path is not None and os.path.exists(

1014 self.path # pyright: ignore

1015 ):

1016 self.read()

1017

1018 @property

1019 def mapping(self) -> Structure:

1020 """

1021 Getter for the structure containing file mappings.

1022

1023 Returns

1024 -------

1025 :class:`colour.utilities.Structure`

1026 Mapping structure.

1027 """

1028

1029 return self._mapping

1030

1031 @property

1032 def path(self) -> str | None:

1033 """

1034 Getter and setter for the resource path.

1035

1036 Parameters

1037 ----------

1038 value

1039 Value to set the path with.

1040

1041 Returns

1042 -------

1043 :class:`str` or :py:data:`None`

1044 Path to the resource.

1045 """

1046

1047 return self._path

1048

1049 @path.setter

1050 def path(self, value: str | PathLike | None) -> None:

1051 """Setter for the **self.path** property."""

1052

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 )

1058

1059 value = str(value)

1060

1061 self._path = value

1062

1063 @property

1064 def header(self) -> Header_IESTM2714:

1065 """

1066 Getter and setter for the *IES TM-27-14* spectral distribution header.

1067

1068 Parameters

1069 ----------

1070 value

1071 Value to set the header with.

1072

1073 Returns

1074 -------

1075 :class:`colour.io.tm2714.Header_IESTM2714`

1076 Header object containing spectral distribution metadata.

1077 """

1078

1079 return self._header

1080

1081 @header.setter

1082 def header(self, value: Header_IESTM2714) -> None:

1083 """Setter for the **self.header** property."""

1084

1085 attest(

1086 isinstance(value, Header_IESTM2714),

1087 f'"header" property: "{value}" type is not "Header_IESTM2714"!',

1088 )

1089

1090 self._header = value

1091

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.

1114

1115 Parameters

1116 ----------

1117 value

1118 Value to set the spectral quantity with.

1119

1120 Returns

1121 -------

1122 :class:`str` or :py:data:`None`

1123 Spectral quantity.

1124 """

1125

1126 return self._spectral_quantity

1127

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."""

1150

1151 if value is not None:

1152 attest(

1153 isinstance(value, str),

1154 f'"spectral_quantity" property: "{value}" type is not "str"!',

1155 )

1156

1157 self._spectral_quantity = value

1158

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.

1181

1182 Parameters

1183 ----------

1184 value

1185 Value to set the reflection geometry with.

1186

1187 Returns

1188 -------

1189 :class:`str` or :py:data:`None`

1190 Reflection geometry.

1191 """

1192

1193 return self._reflection_geometry

1194

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."""

1217

1218 if value is not None:

1219 attest(

1220 isinstance(value, str),

1221 f'"reflection_geometry" property: "{value}" type is not "str"!',

1222 )

1223

1224 self._reflection_geometry = value

1225

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.

1232

1233 Parameters

1234 ----------

1235 value

1236 Value to set the transmission geometry with.

1237

1238 Returns

1239 -------

1240 :class:`str` or :py:data:`None`

1241 Transmission geometry.

1242 """

1243

1244 return self._transmission_geometry

1245

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."""

1252

1253 if value is not None:

1254 attest(

1255 isinstance(value, str),

1256 f'"transmission_geometry" property: "{value}" type is not "str"!',

1257 )

1258

1259 self._transmission_geometry = value

1260

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.

1266

1267 Parameters

1268 ----------

1269 value

1270 Value to set the full-width half-maximum bandwidth with.

1271

1272 Returns

1273 -------

1274 :class:`float` or :py:data:`None`

1275 Full-width half-maximum bandwidth.

1276 """

1277

1278 return self._bandwidth_FWHM

1279

1280 @bandwidth_FWHM.setter

1281 def bandwidth_FWHM(self, value: float | None) -> None:

1282 """Setter for the **self.bandwidth_FWHM** property."""

1283

1284 if value is not None:

1285 attest(

1286 is_numeric(value),

1287 f'"bandwidth_FWHM" property: "{value}" is not a "number"!',

1288 )

1289

1290 value = as_float_scalar(value)

1291

1292 self._bandwidth_FWHM = value

1293

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.

1299

1300 Parameters

1301 ----------

1302 value

1303 Whether bandwidth correction has been applied to the measured

1304 data.

1305

1306 Returns

1307 -------

1308 :class:`bool` or :py:data:`None`

1309 Whether bandwidth correction has been applied to the measured

1310 data.

1311 """

1312

1313 return self._bandwidth_corrected

1314

1315 @bandwidth_corrected.setter

1316 def bandwidth_corrected(self, value: bool | None) -> None:

1317 """Setter for the **self.bandwidth_corrected** property."""

1318

1319 if value is not None:

1320 attest(

1321 isinstance(value, bool),

1322 f'"bandwidth_corrected" property: "{value}" type is not "bool"!',

1323 )

1324

1325 self._bandwidth_corrected = value

1326

1327 def __str__(self) -> str:

1328 """

1329 Generate a formatted string representation of the *IES TM-27-14*

1330 spectral distribution.

1331

1332 Returns

1333 -------

1334 :class:`str`

1335 Formatted string representation containing path, spectral

1336 metadata, header details, and spectral data values.

1337

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 """

1458

1459 try:

1460 str_parent = super().__str__()

1461

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__()

1500

1501 def __repr__(self) -> str:

1502 """

1503 Return an evaluable string representation of the *IES TM-27-14*

1504 spectral distribution.

1505

1506 Returns

1507 -------

1508 :class:`str`

1509 Evaluable string representation.

1510

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 """

1624

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__()

1659

1660 def read(self) -> SpectralDistribution_IESTM2714:

1661 """

1662 Read and parse the spectral data from the specified *XML* file path.

1663

1664 Returns

1665 -------

1666 :class:`colour.SpectralDistribution_IESTM2714`

1667 *IES TM-27-14* spectral distribution.

1668

1669 Raises

1670 ------

1671 ValueError

1672 If the *IES TM-27-14* spectral distribution path is undefined.

1673

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 """

1686

1687 if self._path is not None:

1688 formatter = "./{{{0}}}{1}/{{{0}}}{2}"

1689

1690 tree = ET.parse(self._path) # noqa: S314

1691 root = tree.getroot()

1692

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 )

1700

1701 raise ValueError(error)

1702

1703 self.name = os.path.splitext(os.path.basename(self._path))[0]

1704

1705 iterator = root.iter

1706

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 )

1721

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)

1730

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)

1741

1742 self.wavelengths = as_float_array(wavelengths)

1743 self.values = as_float_array(values)

1744

1745 return self

1746

1747 error = 'The "IES TM-27-14" spectral distribution path is undefined!'

1748

1749 raise ValueError(error)

1750

1751 def write(self) -> bool:

1752 """

1753 Write the spectral distribution spectral data to the specified *XML*

1754 file path.

1755

1756 Returns

1757 -------

1758 :class:`bool`

1759 Definition success.

1760

1761 Raises

1762 ------

1763 ValueError

1764 If the *IES TM-27-14* spectral distribution path is undefined.

1765

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 """

1779

1780 if self._path is not None:

1781 root = ET.Element("IESTM2714")

1782 root.attrib = {

1783 "xmlns": NAMESPACE_IESTM2714,

1784 "version": VERSION_IESTM2714,

1785 }

1786

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)

1795

1796 if header_element is self:

1797 spectral_distribution = element

1798

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 }

1808

1809 xml = minidom.parseString( # noqa: S318

1810 ET.tostring(root)

1811 ).toprettyxml()

1812

1813 with open(self._path, "w") as file:

1814 file.write(xml)

1815

1816 return True

1817

1818 error = 'The "IES TM-27-14" spectral distribution path is undefined!'

1819

1820 raise ValueError(error)