Skip to main content

eventcv_core/representation/
count.rs

1use super::{
2    event_index, frame_len, EventFrame, EventFrameData, Representation, RepresentationError,
3    RepresentationKind,
4};
5use crate::EventStream;
6
7/// Single-channel event-count image: the number of events at each pixel, both polarities
8/// summed. Unlike [`super::Polarity`] (per-polarity planes) this collapses polarity into
9/// one intensity map — the plainest "how much happened here" frame. Counts accumulate as
10/// `u64`; `normalize` rescales the plane into `u8` for display.
11#[derive(Clone, Copy, Debug, Default)]
12pub struct EventCount {
13    normalize: bool,
14}
15
16impl EventCount {
17    pub fn new(normalize: bool) -> Self {
18        Self { normalize }
19    }
20
21    pub fn is_normalized(&self) -> bool {
22        self.normalize
23    }
24}
25
26impl Representation for EventCount {
27    type Output = EventFrame;
28
29    fn generate(&self, stream: &EventStream) -> Result<EventFrame, RepresentationError> {
30        let (width, height, length) = frame_len(stream, 1)?;
31        let mut counts = vec![0_u64; length];
32
33        for event in stream.iter() {
34            counts[event_index(event, width, height)?] += 1;
35        }
36
37        let data = if self.normalize {
38            EventFrameData::U8(normalize_u8(&counts))
39        } else {
40            EventFrameData::U64(counts)
41        };
42
43        Ok(EventFrame {
44            data,
45            channels: 1,
46            width,
47            height,
48            kind: RepresentationKind::Count,
49            channel_names: vec!["count".to_owned()],
50        })
51    }
52}
53
54/// Linearly rescales the counts so the busiest pixel maps to `u8::MAX` (rounded).
55fn normalize_u8(counts: &[u64]) -> Vec<u8> {
56    let maximum = counts.iter().copied().max().unwrap_or(0);
57    if maximum == 0 {
58        return vec![0; counts.len()];
59    }
60    counts
61        .iter()
62        .map(|&count| {
63            let scaled = count * u64::from(u8::MAX);
64            ((scaled + maximum / 2) / maximum) as u8
65        })
66        .collect()
67}
68
69#[cfg(test)]
70mod tests {
71    use ndarray::array;
72
73    use super::{EventCount, Representation};
74    use crate::{representation::EventFrameData, EventStream};
75
76    #[test]
77    fn sums_both_polarities_into_one_plane() {
78        let stream = EventStream::from_array2(
79            array![[0, 0, 1, 1], [0, 0, 2, 0], [1, 1, 3, 1]],
80            2,
81            2,
82            0.001,
83        );
84
85        let frame = EventCount::default().generate(&stream).unwrap();
86
87        assert_eq!(frame.shape(), (1, 2, 2));
88        assert_eq!(frame.data(), &EventFrameData::U64(vec![2, 0, 0, 1]));
89    }
90
91    #[test]
92    fn normalizes_the_busiest_pixel_to_full_scale() {
93        let stream = EventStream::from_array2(
94            array![[0, 0, 1, 1], [0, 0, 2, 0], [1, 0, 3, 1]],
95            2,
96            1,
97            0.001,
98        );
99
100        let frame = EventCount::new(true).generate(&stream).unwrap();
101
102        assert_eq!(frame.data(), &EventFrameData::U8(vec![255, 128]));
103    }
104
105    #[test]
106    fn rejects_out_of_bounds_events() {
107        let stream = EventStream::from_array2(array![[2, 0, 10, 1]], 2, 2, 0.001);
108
109        let error = EventCount::default().generate(&stream).unwrap_err();
110
111        assert_eq!(
112            error.to_string(),
113            "event coordinate (2, 0) exceeds sensor size 2x2"
114        );
115    }
116}