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 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 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 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}