ezmsg.sigproc.util.sosfilt_direct#

Call scipy’s SOS kernel directly, skipping the per-call wrapper cost.

scipy.signal.sosfilt spends a fixed ~12-18 us per call on validation, dtype resolution and shape bookkeeping before reaching its Cython kernel. That cost is independent of chunk size, so it is invisible offline and dominant online:

n_ch x N sosfilt direct saved

16 x 30 15.3 us 6.1 us 59.9% 32 x 30 18.4 us 9.0 us 51.0% 64 x 30 23.8 us 14.6 us 38.8%

256 x 30 59.2 us 49.3 us 16.6% 256 x 128 207.0 us 198.4 us 4.1%

This module reproduces sosfilt’s semantics exactly – same dtype promotion, same layout, same Cython kernel – with the invariant work hoisted into DirectSosfilt, built once per stream. Output is bit-identical to scipy.signal.sosfilt, which is the point: it must be a pure latency optimization, never a numerical one.

It depends on scipy.signal._sosfilt._sosfilt, a private entry point. Two guards cover that: the import is optional, and available() verifies the private kernel against the public function once per process before anything trusts it. If either fails, callers fall back to scipy.signal.sosfilt and the only consequence is the lost microseconds.

Note the state (zi) is deliberately kept in scipy’s public layout and converted per call. Keeping it in the kernel’s layout would save a further ~10-15% but would change the meaning of FilterState.zi, which other code reads; the conversion is cheap enough that the compatibility is worth more.

Module Attributes

SUPPORTED_DTYPES

Promoted dtypes the Cython kernel handles and for which we have verified equivalence.

Functions

available()[source]#

Whether the direct path may be used, verifying the private kernel once.

The check filters a small random signal through both the private kernel and scipy.signal.sosfilt and requires bit-identical output and state. It runs at most once per process and costs well under a millisecond.

Return type:

bool

can_apply(sos, data, zi)[source]#

Whether data/zi are shaped and typed for the direct path.

Parameters:
  • sos (NDArray)

  • data (Any)

  • zi (Any)

Return type:

bool

Classes

class DirectSosfilt(sos, dtype)[source]#

Bases: object

A SOS filter with scipy’s per-call setup hoisted to construction.

Construction reproduces the validation scipy.signal.sosfilt performs via _validate_sos, and raises the same errors. That is not defensive tidiness: the Cython kernel assumes a0 == 1 and silently filters with whatever it is given, so coefficients public scipy rejects would otherwise be accepted here and produce a different answer. Callers are expected to treat a ValueError as “fall back to public scipy” and let it raise the authoritative error.

Parameters:
  • sos (NDArray) – (n_sections, 6) coefficients.

  • dtype (DTypeLike) – The promoted dtype the stream will run in. Must match what np.result_type(sos, data, zi) yields, or apply() would diverge from scipy; can_apply() is the guard for that.

__init__(sos, dtype)[source]#
Parameters:
  • sos (NDArray)

  • dtype (DTypeLike)

apply(data, axis_idx, zi)[source]#

Filter data along axis_idx, returning (y, zf).

Mirrors scipy.signal.sosfilt step for step so the result is bit-identical: move the sample axis last, flatten to 2-D, copy into a C-contiguous buffer (the kernel works in place), run, restore shapes.

Parameters:
  • data (NDArray)

  • axis_idx (int)

  • zi (NDArray)

Return type:

tuple[NDArray, NDArray]