Skip to main content

Crate spectral_io

Crate spectral_io 

Source
Expand description

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:

§Quick start

§Reading a JSON file

use spectral_io::SpectrumFile;

let file = SpectrumFile::from_path("spectrum.json").expect("could not load file");
for sp in file.spectra() {
    let (min_nm, max_nm) = sp.wavelength_range_nm().unwrap();
    println!("{}: {} points, {:.0}–{:.0} nm", sp.id, sp.n_points(), min_nm, max_nm);
}

§Importing from other formats

  • CSV / TSV (csv feature):
use spectral_io::SpectrumFile;

let file = SpectrumFile::from_csv_path("measurements.tsv")
    .expect("could not parse file");
let tsv = file.to_tsv();
  • SpectraShop (spectrashop feature):
use spectral_io::SpectrumFile;

let file = SpectrumFile::from_spectrashop_path("Munsell Matte 1994.txt")
    .expect("could not parse SpectraShop file");
println!("{} spectra imported", file.spectra().len());

§Resampling to an equidistant grid

use spectral_io::{SpectrumFile, ResampleMethod, WavelengthAxis, WavelengthRange};

let file = SpectrumFile::from_path("spectrum.json").unwrap();
let target = WavelengthAxis {
    range_nm: Some(WavelengthRange { start: 380.0, end: 780.0, interval: 10.0 }),
    values_nm: None,
};
for sp in file.spectra() {
    let resampled = sp.resample(&target, ResampleMethod::Linear);
    println!("{}: {} points", resampled.id, resampled.n_points());
}

§Serialising back to JSON

Any SpectrumFile can be round-tripped through serde_json:

let json = serde_json::to_string_pretty(&file).expect("serialisation failed");
std::fs::write("output.json", json).unwrap();

§Cargo features

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

{
  "schema_version": "1.0.0",
  "file_type": "single",
  "spectrum": { "id": "patch-01", "metadata": { "..." }, "..." }
}

§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:

{
  "schema_version": "1.0.0",
  "file_type": "batch",
  "batch_metadata": { "title": "Munsell Matte 1994", "date": "1994-01-01" },
  "spectra": [ { "id": "5R 4/2", "..." }, { "id": "5YR 4/2", "..." } ]
}

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

FieldTypeNotes
measurement_typestring enumreflectance, transmittance, absorbance, radiance, irradiance, emission, sensitivity
datestringISO 8601 date (YYYY-MM-DD)
titlestringoptional human-readable name for the sample
sample_idstringoptional machine-readable sample identifier
operatorstringoptional name or ID of the person who measured
instrumentobjectoptional: manufacturer, model, serial_number, detector_type, light_source
measurement_conditionsobjectoptional: integration_time_ms, averaging, temperature_celsius, geometry, specular_component, spectral_resolution_nm, measurement_aperture_mm, measurement_filter
surfacestringoptional surface finish of the specimen (e.g. "Matte", "Gloss", "Semigloss")
sample_backingstringoptional backing used behind the specimen during measurement (e.g. "Black", "White")
tagsstring[]optional free-form labels for search and filtering
copyrightstringoptional copyright notice (e.g. "© 2024 Acme Lab")
customobjectoptional 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.

FieldTypeNotes
values_nmnumber[]explicit wavelength list in nm; min 2 entries, strictly increasing
range_nmobjectevenly-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.

FieldTypeNotes
valuesnumber[]measured values, one per wavelength point
uncertaintynumber[]optional per-point 1-σ uncertainty, same length as values
scalestring 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.

FieldTypeNotes
illuminantstring enumD65, D50, D55, D75, A, B, C, F1F12, LED-*, or "custom"
illuminant_custom_sdobjectrequired when illuminant is "custom"; provide the SPD as wavelengths_nm and values arrays
cie_observerstring enum"CIE 1931 2 degree" (default), "CIE 1964 10 degree", "CIE 2015 2 degree", "CIE 2015 10 degree"
white_referenceobjectoptional calibration tile description and spectral reflectance values
resultsobjectoptional pre-computed colorimetric values — informational only

Available results sub-fields (all optional):

FieldTypeNotes
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_KnumberCorrelated colour temperature in Kelvin
DuvnumberDistance 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:

{
  "schema_version": "1.0.0",
  "file_type": "single",
  "spectrum": {
    "id": "chip-5R-4-2",
    "metadata": {
      "measurement_type": "reflectance",
      "date": "2026-04-01",
      "title": "Munsell 5R 4/2",
      "instrument": { "manufacturer": "Konica Minolta", "model": "CM-700d" },
      "measurement_conditions": { "geometry": "d:8", "specular_component": "excluded" }
    },
    "wavelength_axis": {
      "range_nm": { "start": 380, "end": 780, "interval": 10 }
    },
    "spectral_data": {
      "values": [0.048, 0.051, 0.054, 0.058, 0.063],
      "scale": "fractional"
    },
    "color_science": {
      "illuminant": "D65",
      "cie_observer": "CIE 1931 2 degree",
      "results": {
        "XYZ": [17.35, 9.12, 1.18],
        "xy": [0.629, 0.330],
        "Lab": [36.1, 55.7, 37.2]
      }
    }
  }
}

§Validation

SpectrumFile::from_path and SpectrumFile::from_json_str run two validation passes before returning:

  1. Schema validation — checks required fields, correct types, and that enum fields (measurement_type, illuminant, cie_observer, scale) contain only allowed values.
  2. Cross-field validation — checks that values_nm and values have equal length; that uncertainty (if present) has the same length; that wavelengths are strictly increasing; that reflectance/transmittance values lie in [0, 1] when scale is "fractional"; and that a custom illuminant is accompanied by illuminant_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:

  1. Header block — zero or more KEY: VALUE metadata lines (or KEY = VALUE; or KEY<delim>VALUE for a set of recognised keywords). Lines starting with # and blank lines are ignored throughout.

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

Structs§

BatchMetadata
Optional metadata common to all spectra in a batch file.
ColorScience
Metadata required for CIE colorimetry and color-science calculations.
ColorScienceResults
Pre-computed colorimetric results derived from the spectral data.
CustomIlluminantSd
Spectral power distribution for a custom illuminant.
Instrument
Minimal instrument identification.
MeasurementConditions
Physical conditions under which the measurement was made.
ProcessingStep
A single processing step applied to the raw data.
Provenance
Processing history and software trail.
SpectralData
The measured spectral values.
SpectrumMetadata
Descriptive metadata for one spectrum.
SpectrumRecord
A single spectral measurement.
WavelengthAxis
The wavelength axis of the measurement. All values are in nm.
WavelengthRange
Evenly-spaced wavelength grid defined by start, end, and interval (all in nm).
WhiteReference
White reference / calibration tile.

Enums§

MeasurementType
The physical quantity measured.
ResampleMethod
Method used when resampling a spectrum to a new wavelength axis.
SpectrumFile
The top-level structure of a spectrum JSON file. Tagged by file_type: either "single" or "batch".
SpectrumFileError
All errors that can occur while loading or validating a spectrum file.
SpecularComponent
Whether the specular component is included or excluded.

Type Aliases§

Result