Skip to main content

eventcv_core/
representation.rs

1use std::{error::Error, fmt};
2
3use crate::{Event, EventStream};
4
5mod averaged_time_surface;
6mod binary;
7mod count;
8mod mcts;
9mod point_set;
10mod polarity;
11mod tencode;
12mod time_surface;
13mod voxel;
14
15pub use averaged_time_surface::AveragedTimeSurface;
16pub use binary::Binary;
17pub use count::EventCount;
18pub use mcts::Mcts;
19pub use point_set::{EventPointSet, PointSet};
20pub use polarity::Polarity;
21pub use tencode::Tencode;
22pub use time_surface::TimeSurface;
23pub use voxel::VoxelGrid;
24
25pub trait Representation {
26    type Output;
27
28    fn generate(&self, stream: &EventStream) -> Result<Self::Output, RepresentationError>;
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub enum RepresentationKind {
33    AveragedTimeSurface,
34    Binary,
35    Count,
36    Flow,
37    Labels,
38    Mcts,
39    Polarity,
40    Tencode,
41    TimeSurface,
42    Voxel,
43}
44
45impl RepresentationKind {
46    pub fn as_str(self) -> &'static str {
47        match self {
48            Self::AveragedTimeSurface => "atsurf",
49            Self::Binary => "binary",
50            Self::Count => "count",
51            Self::Flow => "flow",
52            Self::Labels => "labels",
53            Self::Mcts => "mcts",
54            Self::Polarity => "polarity",
55            Self::Tencode => "tencode",
56            Self::TimeSurface => "tsurf",
57            Self::Voxel => "voxel",
58        }
59    }
60
61    /// The inverse of [`Self::as_str`] — recovers the kind tag stored by the frame writers.
62    pub fn from_tag(tag: &str) -> Option<Self> {
63        Some(match tag {
64            "atsurf" => Self::AveragedTimeSurface,
65            "binary" => Self::Binary,
66            "count" => Self::Count,
67            "flow" => Self::Flow,
68            "labels" => Self::Labels,
69            "mcts" => Self::Mcts,
70            "polarity" => Self::Polarity,
71            "tencode" => Self::Tencode,
72            "tsurf" => Self::TimeSurface,
73            "voxel" => Self::Voxel,
74            _ => return None,
75        })
76    }
77}
78
79#[derive(Clone, Debug)]
80pub struct EventFrame {
81    pub(crate) data: EventFrameData,
82    pub(crate) channels: usize,
83    pub(crate) width: usize,
84    pub(crate) height: usize,
85    pub(crate) kind: RepresentationKind,
86    pub(crate) channel_names: Vec<String>,
87}
88
89#[derive(Clone, Debug, PartialEq)]
90pub enum EventFrameData {
91    U8(Vec<u8>),
92    U16(Vec<u16>),
93    U64(Vec<u64>),
94    F32(Vec<f32>),
95}
96
97impl EventFrame {
98    /// Reassembles a frame from its stored parts — used by the IO frame readers. Channels
99    /// is `channel_names.len()`, and the data length must equal `channels * width * height`.
100    pub(crate) fn from_parts(
101        data: EventFrameData,
102        width: usize,
103        height: usize,
104        kind: RepresentationKind,
105        channel_names: Vec<String>,
106    ) -> Self {
107        Self {
108            data,
109            channels: channel_names.len(),
110            width,
111            height,
112            kind,
113            channel_names,
114        }
115    }
116
117    pub fn data(&self) -> &EventFrameData {
118        &self.data
119    }
120
121    pub fn shape(&self) -> (usize, usize, usize) {
122        (self.channels, self.height, self.width)
123    }
124
125    pub fn channel_names(&self) -> &[String] {
126        &self.channel_names
127    }
128
129    pub fn kind(&self) -> RepresentationKind {
130        self.kind
131    }
132}
133
134impl EventFrameData {
135    /// Number of scalar elements (`channels * width * height` for a well-formed frame).
136    pub(crate) fn len(&self) -> usize {
137        match self {
138            Self::U8(values) => values.len(),
139            Self::U16(values) => values.len(),
140            Self::U64(values) => values.len(),
141            Self::F32(values) => values.len(),
142        }
143    }
144}
145
146pub(crate) fn frame_len(
147    stream: &EventStream,
148    channels: usize,
149) -> Result<(usize, usize, usize), RepresentationError> {
150    let (width, height) = stream.sensor_size();
151    let plane_len = width
152        .checked_mul(height)
153        .ok_or(RepresentationError::SizeOverflow)?;
154    let length = plane_len
155        .checked_mul(channels)
156        .ok_or(RepresentationError::SizeOverflow)?;
157    Ok((width, height, length))
158}
159
160pub(crate) fn event_index(
161    event: Event,
162    width: usize,
163    height: usize,
164) -> Result<usize, RepresentationError> {
165    if event.x >= width || event.y >= height {
166        return Err(RepresentationError::EventOutOfBounds {
167            x: event.x,
168            y: event.y,
169            width,
170            height,
171        });
172    }
173    Ok(event.y * width + event.x)
174}
175
176pub(crate) fn validate_positive(value: f64, name: &'static str) -> Result<(), RepresentationError> {
177    if !value.is_finite() || value <= 0.0 {
178        return Err(RepresentationError::InvalidParameter(name));
179    }
180    Ok(())
181}
182
183pub(crate) fn reference_time(stream: &EventStream) -> Option<u64> {
184    stream.iter().map(|event| event.timestamp).max()
185}
186
187pub(crate) fn age_ms(stream: &EventStream, reference: u64, timestamp: u64) -> f64 {
188    reference.saturating_sub(timestamp) as f64 * stream.timestamp_scale_ms()
189}
190
191#[derive(Debug, PartialEq, Eq)]
192pub enum RepresentationError {
193    SizeOverflow,
194    CountOverflow {
195        x: usize,
196        y: usize,
197    },
198    EventOutOfBounds {
199        x: usize,
200        y: usize,
201        width: usize,
202        height: usize,
203    },
204    InvalidParameter(&'static str),
205}
206
207impl fmt::Display for RepresentationError {
208    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
209        match self {
210            Self::SizeOverflow => formatter.write_str("representation dimensions are too large"),
211            Self::CountOverflow { x, y } => {
212                write!(
213                    formatter,
214                    "event count at ({x}, {y}) exceeds uint16 capacity"
215                )
216            }
217            Self::EventOutOfBounds {
218                x,
219                y,
220                width,
221                height,
222            } => write!(
223                formatter,
224                "event coordinate ({x}, {y}) exceeds sensor size {width}x{height}"
225            ),
226            Self::InvalidParameter(name) => match *name {
227                "bins" => formatter.write_str("bins must be at least 1"),
228                "max_window_ms" => {
229                    formatter.write_str("max_window_ms must be finite and at least 1")
230                }
231                _ => write!(formatter, "{name} must be finite and positive"),
232            },
233        }
234    }
235}
236
237impl Error for RepresentationError {}
238
239#[cfg(test)]
240mod tests {
241    use ndarray::Array2;
242
243    use super::{
244        AveragedTimeSurface, Binary, EventCount, EventFrameData, Mcts, PointSet, Representation,
245        RepresentationError, Tencode, TimeSurface, VoxelGrid,
246    };
247    use crate::EventStream;
248
249    fn empty_stream(width: usize, height: usize) -> EventStream {
250        EventStream::from_array2(Array2::zeros((0, 4)), width, height, 0.001)
251    }
252
253    #[test]
254    fn empty_streams_produce_zero_outputs() {
255        let stream = empty_stream(2, 3);
256
257        for frame in [
258            Binary.generate(&stream).unwrap(),
259            EventCount::new(true).generate(&stream).unwrap(),
260            VoxelGrid::default().generate(&stream).unwrap(),
261            TimeSurface::default().generate(&stream).unwrap(),
262            AveragedTimeSurface::default().generate(&stream).unwrap(),
263            Tencode::default().generate(&stream).unwrap(),
264            Mcts::default().generate(&stream).unwrap(),
265        ] {
266            match frame.data() {
267                EventFrameData::U8(values) => assert!(values.iter().all(|&value| value == 0)),
268                EventFrameData::F32(values) => assert!(values.iter().all(|&value| value == 0.0)),
269                _ => panic!("unexpected empty representation dtype"),
270            }
271        }
272        assert_eq!(PointSet.generate(&stream).unwrap().shape(), (0, 4));
273    }
274
275    #[test]
276    fn rejects_invalid_parameters_and_size_overflow() {
277        let stream = empty_stream(2, 3);
278
279        assert_eq!(
280            VoxelGrid::new(0, 30.0).generate(&stream).unwrap_err(),
281            RepresentationError::InvalidParameter("bins")
282        );
283        assert_eq!(
284            TimeSurface::new(f64::NAN).generate(&stream).unwrap_err(),
285            RepresentationError::InvalidParameter("tau_ms")
286        );
287        assert_eq!(
288            Tencode::new(0.0).generate(&stream).unwrap_err(),
289            RepresentationError::InvalidParameter("window_ms")
290        );
291        assert_eq!(
292            Mcts::new(0.5).generate(&stream).unwrap_err(),
293            RepresentationError::InvalidParameter("max_window_ms")
294        );
295
296        let oversized = empty_stream(usize::MAX, 2);
297        assert_eq!(
298            Binary.generate(&oversized).unwrap_err(),
299            RepresentationError::SizeOverflow
300        );
301    }
302}