Skip to content

Filters

Output-side smoothing filters for prediction-output control vectors. Custom filters implement the VectorFilter protocol below. See Post-process predictions for tuning guidance.

The protocol

VectorFilter

Bases: Protocol

Stateful per-vector filter. Call once per output tick.

Examples:

>>> import numpy as np
>>> from myogestic.outputs.filters import IdentityFilter, VectorFilter
>>> filter_: VectorFilter = IdentityFilter()
>>> filter_(np.array([0.0, 1.0], dtype=np.float32)).tolist()
[0.0, 1.0]

reset

reset() -> None

Clear internal state (history, previous sample).

Built-in filters

OneEuroFilter

OneEuroFilter(hz: float = 50.0, min_cutoff_hz: float = 1.0, beta: float = 0.02, derivative_cutoff_hz: float = 1.0)

1€ Filter — adaptive low-pass for noisy interactive signals.

Trades latency for smoothness based on instantaneous velocity: fast motion → high cutoff (responsive), slow motion → low cutoff (smooth). Standard for hand tracking, controllers, gesture cursors.

Reference: https://gery.casiez.net/1euro/

Parameters:

Name Type Description Default
hz float

Expected sample rate (Hz). Used as a fallback dt when no timestamp is passed to __call__. Filter accuracy depends on this matching the actual call rate; pass timestamp from your predict loop if the rate is jittery.

50.0
min_cutoff_hz float

Cutoff (Hz) at zero velocity — controls baseline smoothing.

1.0
beta float

Velocity-to-cutoff gain. Larger → more responsive on fast moves.

0.02
derivative_cutoff_hz float

Cutoff (Hz) for the velocity smoother.

1.0

Examples:

>>> import numpy as np
>>> from myogestic.outputs.filters import OneEuroFilter
>>> filter_ = OneEuroFilter(hz=1.0, min_cutoff_hz=1.0, beta=0.0)
>>> _ = filter_(np.array([0.0], dtype=np.float32))
>>> round(float(filter_(np.array([1.0], dtype=np.float32))[0]), 3)
0.863

reset

reset() -> None

Clear the previous sample, velocity, and timestamp.

GaussianFilter

GaussianFilter(n_vectors: int = 5, sigma: float = 1.0)

Rolling temporal smoothing for 1-D vectors.

Keeps the last n_vectors vectors and returns their Gaussian-weighted mean (weights peak at the most recent sample). During warmup (buffer not yet full), weights are renormalized over the available history — no zero-padding bias.

Inputs must be 1-D arrays of consistent length (raises on first dimension mismatch).

Examples:

>>> import numpy as np
>>> from myogestic.outputs.filters import GaussianFilter
>>> filter_ = GaussianFilter(n_vectors=2, sigma=1.0)
>>> _ = filter_(np.array([0.0], dtype=np.float32))
>>> round(float(filter_(np.array([1.0], dtype=np.float32))[0]), 3)
0.622

reset

reset() -> None

Clear the rolling vector history.

IdentityFilter

Passthrough — useful as a baseline or "off" toggle.

Examples:

>>> import numpy as np
>>> from myogestic.outputs.filters import IdentityFilter
>>> filter_ = IdentityFilter()
>>> filter_(np.array([0.0, 1.0], dtype=np.float32)).tolist()
[0.0, 1.0]

reset

reset() -> None

No-op — the passthrough filter holds no state.

Factory

make_filter

make_filter(name: str, hz: float = 50.0, **kwargs: Any) -> VectorFilter

Construct a filter by name.

Swap filters in an experiment by changing one string; pass extra kwargs to tune without instantiating the class directly.

Parameters:

Name Type Description Default
name str

"identity" | "gaussian" | "one_euro".

required
hz float

Expected sample rate. Forwarded as hz to one_euro; ignored by the others.

50.0
**kwargs Any

Forwarded to the filter constructor — e.g. make_filter("gaussian", n_vectors=10, sigma=2.0), make_filter("one_euro", hz=32, beta=0.05).

{}

Raises:

Type Description
ValueError

if the name isn't recognized.

TypeError

if a kwarg is unknown for the chosen filter.

Examples:

>>> import numpy as np
>>> from myogestic.outputs.filters import make_filter
>>> filter_ = make_filter("identity")
>>> filter_(np.array([0.0, 1.0], dtype=np.float32)).tolist()
[0.0, 1.0]

Composition

chain

chain(*filters: VectorFilter) -> VectorFilter

Compose filters into one VectorFilter, applied left-to-right.

Lets you present a pipeline of filters as a single filter — e.g. drop chain(GaussianFilter(...), OneEuroFilter(...)) into a FilterProcessor palette as one entry. reset() resets every filter; chain() with no args is the identity (returns its input unchanged).

Contract: this is function composition — each filter's output must be a valid input to the next. Shape-changing filters (e.g. a channel differential, n -> n-1) are allowed only if the downstream filter accepts the new shape and the output dimension stays constant across frames (the stateful smoothers stack a history and reject a shape that varies over time). For output post-processing keep every filter shape-preserving — the sink (e.g. VHI) expects a fixed-size control vector.

Examples:

>>> import numpy as np
>>> from myogestic.outputs.filters import IdentityFilter, chain
>>> filter_ = chain(IdentityFilter(), IdentityFilter())
>>> filter_(np.array([0.0, 1.0], dtype=np.float32)).tolist()
[0.0, 1.0]