1use scirs2_core::ndarray::{Array2, Array3};
13
14use crate::error::{Result, VisionError};
15
16use super::types::{Event, EventFrame, EventProcessingConfig, EventSlice, Polarity};
17
18#[non_exhaustive]
20#[derive(Clone, Debug)]
21pub enum FrameMethod {
22 Histogram,
24 PolarityHistogram,
26 TimeSurface,
28 ExponentialDecay,
30 VoxelGrid {
32 n_bins: usize,
34 },
35}
36
37pub fn events_to_frame(
42 events: &EventSlice,
43 method: &FrameMethod,
44 config: &EventProcessingConfig,
45) -> Result<EventFrame> {
46 let h = config.height as usize;
47 let w = config.width as usize;
48 let (t_start, t_end) = events.time_range();
49
50 let data = match method {
51 FrameMethod::Histogram => {
52 let mut frame = Array2::<f64>::zeros((h, w));
53 for e in events.events() {
54 frame[[e.y as usize, e.x as usize]] += 1.0;
55 }
56 frame
57 }
58 FrameMethod::PolarityHistogram => {
59 let mut frame = Array2::<f64>::zeros((h, w));
60 for e in events.events() {
61 frame[[e.y as usize, e.x as usize]] += e.polarity.sign();
62 }
63 frame
64 }
65 FrameMethod::TimeSurface => {
66 return events_to_time_surface(events, config);
67 }
68 FrameMethod::ExponentialDecay => {
69 let mut frame = Array2::<f64>::zeros((h, w));
70 let tau = config.decay_rate;
71 if tau <= 0.0 {
72 return Err(VisionError::InvalidParameter(
73 "Decay rate (tau) must be positive".to_string(),
74 ));
75 }
76 for e in events.events() {
77 let dt = t_end - e.timestamp;
78 let weight = (-dt / tau).exp();
79 frame[[e.y as usize, e.x as usize]] += e.polarity.sign() * weight;
80 }
81 frame
82 }
83 FrameMethod::VoxelGrid { n_bins } => {
84 let voxel = events_to_voxel_grid(events, *n_bins, config)?;
85 let mut frame = Array2::<f64>::zeros((h, w));
87 for b in 0..*n_bins {
88 for y in 0..h {
89 for x in 0..w {
90 frame[[y, x]] += voxel[[b, y, x]];
91 }
92 }
93 }
94 frame
95 }
96 };
97
98 Ok(EventFrame {
99 data,
100 t_start,
101 t_end,
102 })
103}
104
105pub fn events_to_polarity_frames(
110 events: &EventSlice,
111 config: &EventProcessingConfig,
112) -> Result<(EventFrame, EventFrame)> {
113 let h = config.height as usize;
114 let w = config.width as usize;
115 let (t_start, t_end) = events.time_range();
116
117 let mut on_frame = Array2::<f64>::zeros((h, w));
118 let mut off_frame = Array2::<f64>::zeros((h, w));
119
120 for e in events.events() {
121 match e.polarity {
122 Polarity::On => {
123 on_frame[[e.y as usize, e.x as usize]] += 1.0;
124 }
125 Polarity::Off => {
126 off_frame[[e.y as usize, e.x as usize]] += 1.0;
127 }
128 }
129 }
130
131 Ok((
132 EventFrame {
133 data: on_frame,
134 t_start,
135 t_end,
136 },
137 EventFrame {
138 data: off_frame,
139 t_start,
140 t_end,
141 },
142 ))
143}
144
145pub fn events_to_voxel_grid(
150 events: &EventSlice,
151 n_bins: usize,
152 config: &EventProcessingConfig,
153) -> Result<Array3<f64>> {
154 if n_bins == 0 {
155 return Err(VisionError::InvalidParameter(
156 "n_bins must be at least 1".to_string(),
157 ));
158 }
159
160 let h = config.height as usize;
161 let w = config.width as usize;
162 let (t_start, t_end) = events.time_range();
163 let duration = t_end - t_start;
164
165 let mut voxel = Array3::<f64>::zeros((n_bins, h, w));
166
167 for e in events.events() {
168 let t_norm = if duration > 0.0 {
169 (e.timestamp - t_start) / duration
170 } else {
171 0.5 };
173 let t_clamped = t_norm.clamp(0.0, 1.0 - f64::EPSILON);
175 let bin = (t_clamped * n_bins as f64) as usize;
176 let bin = bin.min(n_bins - 1);
177
178 voxel[[bin, e.y as usize, e.x as usize]] += e.polarity.sign();
179 }
180
181 Ok(voxel)
182}
183
184pub fn events_to_time_surface(
190 events: &EventSlice,
191 config: &EventProcessingConfig,
192) -> Result<EventFrame> {
193 let h = config.height as usize;
194 let w = config.width as usize;
195 let (t_start, t_end) = events.time_range();
196 let duration = t_end - t_start;
197
198 let mut frame = Array2::<f64>::zeros((h, w));
199
200 for e in events.events() {
201 let t_norm = if duration > 0.0 {
202 (e.timestamp - t_start) / duration
203 } else {
204 1.0
205 };
206 frame[[e.y as usize, e.x as usize]] = t_norm;
208 }
209
210 Ok(EventFrame {
211 data: frame,
212 t_start,
213 t_end,
214 })
215}
216
217pub struct StreamingFrameAccumulator {
221 frame: Array2<f64>,
222 timestamps: Array2<f64>,
223 config: EventProcessingConfig,
224}
225
226impl StreamingFrameAccumulator {
227 pub fn new(config: EventProcessingConfig) -> Self {
229 let h = config.height as usize;
230 let w = config.width as usize;
231 Self {
232 frame: Array2::<f64>::zeros((h, w)),
233 timestamps: Array2::<f64>::zeros((h, w)),
234 config,
235 }
236 }
237
238 pub fn add_event(&mut self, event: &Event) {
243 let y = event.y as usize;
244 let x = event.x as usize;
245 if y < self.config.height as usize && x < self.config.width as usize {
246 self.frame[[y, x]] += event.polarity.sign();
247 self.timestamps[[y, x]] = event.timestamp;
248 }
249 }
250
251 pub fn get_frame(&self) -> &Array2<f64> {
253 &self.frame
254 }
255
256 pub fn decay(&mut self, current_time: f64) {
260 let tau = self.config.decay_rate;
261 if tau <= 0.0 {
262 return;
263 }
264 let h = self.config.height as usize;
265 let w = self.config.width as usize;
266 for y in 0..h {
267 for x in 0..w {
268 let dt = current_time - self.timestamps[[y, x]];
269 if dt > 0.0 {
270 self.frame[[y, x]] *= (-dt / tau).exp();
271 self.timestamps[[y, x]] = current_time;
272 }
273 }
274 }
275 }
276
277 pub fn reset(&mut self) {
279 self.frame.fill(0.0);
280 self.timestamps.fill(0.0);
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use crate::event_camera::types::{Event, EventSlice, Polarity};
288
289 fn make_config(w: u16, h: u16) -> EventProcessingConfig {
290 EventProcessingConfig {
291 width: w,
292 height: h,
293 time_window: 0.033,
294 decay_rate: 0.01,
295 polarity_threshold: 0.5,
296 }
297 }
298
299 #[test]
300 fn test_histogram_single_event() {
301 let events = vec![Event::new(5, 10, 0.001, Polarity::On)];
302 let slice = EventSlice::new(events, 20, 20).expect("failed");
303 let config = make_config(20, 20);
304 let frame = events_to_frame(&slice, &FrameMethod::Histogram, &config).expect("failed");
305 assert!((frame.data[[10, 5]] - 1.0).abs() < f64::EPSILON);
306 assert!((frame.data[[0, 0]]).abs() < f64::EPSILON);
308 }
309
310 #[test]
311 fn test_polarity_histogram_separated() {
312 let events = vec![
313 Event::new(0, 0, 0.001, Polarity::On),
314 Event::new(1, 1, 0.002, Polarity::Off),
315 Event::new(0, 0, 0.003, Polarity::On),
316 ];
317 let slice = EventSlice::new(events, 10, 10).expect("failed");
318 let config = make_config(10, 10);
319 let (on_frame, off_frame) = events_to_polarity_frames(&slice, &config).expect("failed");
320 assert!((on_frame.data[[0, 0]] - 2.0).abs() < f64::EPSILON);
321 assert!((off_frame.data[[1, 1]] - 1.0).abs() < f64::EPSILON);
322 assert!((on_frame.data[[1, 1]]).abs() < f64::EPSILON);
323 assert!((off_frame.data[[0, 0]]).abs() < f64::EPSILON);
324 }
325
326 #[test]
327 fn test_time_surface_most_recent() {
328 let events = vec![
329 Event::new(5, 5, 0.0, Polarity::On),
330 Event::new(5, 5, 0.5, Polarity::Off),
331 Event::new(5, 5, 1.0, Polarity::On),
332 ];
333 let slice = EventSlice::new(events, 10, 10).expect("failed");
334 let config = make_config(10, 10);
335 let frame = events_to_time_surface(&slice, &config).expect("failed");
336 assert!((frame.data[[5, 5]] - 1.0).abs() < 1e-9);
338 }
339
340 #[test]
341 fn test_exponential_decay_older_lower_weight() {
342 let events = vec![
344 Event::new(0, 0, 0.0, Polarity::On),
345 Event::new(1, 0, 1.0, Polarity::On), ];
347 let slice = EventSlice::new(events, 10, 10).expect("failed");
348 let config = EventProcessingConfig {
349 width: 10,
350 height: 10,
351 time_window: 0.033,
352 decay_rate: 0.5, polarity_threshold: 0.5,
354 };
355 let frame =
356 events_to_frame(&slice, &FrameMethod::ExponentialDecay, &config).expect("failed");
357 let expected_old = (-2.0_f64).exp();
359 assert!((frame.data[[0, 0]] - expected_old).abs() < 1e-9);
360 assert!((frame.data[[0, 1]] - 1.0).abs() < 1e-9);
362 }
363
364 #[test]
365 fn test_voxel_grid_bin_assignment() {
366 let events = vec![
368 Event::new(0, 0, 0.0, Polarity::On), Event::new(0, 0, 0.5, Polarity::On), Event::new(0, 0, 1.0, Polarity::On), ];
372 let slice = EventSlice::new(events, 10, 10).expect("failed");
373 let config = make_config(10, 10);
374 let voxel = events_to_voxel_grid(&slice, 3, &config).expect("failed");
375 assert_eq!(voxel.shape(), &[3, 10, 10]);
376 let total: f64 = (0..3).map(|b| voxel[[b, 0, 0]]).sum();
378 assert!((total - 3.0).abs() < f64::EPSILON); }
380
381 #[test]
382 fn test_streaming_accumulator_matches_batch() {
383 let events = vec![
384 Event::new(0, 0, 0.001, Polarity::On),
385 Event::new(1, 1, 0.002, Polarity::Off),
386 Event::new(0, 0, 0.003, Polarity::On),
387 ];
388 let config = make_config(10, 10);
389
390 let slice = EventSlice::new(events.clone(), 10, 10).expect("failed");
392 let batch_frame =
393 events_to_frame(&slice, &FrameMethod::PolarityHistogram, &config).expect("failed");
394
395 let mut acc = StreamingFrameAccumulator::new(make_config(10, 10));
397 for e in &events {
398 acc.add_event(e);
399 }
400
401 assert!((acc.get_frame()[[0, 0]] - batch_frame.data[[0, 0]]).abs() < f64::EPSILON);
403 assert!((acc.get_frame()[[1, 1]] - batch_frame.data[[1, 1]]).abs() < f64::EPSILON);
404 }
405}