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        Ok(self.frame(counts, width, height))
38    }
39
40    /// Counting is the one kernel that is *exactly* the CPU's answer rather than close to it:
41    /// contributions are ones and the accumulator is an integer, so the sum is the same however
42    /// the invocations interleave.
43    fn generate_on(
44        &self,
45        stream: &EventStream,
46        device: crate::accel::Device,
47    ) -> Result<EventFrame, RepresentationError> {
48        if device == crate::accel::Device::Cpu {
49            return self.generate(stream);
50        }
51        let (width, height, length) = frame_len(stream, 1)?;
52        let cells = super::on_gpu(
53            stream,
54            &crate::accel::GpuDispatch {
55                entry: "count",
56                cells: length,
57                initial: 0,
58                bins: 1,
59                span_ms: 0.0,
60                fixed_one: 1.0,
61                window_ms: None,
62                needs_ages: false,
63            },
64        )?;
65        let counts = cells.iter().map(|cell| *cell as u64).collect();
66        Ok(self.frame(counts, width, height))
67    }
68}
69
70impl EventCount {
71    fn frame(&self, counts: Vec<u64>, width: usize, height: usize) -> EventFrame {
72        EventFrame {
73            data: if self.normalize {
74                EventFrameData::U8(normalize_u8(&counts))
75            } else {
76                EventFrameData::U64(counts)
77            },
78            channels: 1,
79            width,
80            height,
81            kind: RepresentationKind::Count,
82            channel_names: vec!["count".to_owned()],
83        }
84    }
85}
86
87/// Linearly rescales the counts so the busiest pixel maps to `u8::MAX` (rounded).
88fn normalize_u8(counts: &[u64]) -> Vec<u8> {
89    let maximum = counts.iter().copied().max().unwrap_or(0);
90    if maximum == 0 {
91        return vec![0; counts.len()];
92    }
93    counts
94        .iter()
95        .map(|&count| {
96            let scaled = count * u64::from(u8::MAX);
97            ((scaled + maximum / 2) / maximum) as u8
98        })
99        .collect()
100}
101
102#[cfg(test)]
103mod tests {
104    use ndarray::array;
105
106    use super::{EventCount, Representation};
107    use crate::{representation::EventFrameData, EventStream};
108
109    #[test]
110    fn sums_both_polarities_into_one_plane() {
111        let stream = EventStream::from_array2(
112            array![[0, 0, 1, 1], [0, 0, 2, 0], [1, 1, 3, 1]],
113            2,
114            2,
115            0.001,
116        );
117
118        let frame = EventCount::default().generate(&stream).unwrap();
119
120        assert_eq!(frame.shape(), (1, 2, 2));
121        assert_eq!(frame.data(), &EventFrameData::U64(vec![2, 0, 0, 1]));
122    }
123
124    #[test]
125    fn normalizes_the_busiest_pixel_to_full_scale() {
126        let stream = EventStream::from_array2(
127            array![[0, 0, 1, 1], [0, 0, 2, 0], [1, 0, 3, 1]],
128            2,
129            1,
130            0.001,
131        );
132
133        let frame = EventCount::new(true).generate(&stream).unwrap();
134
135        assert_eq!(frame.data(), &EventFrameData::U8(vec![255, 128]));
136    }
137
138    #[test]
139    fn rejects_out_of_bounds_events() {
140        let stream = EventStream::from_array2(array![[2, 0, 10, 1]], 2, 2, 0.001);
141
142        let error = EventCount::default().generate(&stream).unwrap_err();
143
144        assert_eq!(
145            error.to_string(),
146            "event coordinate (2, 0) exceeds sensor size 2x2"
147        );
148    }
149}