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]
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 |
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
GaussianFilter
¶
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
IdentityFilter
¶
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
|
|
required |
hz
|
float
|
Expected sample rate. Forwarded as |
50.0
|
**kwargs
|
Any
|
Forwarded to the filter constructor — e.g.
|
{}
|
Raises:
| Type | Description |
|---|---|
ValueError
|
if the name isn't recognized. |
TypeError
|
if a kwarg is unknown for the chosen filter. |
Examples:
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: