"""
CSV Tabular Data Input / Output
===============================
Define various input / output objects for *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 pathlib import Path
from colour.hints import Any, Dict, NDArrayFloat
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 | Path, **kwargs: Any
) -> Dict[str, NDArrayFloat]:
"""
Read the spectral data from given *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 returns it as an *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.
Notes
-----
- A *CSV* spectral data file should define 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 | Path, **kwargs: Any
) -> Dict[str, SpectralDistribution]:
"""
Read the spectral data from given *CSV* file and returns its content as a
*dict* of :class:`colour.SpectralDistribution` class instances.
Parameters
----------
path
*CSV* file path.
Other Parameters
----------------
kwargs
Keywords arguments passed to :func:`numpy.recfromcsv` definition.
Returns
-------
:class:`dict`
*dict* of :class:`colour.SpectralDistribution` class instances.
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 | Path
) -> bool:
"""
Write the given spectral distributions to given *CSV* file.
Parameters
----------
sds
Spectral distributions to write to given *CSV* file.
path
*CSV* file path.
Returns
-------
:class:`bool`
Definition success.
Raises
------
ValueError
If the given 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