plesty.lib.analyzer.spectrum_fit

Spectrum fits: line shapes (Gaussian, Lorentzian, Voigt) on a background.

Fitting is analysis, so it lives with the analyzers rather than on the data type. Two layers:

  • SpectrumFit — the Analyzer: a SpectrumData in; every line fitted in every frame out, as SpectrumFitData (centre, FWHM, height, area and background, each ± σ, shaped (frames, peaks)), plus the fitted curve. It seeds from the average spectrum, tracks each line from frame to frame, and fits overlapping lines together.

  • fit_spectrum() and fit_peak() — the primitives on any 1-D x/y: several line shapes on a constant or linear background, with bounds and per-point errors, for analyzers that fit something other than a spectrum stack.

Every line shape is height-parameterised, so height is the peak value above the background and area the integrated intensity, whatever the shape. Fitting needs SciPy, shipped in the fit extra (uv add "plesty-lib[fit]").

from plesty.lib.analyzer import SpectrumFit

outputs = SpectrumFit(model="gaussian", background="linear")(spectrum=spectrum)
fit = outputs["fit"]
fit["center"], fit["center_err"], fit["area"]      # (frames, peaks)

Attributes

MODELS

BACKGROUNDS

AVERAGES

_GAUSS_FWHM

_WIDTH_PARAMETERS

_AT_BOUND

_OUTLIER_Z

DIAGNOSTICS

_FUNCTIONS

Exceptions

PeakFitError

Raised when a fit does not converge.

FitQualityWarning

Warned when fitted lines show signs of a fit that did not follow its line.

Classes

Peak

One line to fit, with its starting values and constraints.

PeakResult

The fitted values of one line, each with its one-sigma error.

SpectrumFitResult

Result of fit_spectrum().

SpectrumFit

Fit every line in every frame of a spectrum.

Functions

_require_scipy(→ Any)

Import SciPy, naming the extra that provides it when it is missing.

gaussian(→ numpy.ndarray)

Gaussian of peak height amplitude above offset.

lorentzian(→ numpy.ndarray)

Lorentzian of peak height amplitude above offset.

voigt(→ numpy.ndarray)

Voigt profile of peak height amplitude above offset (needs SciPy).

voigt_fwhm(→ float)

FWHM of a Voigt profile (Olivero & Longbothum, accurate to ~0.02 %).

_fwhm(→ float)

Convert the shape's intrinsic width parameters to the line's FWHM.

_area(→ float)

Return the integrated area of a height-parameterised line shape.

_widths_from_fwhm(→ list[float])

Return the model's intrinsic width parameters for a target FWHM.

_model_function(→ Callable[Ellipsis, numpy.ndarray])

Return f(x, *values): background plus n_peaks line shapes.

fit_spectrum(→ SpectrumFitResult)

Fit one or more line shapes on a background.

fit_peak(→ SpectrumFitResult)

Fit one line on a background — fit_spectrum() with a single Peak.

_bounds(→ tuple[numpy.ndarray, numpy.ndarray])

Return the lower and upper bound of every parameter.

_inside(→ numpy.ndarray)

Move starting values strictly inside their bounds.

_estimate_width(→ float | None)

Return the distance between the half-maximum crossings around index.

_initial_guess(→ numpy.ndarray)

Return data-driven starting values for fit_spectrum().

_propagate(→ float)

Return the one-sigma error of function(values) from the covariance block.

Module Contents

plesty.lib.analyzer.spectrum_fit.MODELS: tuple[str, Ellipsis] = ('gaussian', 'lorentzian', 'voigt')
plesty.lib.analyzer.spectrum_fit.BACKGROUNDS: tuple[str, Ellipsis] = ('constant', 'linear')
plesty.lib.analyzer.spectrum_fit.AVERAGES: tuple[str, Ellipsis] = ('mean', 'median')
plesty.lib.analyzer.spectrum_fit._GAUSS_FWHM
plesty.lib.analyzer.spectrum_fit._WIDTH_PARAMETERS: dict[str, tuple[str, Ellipsis]]
exception plesty.lib.analyzer.spectrum_fit.PeakFitError

Bases: RuntimeError

Raised when a fit does not converge.

Initialize self. See help(type(self)) for accurate signature.

exception plesty.lib.analyzer.spectrum_fit.FitQualityWarning

Bases: UserWarning

Warned when fitted lines show signs of a fit that did not follow its line.

Initialize self. See help(type(self)) for accurate signature.

plesty.lib.analyzer.spectrum_fit._AT_BOUND = 0.001
plesty.lib.analyzer.spectrum_fit._OUTLIER_Z = 5.0
plesty.lib.analyzer.spectrum_fit.DIAGNOSTICS: tuple[str, Ellipsis] = ('failed', 'no_error', 'center_at_bound', 'width_at_bound', 'jumps', 'outliers')
plesty.lib.analyzer.spectrum_fit._require_scipy() → Any

Import SciPy, naming the extra that provides it when it is missing.

Return type:

Any

plesty.lib.analyzer.spectrum_fit.gaussian(x: numpy.typing.ArrayLike, amplitude: float, center: float, sigma: float, offset: float) → numpy.ndarray

Gaussian of peak height amplitude above offset.

Parameters:
  • x (numpy.typing.ArrayLike) – Positions.

  • amplitude (float) – Peak height above the offset.

  • center (float) – Peak position.

  • sigma (float) – Standard deviation; FWHM is 2·sqrt(2·ln 2)·sigma.

  • offset (float) – Constant background.

Returns:

The model evaluated at x.

Return type:

numpy.ndarray

plesty.lib.analyzer.spectrum_fit.lorentzian(x: numpy.typing.ArrayLike, amplitude: float, center: float, gamma: float, offset: float) → numpy.ndarray

Lorentzian of peak height amplitude above offset.

Parameters:
  • x (numpy.typing.ArrayLike) – Positions.

  • amplitude (float) – Peak height above the offset.

  • center (float) – Peak position.

  • gamma (float) – Half width at half maximum; FWHM is 2·gamma.

  • offset (float) – Constant background.

Returns:

The model evaluated at x.

Return type:

numpy.ndarray

plesty.lib.analyzer.spectrum_fit.voigt(x: numpy.typing.ArrayLike, amplitude: float, center: float, sigma: float, gamma: float, offset: float) → numpy.ndarray

Voigt profile of peak height amplitude above offset (needs SciPy).

Parameters:
  • x (numpy.typing.ArrayLike) – Positions.

  • amplitude (float) – Peak height above the offset.

  • center (float) – Peak position.

  • sigma (float) – Gaussian standard deviation.

  • gamma (float) – Lorentzian half width at half maximum.

  • offset (float) – Constant background.

Returns:

The model evaluated at x.

Return type:

numpy.ndarray

plesty.lib.analyzer.spectrum_fit.voigt_fwhm(sigma: float, gamma: float) → float

FWHM of a Voigt profile (Olivero & Longbothum, accurate to ~0.02 %).

Parameters:
  • sigma (float) – Gaussian standard deviation.

  • gamma (float) – Lorentzian half width at half maximum.

Returns:

The full width at half maximum.

Return type:

float

plesty.lib.analyzer.spectrum_fit._FUNCTIONS: dict[str, Callable[Ellipsis, numpy.ndarray]]
plesty.lib.analyzer.spectrum_fit._fwhm(model: str, widths: Sequence[float] | numpy.ndarray) → float

Convert the shape’s intrinsic width parameters to the line’s FWHM.

widths are the model-specific broadening parameters: sigma for a Gaussian, gamma for a Lorentzian, and (sigma, gamma) for a Voigt. The returned FWHM is the physical width at half maximum in the same units as the fitted axis.

Parameters:
  • model (str)

  • widths (Sequence[float] | numpy.ndarray)

Return type:

float

plesty.lib.analyzer.spectrum_fit._area(model: str, height: float, widths: Sequence[float] | numpy.ndarray) → float

Return the integrated area of a height-parameterised line shape.

height is the peak value above the background, while widths are the model-specific width parameters rather than the final FWHM. For Gaussian and Lorentzian profiles, the area is analytic; for Voigt, it is derived from the central value of the Voigt profile.

Parameters:
  • model (str)

  • height (float)

  • widths (Sequence[float] | numpy.ndarray)

Return type:

float

plesty.lib.analyzer.spectrum_fit._widths_from_fwhm(model: str, fwhm: float) → list[float]

Return the model’s intrinsic width parameters for a target FWHM.

This is used to seed a fit from a rough width estimate in the physical FWHM units of the data. The conversion differs for each line shape because each model uses a different definition of its broadening parameter.

Parameters:
  • model (str)

  • fwhm (float)

Return type:

list[float]

class plesty.lib.analyzer.spectrum_fit.Peak

One line to fit, with its starting values and constraints.

Variables:
  • center – Initial line position.

  • width – Initial FWHM; estimated from the data when None.

  • center_bounds – (low, high) for the position; the fitted range when None.

  • width_bounds – (low, high) bounds on the FWHM in the same units as x. If omitted, the width is constrained to stay above roughly one sample spacing, preventing a narrow noise spike from fitting as a line. For a Voigt profile, the bound is applied to each of the two width components separately.

  • nonnegative – Keep the height at or above zero.

center: float
width: float | None = None
center_bounds: tuple[float, float] | None = None
width_bounds: tuple[float, float] | None = None
nonnegative: bool = True
class plesty.lib.analyzer.spectrum_fit.PeakResult

The fitted values of one line, each with its one-sigma error.

Variables:
  • params – The raw line-shape parameters (height, center, sigma and/or gamma).

  • errors – Their one-sigma errors (nan when not estimable).

center: float
center_err: float
fwhm: float
fwhm_err: float
height: float
height_err: float
area: float
area_err: float
params: dict[str, float]
errors: dict[str, float]
class plesty.lib.analyzer.spectrum_fit.SpectrumFitResult

Result of fit_spectrum().

Variables:
  • model – The line shape of every peak.

  • background – "constant" or "linear".

  • peaks – One PeakResult per peak, in the order given.

  • background_params – offset (at x_ref) and slope (0 for a constant background).

  • background_errors – Their one-sigma errors.

  • x_ref – Position the background offset refers to.

  • r_squared – Coefficient of determination.

  • values – The optimiser parameter tuple in fixed order: background terms first (offset and optionally slope), then for each peak (height, center, ...) with the width parameters in the model’s native order (sigma for Gaussian, gamma for Lorentzian, sigma, gamma for Voigt). Examples: (offset, slope, h1, c1, sigma1, h2, c2, sigma2) for two Gaussian peaks on a linear background; (offset, slope, h, c, gamma) for one Lorentzian peak on a linear background; (offset, h, c, sigma, gamma) for one Voigt peak on a constant background. This is the exact order expected by _model_function() and can be reused as a seed for a later fit.

  • covariance – The parameter covariance matrix in the same order as values.

model: str
background: str
peaks: tuple[PeakResult, Ellipsis]
background_params: dict[str, float]
background_errors: dict[str, float]
x_ref: float
r_squared: float
values: tuple[float, Ellipsis]
covariance: numpy.ndarray
evaluate(x: numpy.typing.ArrayLike) → numpy.ndarray

Evaluate the fitted model — background plus every line — at x.

Parameters:

x (numpy.typing.ArrayLike)

Return type:

numpy.ndarray

to_dict() → dict[str, Any]

Return the result as a JSON-serializable dictionary (no covariance).

Return type:

dict[str, Any]

plesty.lib.analyzer.spectrum_fit._model_function(model: str, background: str, n_peaks: int, x_ref: float) → Callable[Ellipsis, numpy.ndarray]

Return f(x, *values): background plus n_peaks line shapes.

Parameters:
  • model (str)

  • background (str)

  • n_peaks (int)

  • x_ref (float)

Return type:

Callable[Ellipsis, numpy.ndarray]

plesty.lib.analyzer.spectrum_fit.fit_spectrum(x: numpy.typing.ArrayLike, y: numpy.typing.ArrayLike, peaks: Sequence[Peak], model: str = 'gaussian', background: str = 'linear', *, sigma: numpy.typing.ArrayLike | None = None, seed: SpectrumFitResult | None = None, x_ref: float | None = None) → SpectrumFitResult

Fit one or more line shapes on a background.

Without a seed, starting values come from the data: the background from a fit to the points away from the lines, each height from the data at its centre, each width from the half-maximum crossings (or Peak.width). Starting values are moved inside the bounds.

Parameters:
  • x (numpy.typing.ArrayLike) – Positions, 1-D.

  • y (numpy.typing.ArrayLike) – Values at x, same shape.

  • peaks (Sequence[Peak]) – The lines to fit.

  • model (str) – Line shape of every peak, one of MODELS.

  • background (str) – One of BACKGROUNDS.

  • sigma (numpy.typing.ArrayLike | None) – One-sigma error of each y, for a weighted fit. The covariance is scaled by the reduced chi-square, as a relative weighting.

  • seed (SpectrumFitResult | None) – A previous result with the same peaks, model and background, whose values start this fit.

  • x_ref (float | None) – Position the background offset refers to; the mean of the peak centres by default.

Returns:

The fit result.

Raises:
  • ValueError – If an argument is unknown or the data are unusable.

  • PeakFitError – If the optimiser does not converge.

  • ImportError – If SciPy (the fit extra) is not installed.

Return type:

SpectrumFitResult

plesty.lib.analyzer.spectrum_fit.fit_peak(x: numpy.typing.ArrayLike, y: numpy.typing.ArrayLike, model: str = 'gaussian', *, center: float | None = None, width: float | None = None, background: str = 'linear', sigma: numpy.typing.ArrayLike | None = None, center_bounds: tuple[float, float] | None = None, width_bounds: tuple[float, float] | None = None, nonnegative: bool = True) → SpectrumFitResult

Fit one line on a background — fit_spectrum() with a single Peak.

Parameters:
  • x (numpy.typing.ArrayLike) – Positions, 1-D.

  • y (numpy.typing.ArrayLike) – Values at x.

  • model (str) – Line shape, one of MODELS.

  • center (float | None) – Initial line position; the highest point by default.

  • width (float | None) – Initial FWHM.

  • background (str) – One of BACKGROUNDS.

  • sigma (numpy.typing.ArrayLike | None) – One-sigma error of each y.

  • center_bounds (tuple[float, float] | None) – (low, high) for the position.

  • width_bounds (tuple[float, float] | None) – (low, high) for the FWHM.

  • nonnegative (bool) – Keep the height at or above zero.

Returns:

The fit result; result.peaks[0] holds the line.

Return type:

SpectrumFitResult

plesty.lib.analyzer.spectrum_fit._bounds(xs: numpy.ndarray, peaks: Sequence[Peak], model: str, background: str) → tuple[numpy.ndarray, numpy.ndarray]

Return the lower and upper bound of every parameter.

Parameters:
  • xs (numpy.ndarray)

  • peaks (Sequence[Peak])

  • model (str)

  • background (str)

Return type:

tuple[numpy.ndarray, numpy.ndarray]

plesty.lib.analyzer.spectrum_fit._inside(values: numpy.ndarray, lower: numpy.ndarray, upper: numpy.ndarray) → numpy.ndarray

Move starting values strictly inside their bounds.

Parameters:
  • values (numpy.ndarray)

  • lower (numpy.ndarray)

  • upper (numpy.ndarray)

Return type:

numpy.ndarray

plesty.lib.analyzer.spectrum_fit._estimate_width(xs: numpy.ndarray, ys: numpy.ndarray, index: int, level: float) → float | None

Return the distance between the half-maximum crossings around index.

Parameters:
  • xs (numpy.ndarray)

  • ys (numpy.ndarray)

  • index (int)

  • level (float)

Return type:

float | None

plesty.lib.analyzer.spectrum_fit._initial_guess(xs: numpy.ndarray, ys: numpy.ndarray, peaks: Sequence[Peak], model: str, background: str, x_ref: float) → numpy.ndarray

Return data-driven starting values for fit_spectrum().

Parameters:
  • xs (numpy.ndarray)

  • ys (numpy.ndarray)

  • peaks (Sequence[Peak])

  • model (str)

  • background (str)

  • x_ref (float)

Return type:

numpy.ndarray

plesty.lib.analyzer.spectrum_fit._propagate(function: Callable[[numpy.ndarray], float], values: numpy.ndarray, covariance: numpy.ndarray, indices: Sequence[int]) → float

Return the one-sigma error of function(values) from the covariance block.

Parameters:
  • function (Callable[[numpy.ndarray], float])

  • values (numpy.ndarray)

  • covariance (numpy.ndarray)

  • indices (Sequence[int])

Return type:

float

class plesty.lib.analyzer.spectrum_fit.SpectrumFit(peaks: str | Sequence[float] = 'auto', model: str = 'gaussian', background: str = 'linear', window: float | None = None, frames: str | int | Sequence[int] = 'all', order: Sequence[int] | None = None, track: bool = True, overlap: float = 2.0, prominence: float | None = None, distance: int = 1, tolerance: float | None = None, width: float | None = None, width_bounds: tuple[float, float] | None = None, center_tolerance: float | None = None, nonnegative: bool = True, average: str = 'mean')

Bases: plesty.lib.analyzer.base_analyzer.Analyzer

Fit every line in every frame of a spectrum.

  1. Lines — peaks="auto" finds them in the average of the selected frames (prominence, distance); a list of positions uses those, relocated to the nearest local maximum within tolerance if set. average="median" takes the per-pixel median of the frames instead of their mean: a cosmic ray hits one frame and drops out of the median, where in the mean of a few dozen frames it can still stand out as a line.

  2. Groups — each line’s FWHM is estimated on the average; neighbours closer than overlap × the larger FWHM are fitted together, every other line alone. overlap=0 fits every line alone.

  3. Range — a group is fitted over window beyond its outer lines on each side, re-centred on each frame’s starting centres so the range follows a drifting line; without a window, up to halfway to the neighbouring groups.

  4. Frames — each group is fitted to the average first; that result seeds the first frame in order, and with track each frame seeds the next. center_tolerance keeps each line within that distance of its seed. A frame whose fit fails is nan.

  5. Quality — nothing above is changed by it: per line, frames that failed, lacked a centre error or sat at a centre or width bound, jumps wider than the line between frames, and outlying centres are counted into fit.metadata["diagnostics"], and a FitQualityWarning names every line with a count. With track, each frame’s range is centred on the previous frame’s fitted position, so when a line is weak in one frame its fit can move onto a brighter neighbouring spectral line inside the range and stay on it; these counts are how that shows.

Outputs fit (SpectrumFitData, one row per frame in frames) and curve — the fitted model on the spectrum’s axis, nan outside the fitted ranges.

Validate and store the fit parameters.

Parameters:
  • peaks (str | Sequence[float]) – "auto", or line positions in the wavelength unit.

  • model (str) – Line shape, one of MODELS.

  • background (str) – One of BACKGROUNDS.

  • window (float | None) – Fitted range beyond a group’s outer lines, each side.

  • frames (str | int | Sequence[int]) – "all", one frame index, or a list of them.

  • order (Sequence[int] | None) – The frames in the order to fit them.

  • track (bool) – Seed each frame with the previous frame’s fit.

  • overlap (float) – Grouping factor on the FWHM; 0 never groups.

  • prominence (float | None) – Minimum prominence for peaks="auto".

  • distance (int) – Minimum separation in pixels for peaks="auto".

  • tolerance (float | None) – Relocate given positions to a local maximum this close.

  • width (float | None) – Initial FWHM of every line.

  • width_bounds (tuple[float, float] | None) – (low, high) FWHM bounds of every line.

  • center_tolerance (float | None) – Maximum distance of a line from its seed position.

  • nonnegative (bool) – Keep heights at or above zero.

  • average (str) – "mean" or "median" of the frames: the spectrum lines are found in, their widths estimated on, and the first fit made to.

Raises:

ValueError – If a parameter is out of range.

input_schema

name → schema entry.

Type:

Declared inputs of analyze()

output_schema

name → schema entry.

Type:

Declared outputs of analyze()

analyze(spectrum: plesty.lib.data.spectrum.SpectrumData) → dict[str, Any]

Fit every line in every selected frame.

Parameters:

spectrum (plesty.lib.data.spectrum.SpectrumData) – The spectrum to fit.

Returns:

fit and curve, see the class description.

Raises:
  • ValueError – If a frame is out of range, order does not match frames, or no line is found.

  • PeakFitError – If a group cannot be fitted even to the average.

Return type:

dict[str, Any]

static _frames(selection: str | int | list[int], total: int) → list[int]

Return the selected frame indices, normalised and checked.

Parameters:
  • selection (str | int | list[int])

  • total (int)

Return type:

list[int]

_order(order: list[int], frames: list[int], total: int) → list[int]

Return the fitting order, which must be a permutation of frames.

Parameters:
  • order (list[int])

  • frames (list[int])

  • total (int)

Return type:

list[int]

_centers(spectrum: plesty.lib.data.spectrum.SpectrumData, average: numpy.ndarray) → numpy.ndarray

Return the sorted line positions to fit.

Parameters:
Return type:

numpy.ndarray

_fwhms(axis: numpy.ndarray, average: numpy.ndarray, centers: numpy.ndarray) → numpy.ndarray

Estimate each line’s FWHM with a single-line fit to the average.

Parameters:
  • axis (numpy.ndarray)

  • average (numpy.ndarray)

  • centers (numpy.ndarray)

Return type:

numpy.ndarray

_groups(centers: numpy.ndarray, fwhms: numpy.ndarray) → list[list[int]]

Group neighbouring lines closer than overlap × the larger FWHM.

Parameters:
  • centers (numpy.ndarray)

  • fwhms (numpy.ndarray)

Return type:

list[list[int]]

_range(axis: numpy.ndarray, centers: numpy.ndarray, fwhms: numpy.ndarray, groups: list[list[int]], g: int) → tuple[float, float]

Return the fitted range of group g.

Parameters:
  • axis (numpy.ndarray)

  • centers (numpy.ndarray)

  • fwhms (numpy.ndarray)

  • groups (list[list[int]])

  • g (int)

Return type:

tuple[float, float]

_center_bounds(center: float, low: float, high: float) → tuple[float, float]

Return the position bounds of a line: its tolerance, within the range.

Parameters:
  • center (float)

  • low (float)

  • high (float)

Return type:

tuple[float, float]

static _check_bounds(diagnostics: list[dict[str, int]], group: list[int], result: SpectrumFitResult, bounded: list[Peak], x: numpy.ndarray) → None

Count, for one frame, lines without a centre error or pinned at a bound.

Parameters:
  • diagnostics (list[dict[str, int]])

  • group (list[int])

  • result (SpectrumFitResult)

  • bounded (list[Peak])

  • x (numpy.ndarray)

Return type:

None

static _check_tracks(diagnostics: list[dict[str, int]], center: numpy.ndarray, frames: list[int], order: list[int], fwhms: numpy.ndarray) → None

Count, per line, jumps wider than the line between frames and outlying centres.

Parameters:
  • diagnostics (list[dict[str, int]])

  • center (numpy.ndarray)

  • frames (list[int])

  • order (list[int])

  • fwhms (numpy.ndarray)

Return type:

None

static _warn(diagnostics: list[dict[str, int]], centers: numpy.ndarray, unit: str | None) → None

Warn once, naming every line with a count, without changing any result.

Parameters:
  • diagnostics (list[dict[str, int]])

  • centers (numpy.ndarray)

  • unit (str | None)

Return type:

None

static _store(table: dict[str, numpy.ndarray], row: int, group: list[int], result: SpectrumFitResult) → None

Write one group’s fit of one frame into the result table.

Parameters:
  • table (dict[str, numpy.ndarray])

  • row (int)

  • group (list[int])

  • result (SpectrumFitResult)

Return type:

None