Source code for colour.io.tabular

"""
CSV Tabular Data Input / Output
===============================

Define input / output utilities for reading and writing spectral data and
spectral distributions from/to *CSV* tabular data files.

-   :func:`colour.read_spectral_data_from_csv_file`
-   :func:`colour.read_sds_from_csv_file`
-   :func:`colour.write_sds_to_csv_file`
"""

from __future__ import annotations

import csv
import os
import tempfile
import typing

import numpy as np

from colour.colorimetry import SpectralDistribution
from colour.constants import DTYPE_FLOAT_DEFAULT

if typing.TYPE_CHECKING:
    from colour.hints import Any, Dict, NDArrayFloat, PathLike

from colour.hints import cast
from colour.utilities import filter_kwargs

__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__ = [
    "read_spectral_data_from_csv_file",
    "read_sds_from_csv_file",
    "write_sds_to_csv_file",
]


[docs] def read_spectral_data_from_csv_file( path: str | PathLike, **kwargs: Any ) -> Dict[str, NDArrayFloat]: """ Read spectral data from the specified *CSV* file in the following form:: 390, 4.15003e-04, 3.68349e-04, 9.54729e-03 395, 1.05192e-03, 9.58658e-04, 2.38250e-02 400, 2.40836e-03, 2.26991e-03, 5.66498e-02 ... 830, 9.74306e-07, 9.53411e-08, 0.00000 and convert it to a *dict* as follows:: { 'wavelength': ndarray, 'field 1': ndarray, 'field 2': ndarray, ..., 'field n': ndarray } Parameters ---------- path *CSV* file path. Other Parameters ---------------- kwargs Keywords arguments passed to :func:`numpy.recfromcsv` definition. Returns ------- :class:`dict` *CSV* file content. Raises ------ IOError If the file cannot be read. Notes ----- - A *CSV* spectral data file should at least define two fields: one for the wavelengths and one for the associated values of one spectral distribution. Examples -------- >>> import os >>> from pprint import pprint >>> csv_file = os.path.join( ... os.path.dirname(__file__), ... "tests", ... "resources", ... "colorchecker_n_ohta.csv", ... ) >>> sds_data = read_spectral_data_from_csv_file(csv_file) >>> pprint(list(sds_data.keys())) ['wavelength', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24'] """ path = str(path) settings = { "names": True, "delimiter": ",", "case_sensitive": True, # "case_sensitive": "lower", "deletechars": "", "replace_space": " ", "dtype": DTYPE_FLOAT_DEFAULT, } settings.update(**kwargs) transpose = settings.get("transpose") if transpose: delimiter = cast("str", settings.get("delimiter", ",")) with open(path) as csv_file: content = zip(*csv.reader(csv_file, delimiter=delimiter), strict=True) settings["delimiter"] = "," with tempfile.NamedTemporaryFile(mode="w", delete=False) as transposed_csv_file: path = transposed_csv_file.name csv.writer(transposed_csv_file).writerows(content) transposed_csv_file.close() data = np.genfromtxt(path, **filter_kwargs(np.genfromtxt, **settings)) if transpose: os.unlink(transposed_csv_file.name) return {name: data[name] for name in data.dtype.names} # pyright: ignore
[docs] def read_sds_from_csv_file( path: str | PathLike, **kwargs: Any ) -> Dict[str, SpectralDistribution]: """ Read spectral data from the specified *CSV* file and convert its content to a *dict* of :class:`colour.SpectralDistribution` class instances. Parameters ---------- path *CSV* file path. Other Parameters ---------------- kwargs Keywords arguments passed to :func:`numpy.genfromtxt` definition. Returns ------- :class:`dict` *dict* of :class:`colour.SpectralDistribution` class instances. Raises ------ IOError If the file cannot be read. Examples -------- >>> from colour.utilities import numpy_print_options >>> import os >>> csv_file = os.path.join( ... os.path.dirname(__file__), ... "tests", ... "resources", ... "colorchecker_n_ohta.csv", ... ) >>> sds = read_sds_from_csv_file(csv_file) >>> print(tuple(sds.keys())) ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', \ '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24') >>> with numpy_print_options(suppress=True): ... sds["1"] # doctest: +ELLIPSIS ... SpectralDistribution([[380. , 0.048], [385. , 0.051], [390. , 0.055], [395. , 0.06 ], [400. , 0.065], [405. , 0.068], [410. , 0.068], [415. , 0.067], [420. , 0.064], [425. , 0.062], [430. , 0.059], [435. , 0.057], [440. , 0.055], [445. , 0.054], [450. , 0.053], [455. , 0.053], [460. , 0.052], [465. , 0.052], [470. , 0.052], [475. , 0.053], [480. , 0.054], [485. , 0.055], [490. , 0.057], [495. , 0.059], [500. , 0.061], [505. , 0.062], [510. , 0.065], [515. , 0.067], [520. , 0.07 ], [525. , 0.072], [530. , 0.074], [535. , 0.075], [540. , 0.076], [545. , 0.078], [550. , 0.079], [555. , 0.082], [560. , 0.087], [565. , 0.092], [570. , 0.1 ], [575. , 0.107], [580. , 0.115], [585. , 0.122], [590. , 0.129], [595. , 0.134], [600. , 0.138], [605. , 0.142], [610. , 0.146], [615. , 0.15 ], [620. , 0.154], [625. , 0.158], [630. , 0.163], [635. , 0.167], [640. , 0.173], [645. , 0.18 ], [650. , 0.188], [655. , 0.196], [660. , 0.204], [665. , 0.213], [670. , 0.222], [675. , 0.231], [680. , 0.242], [685. , 0.251], [690. , 0.261], [695. , 0.271], [700. , 0.282], [705. , 0.294], [710. , 0.305], [715. , 0.318], [720. , 0.334], [725. , 0.354], [730. , 0.372], [735. , 0.392], [740. , 0.409], [745. , 0.42 ], [750. , 0.436], [755. , 0.45 ], [760. , 0.462], [765. , 0.465], [770. , 0.448], [775. , 0.432], [780. , 0.421]], SpragueInterpolator, {}, Extrapolator, {'method': 'Constant', 'left': None, 'right': None}) """ path = str(path) data = read_spectral_data_from_csv_file(path, **kwargs) fields = list(data.keys()) wavelength_field, sd_fields = fields[0], fields[1:] return { sd_field: SpectralDistribution( data[sd_field], data[wavelength_field], name=sd_field ) for sd_field in sd_fields }
[docs] def write_sds_to_csv_file( sds: Dict[str, SpectralDistribution], path: str | PathLike ) -> bool: """ Write spectral distributions to a CSV file. Parameters ---------- sds Spectral distributions to write to the specified CSV file. path CSV file path. Returns ------- :class:`bool` Definition success. Raises ------ ValueError If the specified spectral distributions have different shapes. """ path = str(path) if len(sds) != 1: shapes = [sd.shape for sd in sds.values()] if not all(shape == shapes[0] for shape in shapes): error = ( "Cannot write spectral distributions " 'with different shapes to "CSV" file!' ) raise ValueError(error) wavelengths = next(iter(sds.values())).wavelengths with open(path, "w") as csv_file: fields = sorted(sds.keys()) writer = csv.DictWriter( csv_file, delimiter=",", fieldnames=["wavelength", *fields], lineterminator="\n", ) writer.writeheader() for wavelength in wavelengths: row = {"wavelength": wavelength} row.update({field: sds[field][wavelength] for field in fields}) writer.writerow(row) return True