Coverage for src/evutils/transforms/functional/_refractory.py: 90%
21 statements
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-18 05:24 +0000
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-18 05:24 +0000
1"""Refractory-period functional transform."""
2import numpy as np
3from evutils.jit import lazy_njit
5from ._common import apply_kernel
7@lazy_njit
8def _refractory_period_jit(t, x, y, p, delta: int):
9 """Discard events that fire within ``delta`` of the previous event at the same pixel.
11 An event survives when ``t - t_last > delta`` for its pixel. The per-pixel
12 clock is updated on *every* event (including dropped ones), matching tonic's
13 ``refractory_period_numpy``. This loop is exactly the kind of scalar-heavy
14 code Numba turns into tight machine code.
15 """
16 n = len(t)
17 if n == 0:
18 return t, x, y, p
19 width = int(x.max()) + 1
20 height = int(y.max()) + 1
21 # Init to t[0] - delta - 1 so the first event at each pixel is always kept, even if t[0] is 0.
22 last = np.full((width, height), t[0] - delta - 1, dtype=np.int64)
23 keep = np.zeros(n, dtype=np.bool_)
25 for i in range(n):
26 xi = x[i]
27 yi = y[i]
28 if t[i] - last[xi, yi] > delta:
29 keep[i] = True
30 last[xi, yi] = t[i]
32 return t[keep], x[keep], y[keep], p[keep]
34def refractory_period(events, delta):
35 """Enforce a per-pixel refractory period.
37 Parameters
38 ----------
39 events : np.ndarray or EventArray
40 Events to filter. Must be sorted by timestamp.
41 delta : int
42 Refractory period in the same time unit as the timestamps. Events at a
43 pixel that fire within ``delta`` of that pixel's previous event are
44 dropped.
46 Returns
47 -------
48 np.ndarray or EventArray
49 Filtered events, in their original container type.
50 """
51 return apply_kernel(events, _refractory_period_jit, int(delta))