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