Pipeline
The pipeline module exposes the composable ops behind get_raw_peaks and
get_centroided_spectrum. Each op takes (and most return) a
RawSpectrum — raw peaks in their native
(scan_number, TOF_index, intensity) integer form.
Use the convenience entry points for common workflows; reach into the ops when you need a custom ordering, want to plug in a transformation, or want to skip a step.
from tdfpy import (
read_spectrum, subset_scans, exclude_region,
apply_noise, convert, centroid_peaks,
ChargeStateRegion, MadThreshold, WatershedCentroider,
)
with tdfpy.timsdata_connect("data.d") as td:
s = read_spectrum(td, frame_id=1)
s = subset_scans(s, scan_num_begin=0, scan_num_end=400)
s = exclude_region(s, ChargeStateRegion(), td=td, frame_id=1)
s = apply_noise(s, (MadThreshold(k=3),), td=td, frame_id=1)
centroids = WatershedCentroider(
attach_scan_half_width=10, attach_mz_idx_half_width=3
)(s, td, 1)
WatershedCentroider accepts an optional per-group "leash" via
max_scan_from_seed and max_mz_idx_from_seed — bounds on how far any
group member can be from its seed. Useful for stopping chain-grown
groups from wandering across the data. max_mz_idx_from_seed defaults
to 10; max_scan_from_seed defaults to None (no bound on that axis).
# Cap group span at ±20 TOF indices from the seed
WatershedCentroider(
attach_scan_half_width=10, attach_mz_idx_half_width=3,
max_mz_idx_from_seed=20,
)
The standalone smooth op (and the lower-level
box_smooth array helper) rewrite intensities in
place — a box sum or mean over a (±scan_half_width,
±mz_idx_half_width) window — without expanding the point set. Summing
(the default) amplifies genuine ion-mobility streaks ahead of noise
filtering; the mean variant backs WatershedCentroider's seed-stabilising
smoother, which runs before seed selection by default via the
smooth_scan_half_width / smooth_mz_idx_half_width fields (defaults 5
and 3; set either to 0 to disable).
from tdfpy import read_spectrum, smooth, apply_noise, VerticalNoiseFilter
s = read_spectrum(td, frame_id=1)
s = smooth(s, scan_half_width=5, mz_idx_half_width=2) # box sum, amplify streaks
s = apply_noise(s, (VerticalNoiseFilter(),), td=td, frame_id=1)
Data carrier
tdfpy.RawSpectrum
dataclass
RawSpectrum(
scan_indices: np.ndarray,
mz_indices: np.ndarray,
intensities: np.ndarray,
num_scans: int,
)
Raw peaks in integer-index (TOF / scan) space.
The native form of Bruker raw data — TOF index and scan number are
integers, intensity is a 32-bit-ish count. All pipeline ops operate
on this representation; conversion to m/z and 1/K0 happens once at
the end via :func:convert.
eq=False keeps the inherited identity == and hash. The
generated dataclass versions compare and hash the ndarray fields, which
raises (ValueError on ==, TypeError on hash) — so a
spectrum could not be put in a set, used as a dict key, or compared even
incidentally. Element-wise comparison is not what callers of == on a
multi-megabyte spectrum want either; compare the arrays explicitly.
filter
filter(mask: np.ndarray) -> 'RawSpectrum'
Return a new spectrum keeping only points where mask is True.
Source code in src/tdfpy/pipeline.py
87 88 89 90 91 92 93 94 95 96 | |
Reading
tdfpy.read_spectrum
read_spectrum(td: TimsData, frame_id: int) -> RawSpectrum
Read a frame's raw peaks into integer-index form.
Source code in src/tdfpy/pipeline.py
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
Scoping
tdfpy.subset_scans
subset_scans(
spectrum: RawSpectrum,
*,
scan_num_begin: int,
scan_num_end: int
) -> RawSpectrum
Restrict the spectrum to peaks in scans [scan_num_begin, scan_num_end).
The bounds are half-open: begin inclusive, end exclusive — matching
Bruker's readScans(frame_id, begin, end) semantics. The returned
RawSpectrum keeps its num_scans field (i.e. the parent frame's
full scan count) so downstream ops still address scans by their original
index.
Used by :class:~tdfpy.DiaWindow and :class:~tdfpy.PrmTransition to
restrict centroiding / raw-peak extraction to the isolation window's
scan range.
Source code in src/tdfpy/pipeline.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | |
tdfpy.exclude_region
exclude_region(
spectrum: RawSpectrum,
region: ChargeStateRegion,
*,
td: TimsData,
frame_id: int
) -> RawSpectrum
Drop peaks lying inside the given region.
Source code in src/tdfpy/pipeline.py
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
Smoothing
The convenience entry points (get_raw_peaks, get_centroided_spectrum,
Frame.centroid(), …) accept smoothing as a single smooth=Smooth(...)
argument; smooth / box_smooth are the underlying composable ops.
tdfpy.Smooth
dataclass
Smooth(
scan_half_width: int = 5,
mz_idx_half_width: int = 2,
mode: Literal["sum", "mean"] = "sum",
)
Config for the pre-noise-filter intensity smoothing step.
A small, hashable carrier for the :func:smooth op's knobs so the
convenience entry points (get_raw_peaks, get_centroided_spectrum,
Frame.centroid(), …) can accept smoothing as a single smooth=Smooth(...)
argument. Frozen so it is hashable (Streamlit-cacheable).
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
apply
apply(spectrum: RawSpectrum) -> RawSpectrum
Return spectrum with intensities box-smoothed per this config.
Source code in src/tdfpy/pipeline.py
515 516 517 518 519 520 521 522 | |
tdfpy.smooth
smooth(
spectrum: RawSpectrum,
*,
scan_half_width: int = 5,
mz_idx_half_width: int = 2,
mode: Literal["sum", "mean"] = "sum"
) -> RawSpectrum
Return a new spectrum with box-smoothed intensities (positions kept).
A pre-noise-filter signal-amplification step: summing intensity over a
small (±scan_half_width, ±mz_idx_half_width) window boosts genuine
features that recur across consecutive mobility scans while leaving
scattered single-hit noise largely unchanged. Composes ahead of
:func:apply_noise in a custom pipeline. See :func:box_smooth.
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
Source code in src/tdfpy/pipeline.py
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 | |
tdfpy.box_smooth
box_smooth(
scan_indices: np.ndarray,
mz_indices: np.ndarray,
intensities: np.ndarray,
*,
scan_half_width: int,
mz_idx_half_width: int,
mode: Literal["sum", "mean"] = "sum"
) -> np.ndarray
Box sum / mean of intensities over a (±scan, ±mz_idx) index window.
For every peak, gathers all peaks within ±scan_half_width mobility
scans and ±mz_idx_half_width TOF indices and replaces the peak's
intensity with the sum (mode="sum") or mean (mode="mean") of that
window. Positions are preserved — only intensities change. Summing
amplifies genuine features (which recur across many scans) while leaving
isolated background hits untouched; the mean variant is used internally by
:class:WatershedCentroider to stabilise seed ordering.
Either half-width may be 0 to smooth along the other axis only; 0
for both is a no-op that returns the input intensities.
Vectorised per mobility-scan: for each scan offset the contributing source
scan's peaks are searched by a sorted-m/z prefix sum, so cost is
O((2·scan_half_width+1) · N · log N) rather than the naïve O(N²).
Raises:
| Type | Description |
|---|---|
ValueError
|
if |
Source code in src/tdfpy/pipeline.py
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 | |
Noise filtering
tdfpy.apply_noise
apply_noise(
spectrum: RawSpectrum,
filters: Iterable[NoiseFilter],
*,
td: TimsData,
frame_id: int
) -> RawSpectrum
Apply each noise filter in order, threading the surviving peaks through.
Source code in src/tdfpy/pipeline.py
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | |
Conversion
tdfpy.convert
convert(
spectrum: RawSpectrum,
td: TimsData,
frame_id: int,
*,
ion_mobility_type: Literal[
"ook0", "ccs", "voltage"
] = "ook0"
) -> np.ndarray
Convert integer indices to (m/z, intensity, ion_mobility).
Returns a (N, 3) array. Empty input yields an empty array of the
same shape so callers don't need to special-case. CCS assumes charge +1.
Raw peaks do not identify a charge state. Use a known precursor charge
with the explicit CCS conversion function for charge-specific values.
Source code in src/tdfpy/pipeline.py
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | |
Centroiders
The two centroiders share an Centroider ABC.
MergePeaksCentroider (default) operates on float m/z values via a greedy
tolerance-based merge; WatershedCentroider works in integer index space
via intensity-ordered region growing.
tdfpy.Centroider
Bases: ABC
Base class for centroiding algorithms.
Subclasses are frozen dataclasses carrying their tunable knobs as fields
and implement :meth:__call__, which takes the (filtered) raw spectrum
and returns an (N, 3) array of [mz, intensity, ion_mobility]
centroids. Centroiders decide internally whether to operate in integer
index space or after conversion to float m/z.
tdfpy.MergePeaksCentroider
dataclass
MergePeaksCentroider(
mz_tolerance: float = 8.0,
mz_tolerance_type: Literal["ppm", "da"] = "ppm",
im_tolerance: float = 0.1,
im_tolerance_type: Literal[
"relative", "absolute"
] = "relative",
min_peaks: int = 3,
max_peaks: int | None = None,
peak_noise_filter: bool = False,
peak_noise_window: float = 0.1,
peak_noise_end_fraction: float = 0.1,
use_numba: bool = True,
)
Bases: Centroider
Greedy m/z-tolerance centroider — wraps :func:tdfpy.merge_peaks.
Operates on float m/z values. Real peaks are matched within an m/z
tolerance (ppm or Da) and an ion mobility tolerance. Default algorithm
used by :func:tdfpy.get_centroided_spectrum.
tdfpy.WatershedCentroider
dataclass
WatershedCentroider(
attach_scan_half_width: int = 10,
attach_mz_idx_half_width: int = 3,
min_seed_intensity: float = 0.0,
min_centroid_intensity: float = 0.0,
smooth_scan_half_width: int = 5,
smooth_mz_idx_half_width: int = 3,
max_scan_from_seed: int | None = None,
max_mz_idx_from_seed: int | None = 10,
use_numba: bool = True,
)
Bases: Centroider
Intensity-ordered region-growing centroider in integer-index space.
Operates on (scan_number, TOF_index) integers — avoiding the
floating-point binning step that :class:MergePeaksCentroider does.
Each point either joins the nearest already-assigned point's group
(within a rectangular tolerance box) or promotes to a new seed.
See apps/ALGORITHM.md Stage 3 for the full write-up.
The optional smooth_*_half_width parameters apply a position-preserving
box-mean filter to intensities before seed selection, which prevents
noisy spikes from outranking the actual peak summit and stabilises
seed ordering. Smoothing affects ordering only — the centroid
intensities reported (and screened by min_centroid_intensity) are sums
of the raw input intensities, so a centroided frame conserves the raw
total ion current. min_seed_intensity is a statement about seed
selection, so it is compared against the smoothed value instead.
Smoothing is skipped only when both half-widths are 0; a single
nonzero half-width smooths along that axis alone (e.g.
smooth_scan_half_width=0 with smooth_mz_idx_half_width=3 averages
across TOF indices within each mobility scan).
The optional max_*_from_seed parameters are per-group "leashes":
a follower is rejected if its distance from the candidate group's
seed (not its nearest member) exceeds the bound on either axis.
This stops a group from wandering by chaining through followers.
None disables the bound on that axis.
tdfpy.centroid_peaks
centroid_peaks(
peaks: np.ndarray, centroider: MergePeaksCentroider
) -> np.ndarray
Cluster (mz, intensity, ion_mobility) peaks into centroids.
Convenience wrapper for users who already have a converted (N, 3)
array and want to skip back through a :class:RawSpectrum. Only
supports :class:MergePeaksCentroider since :class:WatershedCentroider
needs integer indices that aren't recoverable from float peaks.
Source code in src/tdfpy/pipeline.py
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 | |