1use ndarray::Array2;
28
29pub mod bias;
30pub mod camera;
31pub mod cluster;
32#[cfg(feature = "camera")]
33pub mod device;
34pub mod feast;
35pub mod features;
36pub mod filter;
37pub mod flow;
38pub mod image;
39pub mod io;
40pub mod representation;
41pub mod transform;
42pub mod viz;
43
44const COLUMN_COUNT: usize = 4;
45
46#[derive(Clone, Debug)]
50pub struct EventStream {
51 xs: Vec<u16>,
52 ys: Vec<u16>,
53 ts: Vec<i64>,
54 ps: Vec<bool>,
55 width: usize,
56 height: usize,
57 timestamp_scale_ms: f64,
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub struct Event {
62 pub x: usize,
63 pub y: usize,
64 pub timestamp: u64,
65 pub polarity: bool,
66}
67
68impl EventStream {
69 pub fn len(&self) -> usize {
70 self.xs.len()
71 }
72
73 pub fn is_empty(&self) -> bool {
74 self.xs.is_empty()
75 }
76
77 pub fn sensor_size(&self) -> (usize, usize) {
78 (self.width, self.height)
79 }
80
81 pub fn timestamp_scale_ms(&self) -> f64 {
82 self.timestamp_scale_ms
83 }
84
85 pub fn xs(&self) -> &[u16] {
86 &self.xs
87 }
88
89 pub fn ys(&self) -> &[u16] {
90 &self.ys
91 }
92
93 pub fn ts(&self) -> &[i64] {
94 &self.ts
95 }
96
97 pub fn ps(&self) -> &[bool] {
98 &self.ps
99 }
100
101 pub fn iter(&self) -> impl Iterator<Item = Event> + '_ {
102 (0..self.len()).map(move |index| Event {
103 x: self.xs[index] as usize,
104 y: self.ys[index] as usize,
105 timestamp: self.ts[index] as u64,
106 polarity: self.ps[index],
107 })
108 }
109
110 pub fn to_array2(&self) -> Array2<u64> {
112 let mut values = Vec::with_capacity(self.len() * COLUMN_COUNT);
113 for index in 0..self.len() {
114 values.push(u64::from(self.xs[index]));
115 values.push(u64::from(self.ys[index]));
116 values.push(self.ts[index] as u64);
117 values.push(u64::from(self.ps[index]));
118 }
119 Array2::from_shape_vec((self.len(), COLUMN_COUNT), values)
120 .expect("columns share a length by construction")
121 }
122
123 #[cfg(test)]
126 pub(crate) fn from_array2(
127 events: Array2<u64>,
128 width: usize,
129 height: usize,
130 timestamp_scale_ms: f64,
131 ) -> Self {
132 Self {
133 xs: events.column(0).iter().map(|&value| value as u16).collect(),
134 ys: events.column(1).iter().map(|&value| value as u16).collect(),
135 ts: events.column(2).iter().map(|&value| value as i64).collect(),
136 ps: events.column(3).iter().map(|&value| value != 0).collect(),
137 width,
138 height,
139 timestamp_scale_ms,
140 }
141 }
142}
143
144#[derive(Clone, Debug)]
147pub struct EventStreamBuilder {
148 xs: Vec<u16>,
149 ys: Vec<u16>,
150 ts: Vec<i64>,
151 ps: Vec<bool>,
152 width: usize,
153 height: usize,
154 timestamp_scale_ms: f64,
155}
156
157impl EventStreamBuilder {
158 pub fn new(width: usize, height: usize, timestamp_scale_ms: f64) -> Self {
159 Self::with_capacity(width, height, timestamp_scale_ms, 0)
160 }
161
162 pub fn with_capacity(
163 width: usize,
164 height: usize,
165 timestamp_scale_ms: f64,
166 capacity: usize,
167 ) -> Self {
168 Self {
169 xs: Vec::with_capacity(capacity),
170 ys: Vec::with_capacity(capacity),
171 ts: Vec::with_capacity(capacity),
172 ps: Vec::with_capacity(capacity),
173 width,
174 height,
175 timestamp_scale_ms,
176 }
177 }
178
179 pub fn push(&mut self, x: u16, y: u16, timestamp: i64, polarity: bool) -> bool {
182 if usize::from(x) >= self.width || usize::from(y) >= self.height {
183 return false;
184 }
185 self.xs.push(x);
186 self.ys.push(y);
187 self.ts.push(timestamp);
188 self.ps.push(polarity);
189 true
190 }
191
192 pub fn len(&self) -> usize {
193 self.xs.len()
194 }
195
196 pub fn is_empty(&self) -> bool {
197 self.xs.is_empty()
198 }
199
200 pub fn build(self) -> EventStream {
201 EventStream {
202 xs: self.xs,
203 ys: self.ys,
204 ts: self.ts,
205 ps: self.ps,
206 width: self.width,
207 height: self.height,
208 timestamp_scale_ms: self.timestamp_scale_ms,
209 }
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use std::path::PathBuf;
216
217 use crate::io::{load, LoadOptions};
218
219 use super::{EventStream, EventStreamBuilder};
220
221 #[test]
222 fn loads_n_imagenet_events() {
223 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../data/test/example.npz");
224 let stream = load(path, LoadOptions::default()).unwrap();
225 let events = stream.to_array2();
226
227 assert!(!stream.is_empty());
228 assert_eq!(stream.sensor_size(), (640, 480));
229 assert_eq!(events.dim(), (stream.len(), 4));
230 assert!(events.column(0).iter().all(|&x| x < 640));
231 assert!(events.column(1).iter().all(|&y| y < 480));
232 assert!(events.column(3).iter().all(|&polarity| polarity <= 1));
233 }
234
235 #[test]
236 fn builder_drops_out_of_bounds_events_and_keeps_columns_aligned() {
237 let mut builder = EventStreamBuilder::new(4, 3, 0.001);
238
239 assert!(builder.push(1, 2, 10, true));
240 assert!(!builder.push(4, 0, 20, false)); assert!(!builder.push(0, 3, 30, true)); assert!(builder.push(3, 0, 40, false));
243
244 let stream = builder.build();
245 assert_eq!(stream.len(), 2);
246 assert_eq!(stream.xs(), &[1, 3]);
247 assert_eq!(stream.ys(), &[2, 0]);
248 assert_eq!(stream.ts(), &[10, 40]);
249 assert_eq!(stream.ps(), &[true, false]);
250 assert_eq!(stream.sensor_size(), (4, 3));
251 }
252
253 #[test]
254 fn to_array2_round_trips_columns_in_xytp_order() {
255 let stream =
256 EventStream::from_array2(ndarray::array![[1, 2, 100, 1], [3, 0, 250, 0]], 4, 3, 0.001);
257 let events = stream.to_array2();
258
259 assert_eq!(events.dim(), (2, 4));
260 assert_eq!(events.row(0).to_vec(), vec![1, 2, 100, 1]);
261 assert_eq!(events.row(1).to_vec(), vec![3, 0, 250, 0]);
262 }
263}