Skip to content

Resample a spectrum onto a new grid

Your spectrum lives on one axis grid and you need it on another: to match a second instrument’s wavenumber axis, or to align replicates before you compare them.

sample.transform covers your own resampling function; the reviewed SciPy-backed resampling operations in this guide are committed through sample.core.transform with explicit field names.

This guide assumes: you have finished the tutorial path, including Transform and plot a spectrum, and you are comfortable committing checkpoints and reading lineage.

Resampling is interpolation: you keep the measured points and ask for estimated values on a new set of axis positions. The base install of MTRL supplies resample_linear. The interp extra adds two SciPy-backed operations beside it: resample_pchip, which is shape-preserving and avoids overshoot near sharp peaks, and resample_cubic, a smooth not-a-knot spline. SciPy loads only when one of them runs, so the extra costs nothing until you use it.

Install the extra into your environment:

Terminal window
uv pip install "vector-mtrl[interp]"

Required install: vector-mtrl[interp]

import numpy as np
from mtrl import InstrumentRun, MaterialData, Mtrl, Unit
axis = np.linspace(2000.0, 3000.0, 101)
content = np.empty(
axis.size,
dtype=[("wavenumber", "<f8"), ("absorbance", "<f8")],
)
content["wavenumber"] = axis
content["absorbance"] = np.exp(-((axis - 2500.0) / 35.0) ** 2)
application = Mtrl(sample_id="polymer-film-01")
run = InstrumentRun(run_name="ftir-replicate-01")
checkpoint = MaterialData(
content=content,
type="raw",
checkpoint_name="spectrum",
units=Unit.from_raw_map({"wavenumber": "cm^-1", "absorbance": "a.u."}),
)
run.add_checkpoint(checkpoint)
application.add(run)
targets = np.linspace(2200.0, 2800.0, 21).tolist()
resampled = application.transform(
run.run_id,
checkpoint.checkpoint_id,
"resample_pchip",
parameters={"x_field": "wavenumber", "y_field": "absorbance", "x_targets": targets},
)
print(f"operation: {resampled.lineage.provenance.qualified_identifier}")
print(f"points: {resampled.content.size}; max absorbance: {resampled.content['absorbance'].max()}")
operation: mtrl.resample.pchip
points: 21; max absorbance: 1.0

The first line prints operation: mtrl.resample.pchip: the qualified name recorded in the new checkpoint’s lineage. The second prints points: 21; max absorbance: 1.0: the resampled spectrum has your 21 target points, and the peak at 2500 cm^-1 survives at full height because the target grid lands on it exactly.

The call application.transform(..., "resample_pchip", parameters={"x_field": "wavenumber", "y_field": "absorbance", "x_targets": targets}) looked up the operation in the application’s registry, loaded SciPy for that one call, and committed the interpolated spectrum as a new checkpoint in the same run. The lineage records the qualified name mtrl.resample.pchip and the effective parameters, including the target axis, so the recipe is complete. Your raw spectrum is unchanged.

Two facts about the operation matter. First, every target must lie inside the source axis range: the resample operations never extrapolate, and a target outside the range fails rather than inventing data. Second, interpolated values are not measurements. The new checkpoint is an estimate derived from your raw spectrum, and its lineage says so.

Choose the kernel to match your purpose. resample_pchip preserves the shape of the data and does not overshoot, which suits peaks and edges. resample_cubic gives a smoother curve, at the price of possible overshoot near sharp features. resample_linear needs no optional library and is the fallback when the interp extra is not installed.

  • Align mixed-resolution members — the common reason to resample: getting replicates onto one axis before aggregating them.
  • Handle missing extras — what happens when code needs interp in an environment that lacks it.
  • Reference — the operation registry table with every short name and its qualified name.