plesty.lib.data.spectrum

Spectra: the flagship ExperimentData type.

A SpectrumData is a wavelength axis and the intensity measured on it — one spectrum, or a stack of frames taken on the same axis. It reads LightField .spe files and LightField’s CSV export, writes and reads every native format, and carries the processing a PL analysis starts with. Fitting is analysis and lives in plesty.lib.analyzer.SpectrumFit.

Every processing method returns a new spectrum and appends what it did to metadata["processing"], so a saved result says how it was derived.

from plesty.lib.data import SpectrumData

spectrum = SpectrumData.load("qd1.spe")
prepared = spectrum.crop(770, 780).subtract_background("median")
prepared.find_peaks(prominence=100)

The raw .spe content — every region, the calibration, the XML footer — is available through SpeFile.read() when a spectrum is not the whole story.

An SPE 3.0 file is a 4100-byte binary header, the frame data, and an XML footer. The footer is authoritative: it declares the pixel format, the frame and region layout, and the wavelength calibration, and the legacy binary header fields may be zero. Only the footer offset (bytes 678–685) and the file version (bytes 1992–1995) are read from the header. Older WinSpec (SPE 2.x) files carry no XML footer and are refused rather than guessed at.

Attributes

HC_EV_NM

COORDINATE_PREFIX

COORDINATE_UNITS

_WAVELENGTH_DESCRIPTION

_INTENSITY_DESCRIPTION

Classes

SpeFile

The content of one LightField SPE 3.0 file; read() builds it.

SpectrumData

A wavelength axis and the intensity measured on it.

SpectrumFitData

Line-shape fit results of a spectrum: one row per fitted frame, one column per line.

Functions

_as_array(→ plesty.lib.data.array.PlestyArray)

Coerce value to a PlestyArray, filling metadata it lacks.

_coordinate_unit(→ str | None)

Return the unit a coordinate's name ends in, e.g. deg for hwp_deg.

_frame_coordinates(→ dict[str, ...)

Validate per-frame coordinates and give plain arrays their name and unit.

_split_coordinates(→ dict[str, ...)

Return the coordinate_<name> arrays of a saved spectrum, keyed by name.

_is_frame_block_header(→ bool)

Return whether line opens a frame block (Frame 3), not a column header.

_parse_block_row(→ tuple[float, float] | None)

Parse wavelength intensity from a block row, commas read as decimals.

_plateau_falls(→ bool)

Return whether the flat run starting at start is followed by a drop.

_prominence(→ float)

Return the topographic prominence of the peak at index.

Module Contents

plesty.lib.data.spectrum.HC_EV_NM = 1239.841984
plesty.lib.data.spectrum.COORDINATE_PREFIX = 'coordinate_'
plesty.lib.data.spectrum.COORDINATE_UNITS: dict[str, str]
class plesty.lib.data.spectrum.SpeFile

The content of one LightField SPE 3.0 file; read() builds it.

Variables:
  • version – File format version from the header (3.0).

  • regions – One array per region of interest, each shaped (frames, height, width).

  • wavelength – Wavelength in nm of each pixel column of the first region, or None when the file has no calibration.

  • metadata – Acquisition context read from the XML footer — only the keys found are present (exposure_time_ms, center_wavelength_nm, grating, sensor_temperature_c, created, software, camera, spectrometer).

  • xml – The raw XML footer, for anything metadata omits.

HEADER_SIZE: ClassVar[int] = 4100

Size of the binary header preceding the frame data.

XML_OFFSET_POS: ClassVar[int] = 678

Header position of the XML footer offset (uint64).

VERSION_POS: ClassVar[int] = 1992

Header position of the file version (float32).

PIXEL_FORMATS: ClassVar[dict[str, str]]

Footer pixel formats this reader decodes, mapped to NumPy dtypes.

version: float
regions: tuple[numpy.ndarray, Ellipsis]
wavelength: numpy.ndarray | None
metadata: dict[str, Any]
xml: str = ''
classmethod read(path: str | pathlib.Path) → Self

Read a LightField SPE 3.0 file.

Parameters:

path (str | pathlib.Path) – The .spe file.

Returns:

The file’s regions, wavelength calibration and metadata.

Raises:

ValueError – If the file is not SPE 3.0, is truncated, or uses a pixel format this reader does not know.

Return type:

Self

classmethod _wavelength(root: xml.etree.ElementTree.Element, width: int) → numpy.ndarray | None

Return the per-pixel wavelength of a region width pixels wide.

The calibration covers the sensor; a region cropped or binned on the sensor is mapped through SensorMapping (x offset, width, x binning).

Parameters:
  • root (xml.etree.ElementTree.Element)

  • width (int)

Return type:

numpy.ndarray | None

classmethod _metadata(root: xml.etree.ElementTree.Element) → dict[str, Any]

Collect the acquisition settings a spectrum report needs from the footer.

Parameters:

root (xml.etree.ElementTree.Element)

Return type:

dict[str, Any]

static _local(element: xml.etree.ElementTree.Element) → str

Return an element’s tag without its XML namespace.

Parameters:

element (xml.etree.ElementTree.Element)

Return type:

str

classmethod _find(root: xml.etree.ElementTree.Element, *names: str) → xml.etree.ElementTree.Element | None

Find the first element reached by descending through names, at any depth.

Parameters:
  • root (xml.etree.ElementTree.Element)

  • names (str)

Return type:

xml.etree.ElementTree.Element | None

classmethod _text(root: xml.etree.ElementTree.Element, *names: str) → str | None

Return the stripped text of the element at names, or None.

Parameters:
  • root (xml.etree.ElementTree.Element)

  • names (str)

Return type:

str | None

classmethod _float(root: xml.etree.ElementTree.Element, *names: str) → float | None

Return the element at names parsed as a float, or None.

Parameters:
  • root (xml.etree.ElementTree.Element)

  • names (str)

Return type:

float | None

plesty.lib.data.spectrum._WAVELENGTH_DESCRIPTION = 'Wavelength of each detector pixel.'
plesty.lib.data.spectrum._INTENSITY_DESCRIPTION = 'Detected intensity per pixel; one row per frame when stacked.'
plesty.lib.data.spectrum._as_array(value: numpy.typing.ArrayLike, name: str, unit: str, description: str) → plesty.lib.data.array.PlestyArray

Coerce value to a PlestyArray, filling metadata it lacks.

Parameters:
  • value (numpy.typing.ArrayLike)

  • name (str)

  • unit (str)

  • description (str)

Return type:

plesty.lib.data.array.PlestyArray

class plesty.lib.data.spectrum.SpectrumData(wavelength: numpy.typing.ArrayLike, intensity: numpy.typing.ArrayLike, metadata: dict[str, Any] | None = None, provenance: dict[str, Any] | None = None, coordinates: Mapping[str, numpy.typing.ArrayLike] | None = None)

Bases: plesty.lib.data.experiment_data.ExperimentData

A wavelength axis and the intensity measured on it.

Variables:
  • wavelength – 1-D axis, one value per pixel (default unit nm).

  • intensity – (pixels,) for one spectrum or (frames, pixels) for a stack (default unit counts).

  • coordinates – Per-frame values of the sweep the frames were taken over, by name — e.g. hwp_deg or power_w — each 1-D with one value per frame. Empty for a spectrum that is no sweep.

Parameters:
  • wavelength (numpy.typing.ArrayLike)

  • intensity (numpy.typing.ArrayLike)

  • metadata (dict[str, Any] | None)

  • provenance (dict[str, Any] | None)

  • coordinates (Mapping[str, numpy.typing.ArrayLike] | None)

Validate and store the axis, the intensity and the frame coordinates.

Parameters:
  • wavelength (numpy.typing.ArrayLike) – 1-D axis; a plain array gets unit nm.

  • intensity (numpy.typing.ArrayLike) – 1-D spectrum or 2-D (frames, pixels) stack; a plain array gets unit counts.

  • metadata (dict[str, Any] | None) – Acquisition context.

  • provenance (dict[str, Any] | None) – Origin of the data.

  • coordinates (Mapping[str, numpy.typing.ArrayLike] | None) – Per-frame sweep values by name; a plain array gets the unit its name ends in (_deg, _w, _nm, …).

Raises:

ValueError – If the shapes do not describe a spectrum on the axis, or a coordinate does not have one value per frame.

kind = 'spectrum'

Registry key and schema dtype token, e.g. "spectrum".

schema_version = 1

Version of the array layout this class writes and reads.

wavelength: plesty.lib.data.array.PlestyArray
intensity: plesty.lib.data.array.PlestyArray
coordinates: dict[str, plesty.lib.data.array.PlestyArray]
to_arrays() → dict[str, plesty.lib.data.array.PlestyArray]

Return wavelength, intensity and each coordinate_<name>.

Return type:

dict[str, plesty.lib.data.array.PlestyArray]

classmethod from_arrays(arrays: dict[str, plesty.lib.data.array.PlestyArray], metadata: dict[str, Any], provenance: dict[str, Any]) → Self

Rebuild a spectrum from wavelength, intensity and its coordinates.

Parameters:
  • arrays (dict[str, plesty.lib.data.array.PlestyArray]) – Must hold wavelength and intensity; every coordinate_<name> becomes a coordinate.

  • metadata (dict[str, Any]) – The saved metadata.

  • provenance (dict[str, Any]) – The saved provenance.

Return type:

Self

classmethod example() → Self

Return a 101-pixel spectrum with one Lorentzian line at 775 nm.

Return type:

Self

classmethod from_text(lines: list[str], delimiter: str | None) → Self

Build a spectrum from a text export, in blocks or as one table.

A file in which a Frame … line opens each frame — followed by wavelength intensity rows, whitespace-separated, where a comma may be the decimal separator — is read block by block. Anything else is a table, see from_table().

Parameters:
  • lines (list[str]) – The file’s non-empty lines, # comments removed.

  • delimiter (str | None) – "," for .csv; None for .txt.

Raises:

ValueError – If the blocks hold no data or frames of different lengths.

Return type:

Self

classmethod from_table(columns: list[numpy.ndarray], names: list[str] | None) → Self

Build a spectrum from a plain table.

Two layouts are understood:

  • LightField’s CSV export — columns Frame, Wavelength, Intensity (and Row for multi-row regions, summed per pixel); one line per pixel.

  • Wide — the first column is the wavelength, every further column one frame of intensity.

Parameters:
  • columns (list[numpy.ndarray]) – The table’s columns.

  • names (list[str] | None) – The header row, when the file had one.

Raises:

ValueError – If the table has fewer than two columns.

Return type:

Self

classmethod _from_lightfield_table(columns: list[numpy.ndarray], lowered: list[str]) → Self

Rebuild frames from LightField’s one-line-per-pixel CSV export.

Parameters:
  • columns (list[numpy.ndarray])

  • lowered (list[str])

Return type:

Self

classmethod from_spe(path: str | pathlib.Path, region: int = 0, wavelength: numpy.typing.ArrayLike | None = None) → Self

Read a LightField SPE 3.0 file.

Rows of a multi-row region are summed per pixel (full vertical binning); a single frame becomes a 1-D spectrum, several a stack.

Parameters:
  • path (str | pathlib.Path) – The .spe file.

  • region (int) – Index of the region of interest to read.

  • wavelength (numpy.typing.ArrayLike | None) – Axis to use when the file has no calibration, or to override it.

Returns:

The spectrum, with the file’s acquisition settings in metadata and its name under metadata["source"].

Raises:

ValueError – If the file has no such region, or no calibration and none was given.

Return type:

Self

classmethod from_run(run: Any, file_key: str = 'data_file', coordinates: Sequence[str] = (), where: Mapping[str, Any] | None = None, wavelength: numpy.typing.ArrayLike | None = None) → Self

Stack the spectra an experiment run recorded, in record order.

Each record holding file_key names the raw file a device wrote for that step; the path is translated to this machine through the run’s data paths (a relative path is taken from the run directory). A .spe file is read with from_spe(); a .npy frame, which carries no axis, needs wavelength, and its rows are summed per pixel; any other file goes through load(). A record whose value is itself a SpectrumData is used as it is.

Parameters:
  • run (Any) – A Run or a run directory.

  • file_key (str) – Record field naming the raw file.

  • coordinates (Sequence[str]) – Record fields to keep per frame, e.g. ("hwp_deg", "power_w").

  • where (Mapping[str, Any] | None) – Only records whose fields equal these values, e.g. {"qd": "QD-004"}.

  • wavelength (numpy.typing.ArrayLike | None) – Axis for files without a calibration.

Returns:

One frame per recorded frame, with the requested coordinates; metadata names the run and provenance the run and experiment.

Raises:

ValueError – If no record matches, a record lacks a coordinate, or the files do not share one wavelength axis.

Return type:

Self

classmethod _read_raw(reader: Any, host_path: str, wavelength: numpy.typing.ArrayLike | None) → SpectrumData

Read one raw device file named by a record.

Parameters:
  • reader (Any)

  • host_path (str)

  • wavelength (numpy.typing.ArrayLike | None)

Return type:

SpectrumData

property is_stack: bool

Whether the intensity holds several frames.

Return type:

bool

property n_frames: int

Number of frames (1 for a single spectrum).

Return type:

int

property n_pixels: int

Number of pixels on the wavelength axis.

Return type:

int

frame(index: int) → Self

Return one frame as a single spectrum.

Parameters:

index (int) – Frame index; negative indices count from the end.

Raises:

IndexError – If there is no such frame.

Return type:

Self

combine(method: Literal['mean', 'sum'] = 'mean') → Self

Collapse a stack into one spectrum; the frame coordinates are dropped.

Parameters:

method (Literal['mean', 'sum']) – "mean" or "sum" over the frames.

Raises:

ValueError – If method is unknown.

Return type:

Self

crop(start: float, stop: float) → Self

Keep the pixels whose wavelength lies in [start, stop].

Parameters:
  • start (float) – Lower bound, in the wavelength’s unit.

  • stop (float) – Upper bound, in the wavelength’s unit.

Raises:

ValueError – If no pixel lies in the range.

Return type:

Self

subtract_background(background: float | Literal['min', 'median'] | numpy.typing.ArrayLike = 'median') → Self

Subtract a background from every frame.

Parameters:

background (float | Literal['min', 'median'] | numpy.typing.ArrayLike) – A constant; "min" or "median" of each frame; or an array broadcastable to the intensity (e.g. a dark frame).

Raises:

ValueError – If background is an unknown keyword.

Return type:

Self

normalize(method: Literal['max', 'area'] = 'max') → Self

Scale every frame to a peak of 1 or a unit area; the unit becomes a.u..

Parameters:

method (Literal['max', 'area']) – "max" or "area" (trapezoidal, over the axis).

Raises:

ValueError – If method is unknown or a frame’s norm is zero.

Return type:

Self

energy() → plesty.lib.data.array.PlestyArray

Return the photon energy of each pixel, in eV.

Raises:

ValueError – If the wavelength unit is not a length.

Return type:

plesty.lib.data.array.PlestyArray

find_peaks(frame: int = 0, height: float | None = None, prominence: float | None = None, distance: int = 1) → numpy.ndarray

Find local maxima in one frame (NumPy only).

A plateau counts once, at its first pixel. prominence is the height above the higher of the two lowest points separating the peak from taller neighbours (or from the ends of the axis).

Parameters:
  • frame (int) – Frame to search.

  • height (float | None) – Minimum value of a peak.

  • prominence (float | None) – Minimum prominence of a peak.

  • distance (int) – Minimum separation in pixels; the taller peak wins.

Returns:

Pixel indices of the peaks, in ascending order.

Return type:

numpy.ndarray

peak_prominences(indices: numpy.typing.ArrayLike, frame: int = 0) → numpy.ndarray

Return the prominence of each peak at indices, as find_peaks() defines it.

Parameters:
  • indices (numpy.typing.ArrayLike) – Pixel indices of peaks, e.g. from find_peaks().

  • frame (int) – Frame the peaks are in.

Returns:

One prominence per index, in the intensity’s unit.

Raises:

IndexError – If an index is outside the axis.

Return type:

numpy.ndarray

match_peaks(reference: numpy.typing.ArrayLike, tolerance: float, frame: int = 0) → numpy.ndarray

Relocate known line positions to the strongest local maximum near each.

For each reference position, the local maxima within ±tolerance are searched and the strongest is taken; without a local maximum in range, the highest pixel in range is.

Parameters:
  • reference (numpy.typing.ArrayLike) – Line positions, in the wavelength’s unit — e.g. lines identified in another measurement of the same emitter.

  • tolerance (float) – Half-width of the search range around each position.

  • frame (int) – Frame to search.

Returns:

One pixel index per reference position, in the order given.

Raises:

ValueError – If tolerance is not positive or a range holds no pixel.

Return type:

numpy.ndarray

_derive(wavelength: numpy.typing.ArrayLike, intensity: numpy.typing.ArrayLike, step: dict[str, Any], coordinates: Mapping[str, numpy.typing.ArrayLike] | None = None) → Self

Return a new spectrum carrying this one’s context plus step.

The frame coordinates are kept unless coordinates replaces them.

Parameters:
  • wavelength (numpy.typing.ArrayLike)

  • intensity (numpy.typing.ArrayLike)

  • step (dict[str, Any])

  • coordinates (Mapping[str, numpy.typing.ArrayLike] | None)

Return type:

Self

class plesty.lib.data.spectrum.SpectrumFitData(values: Mapping[str, numpy.typing.ArrayLike], wavelength_unit: str | None = 'nm', intensity_unit: str | None = 'counts', metadata: dict[str, Any] | None = None, provenance: dict[str, Any] | None = None, coordinates: Mapping[str, numpy.typing.ArrayLike] | None = None)

Bases: plesty.lib.data.experiment_data.ExperimentData

Line-shape fit results of a spectrum: one row per fitted frame, one column per line.

Produced by plesty.lib.analyzer.SpectrumFit. Every quantity is an array of shape (frames, peaks), with nan where a frame’s fit failed; frame names the spectrum frame each row came from, and x_ref (one per peak) the position the linear background’s offset refers to.

center

line position (wavelength unit)

fwhm

full width at half maximum (wavelength unit)

height

peak height above the background (intensity unit)

area

integrated intensity (intensity × wavelength unit)

background_offset

background at x_ref (intensity unit)

background_slope

background slope (intensity / wavelength unit)

r_squared

coefficient of determination of the peak’s group

Each quantity but r_squared has a one-sigma <name>_err array. metadata records the model, the background mode and the groups of peaks fitted together. coordinates carries the fitted frames’ sweep values (hwp_deg, power_w, …) from the spectrum, one per row — what a sweep analysis plots the quantities against.

Validate the arrays and attach their units.

Parameters:
  • values (Mapping[str, numpy.typing.ArrayLike]) – Every name in names, as arrays.

  • wavelength_unit (str | None) – Unit of the spectrum’s axis.

  • intensity_unit (str | None) – Unit of the spectrum’s intensity.

  • metadata (dict[str, Any] | None) – Fit context (model, background, groups).

  • provenance (dict[str, Any] | None) – Origin of the data.

  • coordinates (Mapping[str, numpy.typing.ArrayLike] | None) – Sweep values of the fitted frames, one per row.

Raises:

ValueError – If a name is missing or the shapes disagree.

kind = 'spectrum_fit'

Registry key and schema dtype token, e.g. "spectrum".

schema_version = 1

Version of the array layout this class writes and reads.

QUANTITIES: ClassVar[tuple[str, Ellipsis]] = ('center', 'fwhm', 'height', 'area', 'background_offset', 'background_slope')
values: dict[str, plesty.lib.data.array.PlestyArray]

the quantities, their errors, r_squared, frame, x_ref.

Type:

Every array by name

coordinates: dict[str, plesty.lib.data.array.PlestyArray]

Sweep values of the fitted frames by name, one per row.

classmethod names() → tuple[str, Ellipsis]

Return every array name, in storage order.

Return type:

tuple[str, Ellipsis]

static _units(wavelength: str | None, intensity: str | None) → dict[str, str | None]

Return the unit of each array from the spectrum’s units.

Parameters:
  • wavelength (str | None)

  • intensity (str | None)

Return type:

dict[str, str | None]

__getitem__(name: str) → plesty.lib.data.array.PlestyArray

Return the array called name, e.g. fit["center"].

Parameters:

name (str)

Return type:

plesty.lib.data.array.PlestyArray

property n_frames: int

Number of fitted frames.

Return type:

int

property n_peaks: int

Number of fitted lines.

Return type:

int

peak(index: int) → dict[str, plesty.lib.data.array.PlestyArray]

Return every per-frame quantity of one line, as 1-D arrays over frames.

Parameters:

index (int) – Peak index.

Raises:

IndexError – If there is no such peak.

Return type:

dict[str, plesty.lib.data.array.PlestyArray]

to_arrays() → dict[str, plesty.lib.data.array.PlestyArray]

Return every array by name, coordinates as coordinate_<name>.

Return type:

dict[str, plesty.lib.data.array.PlestyArray]

classmethod from_arrays(arrays: dict[str, plesty.lib.data.array.PlestyArray], metadata: dict[str, Any], provenance: dict[str, Any]) → Self

Rebuild fit results from to_arrays(); units travel with the arrays.

Parameters:
Return type:

Self

classmethod example() → Self

Return one Gaussian line at 775 nm fitted in two frames.

Return type:

Self

plesty.lib.data.spectrum._coordinate_unit(name: str) → str | None

Return the unit a coordinate’s name ends in, e.g. deg for hwp_deg.

Parameters:

name (str)

Return type:

str | None

plesty.lib.data.spectrum._frame_coordinates(coordinates: Mapping[str, numpy.typing.ArrayLike] | None, n_frames: int) → dict[str, plesty.lib.data.array.PlestyArray]

Validate per-frame coordinates and give plain arrays their name and unit.

Parameters:
  • coordinates (Mapping[str, numpy.typing.ArrayLike] | None)

  • n_frames (int)

Return type:

dict[str, plesty.lib.data.array.PlestyArray]

plesty.lib.data.spectrum._split_coordinates(arrays: Mapping[str, plesty.lib.data.array.PlestyArray]) → dict[str, plesty.lib.data.array.PlestyArray]

Return the coordinate_<name> arrays of a saved spectrum, keyed by name.

Parameters:

arrays (Mapping[str, plesty.lib.data.array.PlestyArray])

Return type:

dict[str, plesty.lib.data.array.PlestyArray]

plesty.lib.data.spectrum._is_frame_block_header(line: str) → bool

Return whether line opens a frame block (Frame 3), not a column header.

Parameters:

line (str)

Return type:

bool

plesty.lib.data.spectrum._parse_block_row(line: str) → tuple[float, float] | None

Parse wavelength intensity from a block row, commas read as decimals.

Parameters:

line (str)

Return type:

tuple[float, float] | None

plesty.lib.data.spectrum._plateau_falls(y: numpy.ndarray, start: int) → bool

Return whether the flat run starting at start is followed by a drop.

Parameters:
  • y (numpy.ndarray)

  • start (int)

Return type:

bool

plesty.lib.data.spectrum._prominence(y: numpy.ndarray, index: int) → float

Return the topographic prominence of the peak at index.

Parameters:
  • y (numpy.ndarray)

  • index (int)

Return type:

float