spectral-io
spectral-io reads, writes, and validates optical spectral data files.
It defines a compact JSON format — spectrum_file_schema.json v1.0.0 — for
UV-Vis and visible-range measurements, designed to be suitable for color-science
calculations, long-term archiving, and data exchange between instruments,
pipelines, and applications.
The format captures everything a downstream calculation needs in one place: the measured spectrum, the physical conditions under which it was taken (instrument, geometry, illuminant, observer), and an optional provenance trail. The crate also ships support for additional formats:
- CSV / TSV (
csvfeature) — generic delimited text with an optionalKEY: VALUEmetadata header block; import via [SpectrumFile::from_csv_path] / [SpectrumFile::from_csv_str], export via [SpectrumFile::to_tsv] / [SpectrumFile::to_csv]. - SpectraShop (
spectrashopfeature) — the tab-separated text format used to distribute the Chromaxion Spectral Library, one of the largest freely available collections of measured spectra; import via [SpectrumFile::from_spectrashop_path] / [SpectrumFile::from_spectrashop_str].
Quick start
Reading a JSON file
use SpectrumFile;
let file = from_path.expect;
for sp in file.spectra
Importing from other formats
- CSV / TSV (
csvfeature):
use SpectrumFile;
let file = from_csv_path
.expect;
let tsv = file.to_tsv;
- SpectraShop (
spectrashopfeature):
use SpectrumFile;
let file = from_spectrashop_path
.expect;
println!;
Resampling to an equidistant grid
use ;
let file = from_path.unwrap;
let target = WavelengthAxis ;
for sp in file.spectra
Serialising back to JSON
Any [SpectrumFile] can be round-tripped through serde_json:
let json = to_string_pretty.expect;
write.unwrap;
Cargo features
| Feature | Default | Description |
|---|---|---|
spectrashop |
no | Enables [SpectrumFile::from_spectrashop_path] and [SpectrumFile::from_spectrashop_str] |
csv |
no | Enables [SpectrumFile::from_csv_path], [SpectrumFile::from_csv_str], [SpectrumFile::to_tsv], [SpectrumFile::to_csv], [SpectrumFile::write_tsv], and [SpectrumFile::write_csv] |
Error handling
All fallible entry points return Result<_, [SpectrumFileError]>.
[SpectrumFileError] has four variants:
Io— file not found or unreadable.Json— not valid JSON.SchemaValidation— structural problems: missing required fields, wrong types, unknown enum values. All errors for the whole file are collected and returned together so you see every problem at once, not just the first.CrossFieldValidation— inter-field constraint failures: wavelength/value array length mismatches, non-monotonic wavelengths, reflectance values outside[0, 1], a"custom"illuminant without its spectral power distribution, etc. Again all errors are collected before returning.
Use [SpectrumFile::from_str_unchecked] to bypass both validation passes when
you are certain the source is well-formed (e.g. data you just wrote yourself).
File format
Files are JSON objects with a schema_version (semver string) and a
file_type of either "single" or "batch".
Single file
Use a single file when the measurement session produced exactly one spectrum —
for example a single colour patch or a one-off transmission measurement.
The spectrum lives directly under the key "spectrum":
Batch file
Use a batch file when multiple spectra share common conditions — a colour
chart, a paint swatch book, or a series of time-series measurements. An
optional "batch_metadata" block carries fields common to the whole set
(title, operator, instrument, measurement conditions) so they do not need
to be repeated on every spectrum:
SpectrumRecord object
Each spectrum has four top-level sections (two required, two optional).
metadata (required)
Descriptive information about what was measured and how. measurement_type
and date are the only required sub-fields; everything else is optional but
strongly encouraged for reproducibility.
| Field | Type | Notes |
|---|---|---|
measurement_type |
string enum | reflectance, transmittance, absorbance, radiance, irradiance, emission, sensitivity |
date |
string | ISO 8601 date (YYYY-MM-DD) |
title |
string | optional human-readable name for the sample |
sample_id |
string | optional machine-readable sample identifier |
operator |
string | optional name or ID of the person who measured |
instrument |
object | optional: manufacturer, model, serial_number, detector_type, light_source |
measurement_conditions |
object | optional: integration_time_ms, averaging, temperature_celsius, geometry, specular_component, spectral_resolution_nm, measurement_aperture_mm, measurement_filter |
surface |
string | optional surface finish of the specimen (e.g. "Matte", "Gloss", "Semigloss") |
sample_backing |
string | optional backing used behind the specimen during measurement (e.g. "Black", "White") |
tags |
string[] | optional free-form labels for search and filtering |
copyright |
string | optional copyright notice (e.g. "© 2024 Acme Lab") |
custom |
object | optional user-defined key/value pairs for application-specific metadata |
wavelength_axis (required)
Exactly one of values_nm or range_nm must be present — not both, not
neither. Use range_nm for the common case of an evenly-spaced grid (e.g.
380–780 nm at 10 nm steps); it is more compact and unambiguous. Use
values_nm when the instrument produces an irregular grid or when the
spacing is not constant.
| Field | Type | Notes |
|---|---|---|
values_nm |
number[] | explicit wavelength list in nm; min 2 entries, strictly increasing |
range_nm |
object | evenly-spaced grid: start, end, and interval (all in nm) |
spectral_data (required)
The measured values, one per wavelength point. For reflectance and
transmittance, values must lie in [0, 1] when scale is "fractional"
(the default), or in [0, 100] when scale is "percent". There is no
range constraint for absorbance, radiance, or irradiance.
| Field | Type | Notes |
|---|---|---|
values |
number[] | measured values, one per wavelength point |
uncertainty |
number[] | optional per-point 1-σ uncertainty, same length as values |
scale |
string enum | "fractional" (0–1, default) or "percent" (0–100) |
color_science (optional)
Metadata needed to perform CIE colorimetric calculations from the spectral
data — the illuminant under which the sample is viewed, the observer (colour
matching functions), and an optional white reference. Pre-computed colorimetric
results (XYZ, Lab, CCT, …) may also be stored here as a convenience cache;
the spectral data is always the authoritative source.
| Field | Type | Notes |
|---|---|---|
illuminant |
string enum | D65, D50, D55, D75, A, B, C, F1–F12, LED-*, or "custom" |
illuminant_custom_sd |
object | required when illuminant is "custom"; provide the SPD as wavelengths_nm and values arrays |
cie_observer |
string enum | "CIE 1931 2 degree" (default), "CIE 1964 10 degree", "CIE 2015 2 degree", "CIE 2015 10 degree" |
white_reference |
object | optional calibration tile description and spectral reflectance values |
results |
object | optional pre-computed colorimetric values — informational only |
Available results sub-fields (all optional):
| Field | Type | Notes |
|---|---|---|
XYZ |
[number, number, number] |
CIE tristimulus values [X, Y, Z] |
xy |
[number, number] |
CIE 1931 chromaticity coordinates [x, y] |
uv_prime |
[number, number] |
CIE 1976 UCS chromaticity [u′, v′] |
Lab |
[number, number, number] |
CIELAB [L*, a*, b*] |
CCT_K |
number | Correlated colour temperature in Kelvin |
Duv |
number | Distance from the Planckian locus (signed, CIE 1960 UCS) |
provenance (optional)
An audit trail recording where the data came from and what has been done to
it. Particularly valuable when spectra have been converted from another
format, averaged, smoothed, or trimmed. Fields: software,
software_version, source_file, source_format, notes, and an ordered
processing_steps array (each step has a step name, description, and
optional parameters object).
Full single-spectrum example
A reflectance measurement of Munsell chip 5R 4/2, measured with a Konica Minolta CM-700d in diffuse/8° geometry, stored as a regular 380–780 nm grid at 10 nm intervals:
Validation
[SpectrumFile::from_path] and [SpectrumFile::from_json_str] run two validation
passes before returning:
- Schema validation — checks required fields, correct types, and that
enum fields (
measurement_type,illuminant,cie_observer,scale) contain only allowed values. - Cross-field validation — checks that
values_nmandvalueshave equal length; thatuncertainty(if present) has the same length; that wavelengths are strictly increasing; that reflectance/transmittance values lie in[0, 1]whenscaleis"fractional"; and that a custom illuminant is accompanied byilluminant_custom_sd.
Both passes collect all errors before returning, so a single call surfaces
every problem in the file at once. Use [SpectrumFile::from_str_unchecked]
to skip validation entirely when the source is fully trusted.
Importing SpectraShop files
SpectraShop is measurement and colour-analysis
software by Robin Myers Imaging. Its tab-separated text export format (.txt)
is used to distribute the
Chromaxion Spectral Library,
which contains measured reflectance, transmittance, and irradiance spectra for
hundreds of real-world materials — paint colours, Munsell chips, colour charts,
photographic filters, monitor primaries, fabrics, inks, and more.
Requires the spectrashop feature.
[SpectrumFile::from_spectrashop_path] and [SpectrumFile::from_spectrashop_str]
parse the format and convert each data record in the BEGIN_DATA/END_DATA
block into a [SpectrumRecord]. File-level metadata (spectrum type, illuminant,
observer, geometry, etc.) is applied to every record. A file with one record
returns [SpectrumFile::Single]; two or more return [SpectrumFile::Batch].
The spectrashop_to_json example binary converts a SpectraShop file to
the spectral-io JSON format and can optionally embed a copyright notice:
cargo run --example spectrashop_to_json -- -c "© Author" input.txt output.json
Format and data licensing
The SpectraShop text format is proprietary to Robin Myers Imaging. A format specification is published at https://www.chromaxion.com/spectral_library/SpectraShop_Import-Export_Format.pdf specifically to permit third-party readers and writers.
Spectral data files from the Chromaxion Spectral Library are subject to the following terms:
- Personal, scientific, and teaching use is free.
- Redistribution requires attribution to Chromaxion.com or Robin Myers.
- Commercial sale of the data in any form requires express written permission from Robin Myers.
All data © Robin D. Myers, all rights reserved worldwide. Contact robin@rmimaging.com for commercial licensing enquiries.
Importing and exporting CSV / TSV files
Requires the csv feature.
[SpectrumFile::from_csv_path] and [SpectrumFile::from_csv_str] read a
generic delimited text file. The delimiter (tab or comma) is auto-detected.
Files have two sections:
-
Header block — zero or more
KEY: VALUEmetadata lines (orKEY = VALUE; orKEY<delim>VALUEfor a set of recognised keywords). Lines starting with#and blank lines are ignored throughout. -
Data block — the first row whose first cell parses as a number (wavelength in nm) starts the data block. The immediately preceding non-blank line (if non-numeric) is the optional column-header row. First column = wavelength; each further column becomes one [
SpectrumRecord].
A file with one data column returns [SpectrumFile::Single]; two or more
return [SpectrumFile::Batch].
Measurement_Type: reflectance
Date: 2026-05-15
Illuminant: D65
wavelength_nm patch_A patch_B
380 0.041 0.089
390 0.052 0.092
400 0.063 0.095
[SpectrumFile::to_tsv] and [SpectrumFile::to_csv] serialise back to
tab- or comma-separated text, writing KEY: VALUE metadata lines so files
round-trip cleanly. [SpectrumFile::write_tsv] and
[SpectrumFile::write_csv] write directly to a file path.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.