eventcv_core/representation/
time_surface.rs1use 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 TimeSurface {
9 tau_ms: f64,
10}
11
12impl TimeSurface {
13 pub fn new(tau_ms: f64) -> Self {
14 Self { tau_ms }
15 }
16}
17
18impl Default for TimeSurface {
19 fn default() -> Self {
20 Self::new(30.0)
21 }
22}
23
24impl Representation for TimeSurface {
25 type Output = EventFrame;
26
27 fn generate(&self, stream: &EventStream) -> Result<EventFrame, RepresentationError> {
28 validate_positive(self.tau_ms, "tau_ms")?;
29 let (width, height, length) = frame_len(stream, 2)?;
30 let plane_len = width * height;
31 let track_touched = stream.len().saturating_mul(4) <= length;
43 let mut latest = vec![0_u64; length];
44 let mut seen = vec![false; length];
45 let mut touched = if track_touched {
46 Vec::with_capacity(stream.len())
47 } else {
48 Vec::new()
49 };
50
51 for event in stream.iter() {
52 let index =
53 event_index(event, width, height)? + if event.polarity { 0 } else { plane_len };
54 if !seen[index] {
55 seen[index] = true;
56 if track_touched {
57 touched.push(index);
58 }
59 latest[index] = event.timestamp;
60 } else if event.timestamp > latest[index] {
61 latest[index] = event.timestamp;
62 }
63 }
64
65 let mut values = vec![0_f32; length];
66 if let Some(reference) = reference_time(stream) {
67 if track_touched {
68 for index in touched {
69 values[index] =
70 (-age_ms(stream, reference, latest[index]) / self.tau_ms).exp() as f32;
71 }
72 } else {
73 for (index, value) in values.iter_mut().enumerate() {
74 if seen[index] {
75 *value =
76 (-age_ms(stream, reference, latest[index]) / self.tau_ms).exp() as f32;
77 }
78 }
79 }
80 }
81
82 Ok(EventFrame {
83 data: EventFrameData::F32(values),
84 channels: 2,
85 width,
86 height,
87 kind: RepresentationKind::TimeSurface,
88 channel_names: vec!["positive".to_owned(), "negative".to_owned()],
89 })
90 }
91
92 fn generate_on(
96 &self,
97 stream: &EventStream,
98 device: crate::accel::Device,
99 ) -> Result<EventFrame, RepresentationError> {
100 if device == crate::accel::Device::Cpu {
101 return self.generate(stream);
102 }
103 validate_positive(self.tau_ms, "tau_ms")?;
104 let (width, height, length) = frame_len(stream, 2)?;
105 let ages = super::on_gpu(
106 stream,
107 &crate::accel::GpuDispatch {
108 entry: "time_surface",
109 cells: length,
110 initial: i32::MAX,
111 bins: 2,
112 span_ms: self.tau_ms as f32,
113 fixed_one: 1.0,
114 window_ms: None,
115 needs_ages: true,
116 },
117 )?;
118 let scale = stream.timestamp_scale_ms();
119 let values = ages
120 .iter()
121 .map(|age| match *age {
122 i32::MAX => 0.0, age => (-(f64::from(age) * scale) / self.tau_ms).exp() as f32,
124 })
125 .collect();
126 Ok(EventFrame {
127 data: EventFrameData::F32(values),
128 channels: 2,
129 width,
130 height,
131 kind: RepresentationKind::TimeSurface,
132 channel_names: vec!["positive".to_owned(), "negative".to_owned()],
133 })
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use ndarray::array;
140
141 use super::{Representation, TimeSurface};
142 use crate::{representation::EventFrameData, EventStream};
143
144 #[test]
145 fn uses_latest_event_per_pixel_and_polarity() {
146 let stream = EventStream::from_array2(
147 array![[0, 0, 30_000, 1], [1, 0, 20_000, 0], [0, 0, 10_000, 1]],
148 2,
149 1,
150 0.001,
151 );
152
153 let frame = TimeSurface::new(10.0).generate(&stream).unwrap();
154 let EventFrameData::F32(values) = frame.data() else {
155 panic!("time surfaces must use float32 data");
156 };
157
158 assert_eq!(values[0], 1.0);
159 assert_eq!(values[1], 0.0);
160 assert!((values[3] - (-1.0_f32).exp()).abs() < 1e-6);
161 }
162
163 #[test]
166 fn a_timestamp_of_zero_is_an_event_not_an_empty_pixel() {
167 let stream = EventStream::from_array2(array![[0, 0, 0, 1]], 2, 1, 0.001);
168
169 let frame = TimeSurface::new(10.0).generate(&stream).unwrap();
170 let EventFrameData::F32(values) = frame.data() else {
171 panic!("time surfaces must use float32 data");
172 };
173
174 assert_eq!(values[0], 1.0); assert_eq!(values[1], 0.0); assert!(values[2..].iter().all(|&value| value == 0.0)); }
178}