Skip to main content

eventcv_core/transform/
temporal.rs

1//! Temporal transforms — operate on timestamps (and event selection in time); coordinates and
2//! sensor size are unchanged.
3
4use crate::{EventStream, EventStreamBuilder};
5
6impl EventStream {
7    /// Keeps events whose timestamp lies in the half-open window `[t0, t1)`.
8    pub fn time_window(&self, t0: i64, t1: i64) -> EventStream {
9        let (width, height) = self.sensor_size();
10        self.remap(width, height, |x, y, t, p| {
11            (t >= t0 && t < t1).then_some((x, y, t, p))
12        })
13    }
14
15    /// Shifts every timestamp by `dt` (same units as the stored timestamps).
16    pub fn time_shift(&self, dt: i64) -> EventStream {
17        let (width, height) = self.sensor_size();
18        self.remap(width, height, |x, y, t, p| Some((x, y, t + dt, p)))
19    }
20
21    /// Scales every timestamp by `factor` (rounded), e.g. to change playback speed.
22    pub fn time_scale(&self, factor: f64) -> EventStream {
23        let (width, height) = self.sensor_size();
24        self.remap(width, height, |x, y, t, p| {
25            Some((x, y, (t as f64 * factor).round() as i64, p))
26        })
27    }
28
29    /// Shifts timestamps so the earliest event starts at zero. A no-op on an empty stream.
30    pub fn normalize_time(&self) -> EventStream {
31        match self.ts().iter().min() {
32            Some(&t_min) => self.time_shift(-t_min),
33            None => self.clone(),
34        }
35    }
36
37    /// Keeps every `k`-th event by index (`k = 1` is the identity); `k = 0` is treated as 1.
38    pub fn decimate(&self, k: usize) -> EventStream {
39        let k = k.max(1);
40        let (width, height) = self.sensor_size();
41        let mut builder = EventStreamBuilder::with_capacity(
42            width,
43            height,
44            self.timestamp_scale_ms(),
45            self.len() / k + 1,
46        );
47        let (xs, ys, ts, ps) = (self.xs(), self.ys(), self.ts(), self.ps());
48        for index in (0..self.len()).step_by(k) {
49            builder.push(xs[index], ys[index], ts[index], ps[index]);
50        }
51        builder.build()
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use crate::{EventStream, EventStreamBuilder};
58
59    fn sample() -> EventStream {
60        let mut builder = EventStreamBuilder::new(4, 3, 0.001);
61        for i in 0..6u16 {
62            builder.push(i % 4, i % 3, 100 + i64::from(i) * 10, i % 2 == 0);
63        }
64        builder.build()
65    }
66
67    #[test]
68    fn time_window_is_half_open() {
69        let windowed = sample().time_window(110, 140); // keeps t = 110, 120, 130
70        assert_eq!(windowed.ts(), &[110, 120, 130]);
71    }
72
73    #[test]
74    fn time_shift_inverts_and_preserves_count() {
75        let s = sample();
76        let back = s.time_shift(1000).time_shift(-1000);
77        assert_eq!(back.ts(), s.ts());
78        assert_eq!(back.len(), s.len());
79    }
80
81    #[test]
82    fn normalize_time_starts_at_zero() {
83        let n = sample().normalize_time();
84        assert_eq!(n.ts()[0], 0);
85        assert_eq!(n.ts(), &[0, 10, 20, 30, 40, 50]);
86    }
87
88    #[test]
89    fn time_scale_rounds() {
90        let scaled = sample().time_scale(0.5);
91        assert_eq!(scaled.ts(), &[50, 55, 60, 65, 70, 75]);
92    }
93
94    #[test]
95    fn decimate_keeps_every_kth_event() {
96        let d = sample().decimate(2);
97        assert_eq!(d.len(), 3);
98        assert_eq!(d.ts(), &[100, 120, 140]);
99        assert_eq!(sample().decimate(0).len(), 6); // k=0 treated as identity
100    }
101
102    #[test]
103    fn temporal_ops_handle_empty() {
104        let empty = EventStreamBuilder::new(4, 3, 0.001).build();
105        assert!(empty.normalize_time().is_empty());
106        assert!(empty.time_shift(5).is_empty());
107        assert!(empty.decimate(3).is_empty());
108    }
109}