ezmsg.simbiophys.baseline_drift#

Always-on slow 1/f (pink) baseline drift.

Adds a low-frequency, velocity-independent baseline wander to a signal so that even at rest there is non-zero power at very low frequencies. This complements ezmsg.simbiophys.dynamic_colored_noise, whose Kasdin filter runs at the full output rate and therefore only holds a true 1/f slope down to a corner of a few hundred Hz (at 30 kHz, ~700 Hz with 5 poles). Reaching sub-Hz drift that way would need thousands of poles.

Instead we generate pink noise with a fixed spectral exponent (β = 1) on a low internal grid (drift_fs, e.g. 50 Hz), where a handful of poles put the 1/f corner well below 1 Hz, then linearly interpolate that slow signal up to the output rate and add it. Because the drift is dominated by frequencies far below the drift-grid Nyquist, linear interpolation is effectively exact.

The drift filter state, interpolation anchors, and phase are all carried across chunks so the output is continuous and independent of how the stream is chunked.

Performance: the grid is generated on only drift_fs samples per second (a handful per chunk), so the sequential IIR recursion is tiny. Upsampling to the output rate, scaling, and adding to the input are fused into a single compiled pass that touches the (n_samples, n_channels) result exactly once (see _pink_drift_add()), which keeps the stage memory-bandwidth bound and scales well to large channel counts on low-power CPUs.

Classes

class BaselineDriftSettings(scale=1.0, drift_fs=50.0, n_poles=24, beta=1.0, seed=None)[source]#

Bases: Settings

Parameters:
scale: float = 1.0#

Amplitude of the added drift (multiplies the raw filter output).

drift_fs: float = 50.0#

Internal sample rate (Hz) at which the slow pink noise is generated. Lower rates push the 1/f corner lower for a given pole count; it only needs to sit comfortably above the drift frequencies of interest.

n_poles: int = 24#

Number of IIR poles for the drift filter. With drift_fs = 50 Hz this places the 1/f corner near ~0.3 Hz, giving sub-Hz to few-Hz wander.

beta: float = 1.0#

Spectral exponent of the drift. 1.0 is pink (1/f); kept < 2 for stability.

seed: int | None = None#

Random seed for reproducibility. If None, uses system entropy.

__init__(scale=1.0, drift_fs=50.0, n_poles=24, beta=1.0, seed=None)#
Parameters:
Return type:

None

class BaselineDriftState[source]#

Bases: object

delay_lines: ndarray[tuple[Any, ...], dtype[float64]] | None = None#

Kasdin filter delay lines, shape (n_channels, n_poles).

coeffs: ndarray[tuple[Any, ...], dtype[float64]] | None = None#

Fixed filter coefficients for beta, shape (n_poles,).

prev: ndarray[tuple[Any, ...], dtype[float64]] | None = None#

Left interpolation anchor (current low-rate sample), shape (n_channels,).

nxt: ndarray[tuple[Any, ...], dtype[float64]] | None = None#

Right interpolation anchor (next low-rate sample), shape (n_channels,).

phase: float = 0.0#

Fractional position between prev and nxt for the next output sample.

rng: Generator | None = None#

Random number generator.

step: float = 0.0#

Low-rate grid samples advanced per output sample (drift_fs / output_fs).

class BaselineDriftTransformer(*args, **kwargs)[source]#

Bases: BaseStatefulTransformer[BaselineDriftSettings, AxisArray, AxisArray, BaselineDriftState]

Add always-on slow 1/f baseline drift to a signal.

The drift is generated independently of the input values (it depends only on the input’s shape and sample rate for timing), so it is present even when the upstream signal is at rest. One independent drift process is generated per channel of the input.

class BaselineDriftUnit(*args, settings=None, **kwargs)[source]#

Bases: BaseTransformerUnit[BaselineDriftSettings, AxisArray, AxisArray, BaselineDriftTransformer]

Unit wrapper for BaselineDriftTransformer.

Parameters:

settings (Settings | None)

SETTINGS#

alias of BaselineDriftSettings