Coverage for src/evutils/transforms/functional/_drop.py: 66%
50 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
1import math
3import numpy as np
4from evutils.jit import lazy_njit
6from ._common import apply_kernel, sample_range
8@lazy_njit
9def _drop_random_events_jit(t: np.ndarray, x: np.ndarray, y: np.ndarray, p: np.ndarray, drop_rate: float, seed: int):
10 """
11 Drops a percentage of events randomly using slicing, compiled via Numba.
13 Parameters
14 ----------
15 t, x, y, p : np.ndarray
16 Constituent event arrays.
17 drop_rate : float
18 Percentage of events to drop (0 to 1).
19 seed : int
20 Seed for the random number generator. If -1, no seed is set.
22 Returns
23 -------
24 tuple
25 (new_t, new_x, new_y, new_p)
26 """
27 if seed != -1:
28 np.random.seed(seed)
29 # Using random.rand to generate a boolean mask is fully supported and highly optimized in Numba
30 mask = np.random.rand(len(t)) >= drop_rate
31 return t[mask], x[mask], y[mask], p[mask]
33def drop_random_events(events, drop_rate: float = 0.1, seed: int | None = None):
34 """Drops a percentage of events randomly.
36 Parameters
37 ----------
38 events : np.ndarray or EventArray
39 Array of events to drop from.
40 drop_rate : float, optional
41 Percentage of events to drop, by default 0.1 (10%).
43 Returns
44 -------
45 np.ndarray or EventArray
46 Array of events with the specified percentage dropped.
47 """
48 import math
49 if math.isnan(drop_rate) or drop_rate <= 0 or drop_rate >= 1:
50 raise ValueError("drop_rate must be between 0 and 1")
52 from evutils.transforms.compose import unwrap_events, repack_events
53 if len(events) == 0:
54 return events
56 t, x, y, p = unwrap_events(events)
57 t, x, y, p = _drop_random_events_jit(t, x, y, p, drop_rate, seed if seed is not None else -1)
58 return repack_events(events, t, x, y, p)
60def drop_event(events, p=0.1, seed: int | None = None):
61 """Randomly drop each event independently with probability ``p``.
63 The tonic-compatible name for :func:`drop_random_events`. Unlike tonic's
64 ``drop_event_numpy`` (which drops exactly ``round(p * n)`` events via
65 shuffle), this drops *independently*, which is O(N) rather than O(N log N).
66 (sampling without replacement) this uses an independent Bernoulli mask per
67 event, which is what keeps the kernel JIT-friendly. The expected number of
68 dropped events is the same; the exact count is binomially distributed.
70 Parameters
71 ----------
72 events : np.ndarray or EventArray
73 Events to drop from.
74 p : float or tuple of float, optional
75 Drop probability in ``[0, 1)``. A ``(lo, hi)`` tuple is sampled uniformly
76 per call. Defaults to ``0.1``.
77 seed : int, optional
78 Seed for the kernel's random number generator (numba's per-thread
79 legacy ``np.random`` state). ``None`` leaves the state unseeded.
81 Returns
82 -------
83 np.ndarray or EventArray
84 Events that survived the drop, in their original container type.
85 """
86 p = sample_range(p)
87 if math.isnan(p) or p < 0 or p >= 1:
88 raise ValueError("p must be in [0, 1)")
89 if p == 0:
90 return events
91 return apply_kernel(events, _drop_random_events_jit, p,
92 seed if seed is not None else -1)
94@lazy_njit
95def _drop_by_time_jit(t, x, y, p, duration_ratio: float, seed: int):
96 """Drop a single contiguous time window covering ``duration_ratio`` of the span."""
97 if seed != -1:
98 np.random.seed(seed)
99 if len(t) == 0:
100 return t, x, y, p
101 t_start = t.min()
102 t_end = t.max()
103 span = t_end - t_start
104 drop_duration = span * duration_ratio
105 hi = t_end - drop_duration
106 # np.random.uniform requires low < high; clamp the degenerate case.
107 drop_start = np.random.uniform(t_start, hi) if hi > t_start else t_start
108 keep = (t < drop_start) | (t > drop_start + drop_duration)
109 return t[keep], x[keep], y[keep], p[keep]
111def drop_by_time(events, duration_ratio=0.2, seed: int | None = None):
112 """Drop every event inside one randomly-placed time window.
114 The window length is ``duration_ratio`` of the recording span (``[t.min(), t.max()]``,
115 following tonic), positioned uniformly at random within it.
117 Parameters
118 ----------
119 events : np.ndarray or EventArray
120 Events to drop from.
121 duration_ratio : float or tuple of float, optional
122 Window length as a fraction of the span, in ``[0, 1)``. A ``(lo, hi)``
123 tuple is sampled uniformly per call. Defaults to ``0.2``.
124 seed : int, optional
125 Seed for the random number generator. Defaults to None.
127 Returns
128 -------
129 np.ndarray or EventArray
130 Events outside the dropped window, in their original container type.
131 """
132 if seed is not None:
133 np.random.seed(seed)
134 ratio = sample_range(duration_ratio)
135 if math.isnan(ratio) or ratio < 0 or ratio >= 1:
136 raise ValueError("duration_ratio must be in [0, 1)")
137 if ratio == 0:
138 return events
139 return apply_kernel(events, _drop_by_time_jit, ratio, seed if seed is not None else -1)