Skip to main content

scirs2_vision/event_camera/
types.rs

1//! Core event camera data types.
2//!
3//! Provides [`Event`], [`EventSlice`], [`EventFrame`], and [`EventProcessingConfig`]
4//! for representing and configuring Dynamic Vision Sensor data.
5
6use scirs2_core::ndarray::Array2;
7
8use crate::error::{Result, VisionError};
9
10/// Polarity of a brightness change event.
11#[non_exhaustive]
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum Polarity {
14    /// Brightness increase (log-intensity crossed threshold upward).
15    On,
16    /// Brightness decrease (log-intensity crossed threshold downward).
17    Off,
18}
19
20impl Polarity {
21    /// Returns `1.0` for [`Polarity::On`] and `-1.0` for [`Polarity::Off`].
22    pub fn sign(self) -> f64 {
23        match self {
24            Polarity::On => 1.0,
25            Polarity::Off => -1.0,
26        }
27    }
28}
29
30/// A single event from a Dynamic Vision Sensor (DVS).
31///
32/// Each event records a brightness change at a specific pixel coordinate and time.
33#[derive(Clone, Copy, Debug)]
34pub struct Event {
35    /// Pixel x coordinate (column).
36    pub x: u16,
37    /// Pixel y coordinate (row).
38    pub y: u16,
39    /// Timestamp in seconds (microsecond precision).
40    pub timestamp: f64,
41    /// Polarity of the brightness change.
42    pub polarity: Polarity,
43}
44
45impl Event {
46    /// Creates a new event.
47    pub fn new(x: u16, y: u16, timestamp: f64, polarity: Polarity) -> Self {
48        Self {
49            x,
50            y,
51            timestamp,
52            polarity,
53        }
54    }
55}
56
57/// A collection of events within a time window, associated with a sensor resolution.
58///
59/// Events are stored sorted by timestamp. The slice tracks the sensor dimensions
60/// and the temporal extent of the contained events.
61pub struct EventSlice {
62    events: Vec<Event>,
63    t_start: f64,
64    t_end: f64,
65    width: u16,
66    height: u16,
67}
68
69impl EventSlice {
70    /// Creates a new `EventSlice` from a vector of events and the sensor resolution.
71    ///
72    /// The events are sorted by timestamp internally. Returns an error if the
73    /// event list is empty or any event coordinate exceeds the given dimensions.
74    pub fn new(mut events: Vec<Event>, width: u16, height: u16) -> Result<Self> {
75        if events.is_empty() {
76            return Err(VisionError::InvalidParameter(
77                "EventSlice requires at least one event".to_string(),
78            ));
79        }
80
81        // Validate coordinates
82        for e in &events {
83            if e.x >= width || e.y >= height {
84                return Err(VisionError::InvalidParameter(format!(
85                    "Event coordinate ({}, {}) exceeds sensor dimensions ({}x{})",
86                    e.x, e.y, width, height
87                )));
88            }
89        }
90
91        // Sort by timestamp for deterministic processing
92        events.sort_by(|a, b| {
93            a.timestamp
94                .partial_cmp(&b.timestamp)
95                .unwrap_or(std::cmp::Ordering::Equal)
96        });
97
98        let t_start = events.first().map(|e| e.timestamp).unwrap_or_default();
99        let t_end = events.last().map(|e| e.timestamp).unwrap_or_default();
100
101        Ok(Self {
102            events,
103            t_start,
104            t_end,
105            width,
106            height,
107        })
108    }
109
110    /// Returns a reference to the event list.
111    pub fn events(&self) -> &[Event] {
112        &self.events
113    }
114
115    /// Returns the time range `(t_start, t_end)`.
116    pub fn time_range(&self) -> (f64, f64) {
117        (self.t_start, self.t_end)
118    }
119
120    /// Returns the sensor width.
121    pub fn width(&self) -> u16 {
122        self.width
123    }
124
125    /// Returns the sensor height.
126    pub fn height(&self) -> u16 {
127        self.height
128    }
129
130    /// Filters events by polarity, returning a new `EventSlice`.
131    pub fn filter_by_polarity(&self, polarity: Polarity) -> Result<Self> {
132        let filtered: Vec<Event> = self
133            .events
134            .iter()
135            .filter(|e| e.polarity == polarity)
136            .copied()
137            .collect();
138
139        if filtered.is_empty() {
140            return Err(VisionError::InvalidParameter(format!(
141                "No events with polarity {:?} found",
142                polarity
143            )));
144        }
145
146        Self::new(filtered, self.width, self.height)
147    }
148
149    /// Splits the event slice at a given timestamp `t`.
150    ///
151    /// Returns `(before, after)` where `before` contains events with `timestamp < t`
152    /// and `after` contains events with `timestamp >= t`.
153    /// Returns an error if either half would be empty.
154    pub fn split_at_time(&self, t: f64) -> Result<(Self, Self)> {
155        let before: Vec<Event> = self
156            .events
157            .iter()
158            .filter(|e| e.timestamp < t)
159            .copied()
160            .collect();
161        let after: Vec<Event> = self
162            .events
163            .iter()
164            .filter(|e| e.timestamp >= t)
165            .copied()
166            .collect();
167
168        if before.is_empty() {
169            return Err(VisionError::InvalidParameter(
170                "Split time is before all events; 'before' half would be empty".to_string(),
171            ));
172        }
173        if after.is_empty() {
174            return Err(VisionError::InvalidParameter(
175                "Split time is after all events; 'after' half would be empty".to_string(),
176            ));
177        }
178
179        Ok((
180            Self::new(before, self.width, self.height)?,
181            Self::new(after, self.width, self.height)?,
182        ))
183    }
184
185    /// Returns the event rate in events per second.
186    pub fn event_rate(&self) -> f64 {
187        let duration = self.t_end - self.t_start;
188        if duration <= 0.0 {
189            return 0.0;
190        }
191        self.events.len() as f64 / duration
192    }
193
194    /// Returns the number of events.
195    pub fn len(&self) -> usize {
196        self.events.len()
197    }
198
199    /// Returns `true` if there are no events.
200    pub fn is_empty(&self) -> bool {
201        self.events.is_empty()
202    }
203
204    /// Creates a sub-slice containing events within `[t0, t1)`.
205    pub fn time_window(&self, t0: f64, t1: f64) -> Result<Self> {
206        let sub: Vec<Event> = self
207            .events
208            .iter()
209            .filter(|e| e.timestamp >= t0 && e.timestamp < t1)
210            .copied()
211            .collect();
212
213        if sub.is_empty() {
214            return Err(VisionError::InvalidParameter(format!(
215                "No events in time window [{}, {})",
216                t0, t1
217            )));
218        }
219
220        Self::new(sub, self.width, self.height)
221    }
222}
223
224/// An accumulated event frame — a 2D image generated from events.
225pub struct EventFrame {
226    /// Image data with shape `[height, width]`.
227    pub data: Array2<f64>,
228    /// Start of the temporal window.
229    pub t_start: f64,
230    /// End of the temporal window.
231    pub t_end: f64,
232}
233
234/// Configuration for event processing operations.
235pub struct EventProcessingConfig {
236    /// Sensor width in pixels.
237    pub width: u16,
238    /// Sensor height in pixels.
239    pub height: u16,
240    /// Duration of each frame window in seconds.
241    pub time_window: f64,
242    /// Decay rate for exponential decay surface (tau).
243    pub decay_rate: f64,
244    /// Polarity threshold for noise filtering.
245    pub polarity_threshold: f64,
246}
247
248impl Default for EventProcessingConfig {
249    fn default() -> Self {
250        Self {
251            width: 240,
252            height: 180,
253            time_window: 0.033, // ~30 fps
254            decay_rate: 0.01,   // 10 ms decay
255            polarity_threshold: 0.5,
256        }
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn test_event_creation() {
266        let e = Event::new(10, 20, 1.0, Polarity::On);
267        assert_eq!(e.x, 10);
268        assert_eq!(e.y, 20);
269        assert!((e.timestamp - 1.0).abs() < f64::EPSILON);
270        assert_eq!(e.polarity, Polarity::On);
271    }
272
273    #[test]
274    fn test_polarity_sign() {
275        assert!((Polarity::On.sign() - 1.0).abs() < f64::EPSILON);
276        assert!((Polarity::Off.sign() - (-1.0)).abs() < f64::EPSILON);
277    }
278
279    #[test]
280    fn test_event_slice_basic() {
281        let events = vec![
282            Event::new(0, 0, 0.002, Polarity::On),
283            Event::new(1, 1, 0.001, Polarity::Off),
284            Event::new(2, 2, 0.003, Polarity::On),
285        ];
286        let slice = EventSlice::new(events, 10, 10).expect("failed to create EventSlice");
287        assert_eq!(slice.len(), 3);
288        assert!(!slice.is_empty());
289        // Should be sorted by timestamp
290        assert!(slice.events()[0].timestamp <= slice.events()[1].timestamp);
291        assert!(slice.events()[1].timestamp <= slice.events()[2].timestamp);
292    }
293
294    #[test]
295    fn test_event_slice_out_of_bounds() {
296        let events = vec![Event::new(10, 5, 0.0, Polarity::On)];
297        let result = EventSlice::new(events, 10, 10); // x=10 is out of bounds for width=10
298        assert!(result.is_err());
299    }
300
301    #[test]
302    fn test_event_slice_empty() {
303        let result = EventSlice::new(vec![], 10, 10);
304        assert!(result.is_err());
305    }
306
307    #[test]
308    fn test_filter_by_polarity() {
309        let events = vec![
310            Event::new(0, 0, 0.001, Polarity::On),
311            Event::new(1, 1, 0.002, Polarity::Off),
312            Event::new(2, 2, 0.003, Polarity::On),
313        ];
314        let slice = EventSlice::new(events, 10, 10).expect("failed");
315        let on_slice = slice.filter_by_polarity(Polarity::On).expect("failed");
316        assert_eq!(on_slice.len(), 2);
317        for e in on_slice.events() {
318            assert_eq!(e.polarity, Polarity::On);
319        }
320    }
321
322    #[test]
323    fn test_split_at_time() {
324        let events = vec![
325            Event::new(0, 0, 0.001, Polarity::On),
326            Event::new(1, 1, 0.002, Polarity::Off),
327            Event::new(2, 2, 0.003, Polarity::On),
328            Event::new(3, 3, 0.004, Polarity::Off),
329        ];
330        let slice = EventSlice::new(events, 10, 10).expect("failed");
331        let (before, after) = slice.split_at_time(0.0025).expect("failed");
332        assert_eq!(before.len(), 2);
333        assert_eq!(after.len(), 2);
334    }
335
336    #[test]
337    fn test_event_rate() {
338        let events = vec![
339            Event::new(0, 0, 0.0, Polarity::On),
340            Event::new(1, 1, 0.5, Polarity::Off),
341            Event::new(2, 2, 1.0, Polarity::On),
342        ];
343        let slice = EventSlice::new(events, 10, 10).expect("failed");
344        let rate = slice.event_rate();
345        assert!((rate - 3.0).abs() < 1e-9); // 3 events / 1 second
346    }
347
348    #[test]
349    fn test_time_window() {
350        let events = vec![
351            Event::new(0, 0, 0.0, Polarity::On),
352            Event::new(1, 1, 0.5, Polarity::Off),
353            Event::new(2, 2, 1.0, Polarity::On),
354            Event::new(3, 3, 1.5, Polarity::Off),
355        ];
356        let slice = EventSlice::new(events, 10, 10).expect("failed");
357        let sub = slice.time_window(0.3, 1.2).expect("failed");
358        assert_eq!(sub.len(), 2); // events at 0.5 and 1.0
359    }
360}