Skip to main content

eventcv_core/representation/
tencode.rs

1use super::{
2    age_ms, event_index, frame_len, reference_time, validate_positive, EventFrame, EventFrameData,
3    Representation, RepresentationError, RepresentationKind,
4};
5use crate::EventStream;
6
7/// Per-pixel winner states for the scratch column: no event in the window, or the polarity of the
8/// latest one.
9const NOTHING: u8 = 0;
10const POSITIVE: u8 = 1;
11const NEGATIVE: u8 = 2;
12
13#[derive(Clone, Copy, Debug)]
14pub struct Tencode {
15    window_ms: f64,
16}
17
18impl Tencode {
19    pub fn new(window_ms: f64) -> Self {
20        Self { window_ms }
21    }
22}
23
24impl Default for Tencode {
25    fn default() -> Self {
26        Self::new(30.0)
27    }
28}
29
30impl Representation for Tencode {
31    type Output = EventFrame;
32
33    fn generate(&self, stream: &EventStream) -> Result<EventFrame, RepresentationError> {
34        validate_positive(self.window_ms, "window_ms")?;
35        let (width, height, length) = frame_len(stream, 3)?;
36        let plane_len = width * height;
37        // Per-pixel winner, split into two lean columns rather than one `Vec<Option<(u64, usize,
38        // bool)>>`: at 1280×720 that packing zeroed 22 MB of scratch per frame, which dominated the
39        // cost of a live window. `state` doubles as the "seen" flag the `Option` used to carry.
40        let mut latest_t = vec![0_u64; plane_len];
41        let mut latest_state = vec![NOTHING; plane_len];
42        let mut values = vec![0_u8; length];
43
44        if let Some(reference) = reference_time(stream) {
45            for event in stream.iter() {
46                if age_ms(stream, reference, event.timestamp) > self.window_ms {
47                    continue;
48                }
49                let index = event_index(event, width, height)?;
50                // Events are visited in stream order, so `>=` keeps the last event of a timestamp
51                // tie — the same winner an explicit order comparison picked.
52                if latest_state[index] == NOTHING || event.timestamp >= latest_t[index] {
53                    latest_t[index] = event.timestamp;
54                    latest_state[index] = if event.polarity { POSITIVE } else { NEGATIVE };
55                }
56            }
57
58            for index in 0..plane_len {
59                match latest_state[index] {
60                    POSITIVE => values[index] = u8::MAX,
61                    NEGATIVE => values[2 * plane_len + index] = u8::MAX,
62                    _ => continue,
63                }
64                values[plane_len + index] = (255.0
65                    * age_ms(stream, reference, latest_t[index])
66                    / self.window_ms)
67                    .round()
68                    .clamp(0.0, 255.0) as u8;
69            }
70        }
71
72        Ok(EventFrame {
73            data: EventFrameData::U8(values),
74            channels: 3,
75            width,
76            height,
77            kind: RepresentationKind::Tencode,
78            channel_names: vec![
79                "positive".to_owned(),
80                "age".to_owned(),
81                "negative".to_owned(),
82            ],
83        })
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use ndarray::array;
90
91    use super::{Representation, Tencode};
92    use crate::{representation::EventFrameData, EventStream};
93
94    #[test]
95    fn encodes_latest_polarity_and_age() {
96        let stream = EventStream::from_array2(
97            array![[0, 0, 10_000, 1], [1, 0, 30_000, 1], [1, 0, 30_000, 0]],
98            2,
99            1,
100            0.001,
101        );
102
103        let frame = Tencode::new(30.0).generate(&stream).unwrap();
104
105        assert_eq!(
106            frame.data(),
107            &EventFrameData::U8(vec![255, 0, 170, 0, 0, 255])
108        );
109    }
110}