Skip to main content

eventcv_core/representation/
voxel.rs

1use super::{
2    age_ms, event_index, frame_len, reference_time, validate_positive, EventFrame, EventFrameData,
3    Representation, RepresentationError, RepresentationKind,
4};
5use crate::EventStream;
6
7#[derive(Clone, Copy, Debug)]
8pub struct VoxelGrid {
9    bins: usize,
10    window_ms: f64,
11}
12
13impl VoxelGrid {
14    pub fn new(bins: usize, window_ms: f64) -> Self {
15        Self { bins, window_ms }
16    }
17}
18
19impl Default for VoxelGrid {
20    fn default() -> Self {
21        Self::new(9, 30.0)
22    }
23}
24
25impl Representation for VoxelGrid {
26    type Output = EventFrame;
27
28    fn generate(&self, stream: &EventStream) -> Result<EventFrame, RepresentationError> {
29        if self.bins == 0 {
30            return Err(RepresentationError::InvalidParameter("bins"));
31        }
32        validate_positive(self.window_ms, "window_ms")?;
33        let (width, height, length) = frame_len(stream, self.bins)?;
34        let plane_len = width * height;
35        let mut values = vec![0_f32; length];
36
37        if let Some(reference) = reference_time(stream) {
38            for event in stream.iter() {
39                let age = age_ms(stream, reference, event.timestamp);
40                if age > self.window_ms {
41                    continue;
42                }
43                let spatial_index = event_index(event, width, height)?;
44                let position = if self.bins == 1 {
45                    0.0
46                } else {
47                    (1.0 - age / self.window_ms) * (self.bins - 1) as f64
48                };
49                let lower = position.floor() as usize;
50                let upper = position.ceil() as usize;
51                let polarity = if event.polarity { 1.0 } else { -1.0 };
52                if lower == upper {
53                    values[lower * plane_len + spatial_index] += polarity;
54                } else {
55                    let upper_weight = (position - lower as f64) as f32;
56                    values[lower * plane_len + spatial_index] += polarity * (1.0 - upper_weight);
57                    values[upper * plane_len + spatial_index] += polarity * upper_weight;
58                }
59            }
60        }
61
62        Ok(EventFrame {
63            data: EventFrameData::F32(values),
64            channels: self.bins,
65            width,
66            height,
67            kind: RepresentationKind::Voxel,
68            channel_names: (0..self.bins).map(|bin| format!("bin_{bin}")).collect(),
69        })
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use ndarray::array;
76
77    use super::{Representation, VoxelGrid};
78    use crate::{representation::EventFrameData, EventStream};
79
80    #[test]
81    fn interpolates_signed_events_between_time_bins() {
82        let stream = EventStream::from_array2(
83            array![[0, 0, 0, 1], [0, 0, 15_000, 0], [1, 0, 30_000, 1]],
84            2,
85            1,
86            0.001,
87        );
88
89        let frame = VoxelGrid::new(3, 30.0).generate(&stream).unwrap();
90
91        assert_eq!(
92            frame.data(),
93            &EventFrameData::F32(vec![1.0, 0.0, -1.0, 0.0, 0.0, 1.0])
94        );
95    }
96
97    #[test]
98    fn equal_timestamps_use_the_final_bin() {
99        let stream = EventStream::from_array2(array![[0, 0, 10, 1], [0, 0, 10, 1]], 1, 1, 0.001);
100
101        let frame = VoxelGrid::new(3, 30.0).generate(&stream).unwrap();
102
103        assert_eq!(frame.data(), &EventFrameData::F32(vec![0.0, 0.0, 2.0]));
104    }
105
106    #[test]
107    fn opposing_events_cancel_in_the_same_bins() {
108        let stream = EventStream::from_array2(array![[0, 0, 10, 1], [0, 0, 10, 0]], 1, 1, 0.001);
109
110        let frame = VoxelGrid::new(3, 30.0).generate(&stream).unwrap();
111
112        assert_eq!(frame.data(), &EventFrameData::F32(vec![0.0; 3]));
113    }
114}