Coverage for src/evutils/transforms/functional/_time.py: 93%
28 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"""Temporal functional transforms (skew, jitter, normalize)."""
2import numpy as np
3from evutils.jit import lazy_njit
5from ._common import apply_kernel
7@lazy_njit
8def _time_skew_jit(t, x, y, p, coefficient: float, offset: float):
9 """Apply the affine timestamp map ``t' = t * coefficient + offset``."""
10 new_t = (t.astype(np.float64) * coefficient + offset).astype(np.int64)
11 return new_t, x, y, p
13def time_skew(events, coefficient, offset=0.0):
14 """Rescale (and shift) all timestamps by a linear map.
16 Parameters
17 ----------
18 events : np.ndarray or EventArray
19 Events to skew.
20 coefficient : float
21 Multiplier applied to every timestamp (e.g. ``2.0`` doubles all gaps).
22 offset : float, optional
23 Added after multiplication. Default ``0.0``.
25 Returns
26 -------
27 np.ndarray or EventArray
28 Events with rewritten timestamps, in their original container type.
29 """
30 return apply_kernel(events, _time_skew_jit, float(coefficient), float(offset))
32@lazy_njit
33def _time_jitter_jit(t, x, y, p, std: float, clip_negative: bool,
34 sort_timestamps: bool):
35 """Add Gaussian noise to timestamps, optionally clipping and re-sorting."""
36 shifts = np.random.normal(0.0, std, len(t))
37 new_t = (t.astype(np.float64) + shifts).astype(np.int64)
39 if clip_negative:
40 keep = new_t >= 0
41 new_t, x, y, p = new_t[keep], x[keep], y[keep], p[keep]
43 if sort_timestamps:
44 order = np.argsort(new_t)
45 new_t, x, y, p = new_t[order], x[order], y[order], p[order]
47 return new_t, x, y, p
49def time_jitter(events, std=1.0, clip_negative=True, sort_timestamps=False):
50 """Add Gaussian noise to each timestamp.
52 Parameters
53 ----------
54 events : np.ndarray or EventArray
55 Events to jitter.
56 std : float, optional
57 Standard deviation of the timestamp noise. Default ``1.0``.
58 clip_negative : bool, optional
59 Drop events whose jittered timestamp is negative. Default ``True``.
60 sort_timestamps : bool, optional
61 Re-sort events by timestamp after jittering. Default ``False``.
63 Returns
64 -------
65 np.ndarray or EventArray
66 Jittered events, in their original container type.
67 """
68 return apply_kernel(events, _time_jitter_jit, float(std),
69 bool(clip_negative), bool(sort_timestamps))
71@lazy_njit
72def _normalize_ts_jit(t, x, y, p, start_ts: int):
73 """Shift every timestamp so the minimum lands at ``start_ts``."""
74 new_t = t - (t.min() - start_ts)
75 return new_t, x, y, p
77def normalize_ts(events, start_ts=0):
78 """Shift timestamps so the earliest event lands at ``start_ts``.
80 Pure and stateless: each call normalizes the batch it is handed on its own.
81 For chunk-by-chunk streams use ``EventReader(normalize_ts=True)``, which
82 latches the offset from the first chunk and applies it across the whole
83 stream (so the timeline stays continuous instead of resetting per chunk).
85 Parameters
86 ----------
87 events : np.ndarray or EventArray
88 Events to normalize.
89 start_ts : int, optional
90 Timestamp assigned to the earliest event. Default ``0``.
92 Returns
93 -------
94 np.ndarray or EventArray
95 Events with shifted timestamps, in their original container type. The
96 input is not modified.
98 Examples
99 --------
100 >>> import numpy as np
101 >>> from evutils.transforms.functional import normalize_ts
102 >>> events = np.array(
103 ... [(0, 0, 100, 1), (1, 1, 200, 1), (2, 2, 300, 0)],
104 ... dtype=[('x', 'u2'), ('y', 'u2'), ('t', 'i8'), ('p', 'i1')]
105 ... )
106 >>> normalize_ts(events)['t']
107 array([ 0, 100, 200])
108 """
109 return apply_kernel(events, _normalize_ts_jit, int(start_ts))