Adding a Reader
Import of spectroscopic data with their metadata from various file formats is a key feature of SpectroChemPy. Data
import is handled through the generic read() function (in spectrochempy.core.readers.importer).
Reader functions are package-level APIs because they create datasets from
external data. New readers should therefore be exposed as
scp.read_xxx(...) or, for plugin readers, scp.<plugin>.read_xxx(...).
Do not add reader methods to NDDataset or dataset accessors such as
dataset.read_xxx(...) or dataset.<plugin>.read_xxx(...).
This guide describes the steps to add a specific reader, using the example of reading Grams/Thermo .spc files.
Step 1: Add Tests and Sample Files
Following Test-Driven Development, start by writing tests and providing sample files:
Create test file in
tests/test_core/test_readers/test_xxx.pyAdd sample files in
spectrochempy_data/testdata/xxx_data/Write basic test case:
def test_read_spc():
path = "spc_data/BENZENE.SPC"
dataset = scp.read_spc(path)
assert dataset.shape == (1, 1842)
assert isinstance(dataset, scp.NDDataset)
For local testing, configure the data directory:
scp.preferences.datadir = Path("path/to/testdata")
Step 2: Register the File Format
Add format details in spectrochempy/core/readers/importer.py:
FILETYPES = [
// ...existing code...
("galactic", "GRAMS/Thermo Galactic files (*.spc)"),
]
ALIAS = [
// ...existing code...
("galactic", "spc"),
]
Step 3: Create the Reader Module
Create spectrochempy/core/readers/reader_xxx.py:
# Basic structure for reader_spc.py
from spectrochempy.core.dataset.nddataset import NDDataset
from spectrochempy.core.readers.importer import _importer_method, Importer
__all__ = ["read_spc"]
def read_spc(*paths, **kwargs):
"""Read Thermo Galactic .spc file(s).
Parameters
----------
*paths : str or Path
Path(s) to .spc file(s)
**kwargs
Additional import options
Returns
-------
NDDataset or list of NDDataset
Loaded spectral data
"""
kwargs["filetypes"] = ["GRAMS/Thermo Galactic files (*.spc)"]
kwargs["protocol"] = ["spc"]
importer = Importer()
return importer(*paths, **kwargs)
@_importer_method
def _read_spc(*args, **kwargs):
"""Internal reader implementation."""
dataset, filename = args
// ...implementation details...
return dataset
Step 4: Data Format Guidelines
When implementing the reader:
Always return 2D datasets, even for 1D spectra
Set coordinates, metadata, and provenance on the semantic destination
Include relevant units and descriptions
Keep parser-only temporary state out of the returned dataset
Reader semantic normalization
Use the existing dataset fields for shared meanings instead of inventing reader-specific conventions:
signal identity:
dataset.name,dataset.title,dataset.units,dataset.descriptionsource provenance:
dataset.filename,dataset.origin,dataset.authoracquisition/session time:
dataset.acquisition_datepointwise time or support geometry:
Coordvaluespointwise identifiers or categories:
Coord.labelsimport and processing events:
dataset.historyvendor-specific technical payloads:
dataset.metaparser-only temporary state: do not persist it on the returned dataset
Dual-time rule:
acquisition_date= dataset or session provenancetime coordinates = observation geometry
both may coexist in the same imported dataset
Examples:
a single acquisition start time belongs in
dataset.acquisition_datea timestamp or elapsed-time axis for each spectrum belongs in a
Coordsample IDs, acquisition names, or categorical row identifiers belong in
Coord.labelsvendor parameter blocks belong in
dataset.meta
Example of proper axis setup:
from spectrochempy.core.dataset.coord import Coord
from spectrochempy.core.dataset.nddataset import NDDataset
x_coord = Coord(wavenumbers, title="wavenumber", units="cm^-1")
y_coord = Coord(
elapsed_seconds,
title="elapsed time",
units="s",
labels=sample_ids,
)
dataset = NDDataset(data)
dataset.set_coordset(y=y_coord, x=x_coord)
dataset.name = filename.stem
dataset.title = "absorbance"
dataset.units = "absorbance"
dataset.description = "Dataset imported from vendor_x"
dataset.filename = filename
dataset.origin = "vendor_x"
dataset.acquisition_date = acquisition_start
dataset.history = f"Imported from vendor_x file {filename}"
dataset.meta.instrument_model = instrument_model
dataset.meta.processing_mode = processing_mode
In this example:
elapsed_secondsstays on theycoordinate because it locates each observationsample_idsstays inCoord.labelsbecause it identifies points along the imported axisacquisition_startbecomesdataset.acquisition_datebecause it is session provenancevendor parameters remain in
dataset.metainstead of being promoted to new typed dataset fields
Step 5: Documentation
Add docstrings following NumPy style
Include examples in docstrings
Add reader to main documentation
Update
whatsnew/changelog.rst
For complete implementation examples, see existing readers in spectrochempy/core/readers/.
Step 6: Semantic Reader Tests
In addition to basic shape or import tests, add focused semantic tests for the reader you introduce.
Recommended coverage:
identity:
name,title,units,descriptionprovenance:
filename,origin,author,acquisition_datewhen availablecoordinate semantics: coordinate values, titles, units, and time-axis meaning
labels: which axis carries labels and what they identify
retained
Meta: important vendor-specific technical payloadshistory: import events and vendor processing history when preserved
These tests should check semantic placement, not only raw values. A good reader
test should make it obvious why a field lives on the dataset, on a coordinate,
in labels, or in Meta.