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.

reset

reset() -> None

Clear internal state (history, previous sample).

Source code in myogestic/outputs/filters.py
def reset(self) -> 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
Source code in myogestic/outputs/filters.py
def __init__(
    self,
    hz: float = 50.0,
    min_cutoff_hz: float = 1.0,
    beta: float = 0.02,
    derivative_cutoff_hz: float = 1.0,
):
    if hz <= 0:
        raise ValueError(f"hz must be > 0 (got {hz})")
    if min_cutoff_hz <= 0:
        raise ValueError(f"min_cutoff_hz must be > 0 (got {min_cutoff_hz})")
    if derivative_cutoff_hz <= 0:
        raise ValueError(f"derivative_cutoff_hz must be > 0 (got {derivative_cutoff_hz})")
    self.hz = hz
    self.min_cutoff_hz = min_cutoff_hz
    self.beta = beta
    self.derivative_cutoff_hz = derivative_cutoff_hz
    self._x_prev: np.ndarray | None = None
    self._dx_prev: np.ndarray | None = None
    self._timestamp_prev: float | None = None

reset

reset() -> None

Clear the previous sample, velocity, and timestamp.

Source code in myogestic/outputs/filters.py
def reset(self) -> None:
    """Clear the previous sample, velocity, and timestamp."""
    self._x_prev = None
    self._dx_prev = None
    self._timestamp_prev = None

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).

Source code in myogestic/outputs/filters.py
def __init__(self, n_vectors: int = 5, sigma: float = 1.0):
    if n_vectors < 1:
        raise ValueError(f"n_vectors must be >= 1 (got {n_vectors})")
    if sigma <= 0:
        raise ValueError(f"sigma must be > 0 (got {sigma})")
    self.n_vectors = n_vectors
    self.sigma = sigma
    # Gaussian kernel centered on the most recent sample (last position).
    idx = np.arange(n_vectors, dtype=np.float64)
    weights = np.exp(-((idx - (n_vectors - 1)) ** 2) / (2.0 * sigma * sigma))
    self._weights = weights / weights.sum()
    self._buf: list[np.ndarray] = []

reset

reset() -> None

Clear the rolling vector history.

Source code in myogestic/outputs/filters.py
def reset(self) -> None:
    """Clear the rolling vector history."""
    self._buf.clear()

IdentityFilter

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

reset

reset() -> None

No-op — the passthrough filter holds no state.

Source code in myogestic/outputs/filters.py
def reset(self) -> 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.

Source code in myogestic/outputs/filters.py
def 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
        ``"identity"`` | ``"gaussian"`` | ``"one_euro"``.
    hz
        Expected sample rate. Forwarded as ``hz`` to ``one_euro``;
        ignored by the others.
    **kwargs
        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
    ------
    ValueError
        if the name isn't recognized.
    TypeError
        if a kwarg is unknown for the chosen filter.
    """
    n = name.lower()
    if n == "identity":
        if kwargs:
            raise TypeError(f"identity takes no kwargs (got {list(kwargs)})")
        return IdentityFilter()
    if n == "gaussian":
        # GaussianFilter's own defaults are n_vectors=5, sigma=1.0 — no need to
        # restate them; kwargs overrides as needed.
        return GaussianFilter(**kwargs)
    if n == "one_euro":
        return OneEuroFilter(
            **{
                "hz": hz,
                "min_cutoff_hz": 1.0,
                "beta": 0.02,
                "derivative_cutoff_hz": 1.0,
                **kwargs,
            }
        )
    raise ValueError(f"Unknown filter {name!r}. Choose: 'identity', 'gaussian', 'one_euro'.")