eventcv_core/transform.rs
1//! Event-domain geometry and stream algebra — OpenCV-style transforms that operate on the
2//! sparse [`EventStream`] itself (not a dense frame), preserving per-event timestamps. Every
3//! op returns a **new** stream so calls chain. Coordinates are remapped and rounded — there is
4//! no pixel interpolation — and out-of-bounds events are dropped (downscale therefore lets
5//! several events share a pixel, which is lossless: representations handle the collisions).
6//!
7//! Frame-domain resize lives separately in [`crate::image`]; the two are complementary.
8
9mod algebra;
10mod spatial;
11mod temporal;
12
13use crate::{EventStream, EventStreamBuilder};
14
15impl EventStream {
16 /// Builds a new stream over a `(width, height)` grid by mapping each event through `f`.
17 /// `f` returns `None` to drop an event, or new `(x, y, t, p)` as `i64` coordinates;
18 /// events landing outside the grid (including negative coordinates) are dropped before the
19 /// `u16` cast. The single construction path shared by the geometric transforms.
20 pub(crate) fn remap(
21 &self,
22 width: usize,
23 height: usize,
24 f: impl Fn(i64, i64, i64, bool) -> Option<(i64, i64, i64, bool)>,
25 ) -> EventStream {
26 let mut builder =
27 EventStreamBuilder::with_capacity(width, height, self.timestamp_scale_ms(), self.len());
28 let (xs, ys, ts, ps) = (self.xs(), self.ys(), self.ts(), self.ps());
29 for index in 0..self.len() {
30 let Some((x, y, t, p)) = f(
31 i64::from(xs[index]),
32 i64::from(ys[index]),
33 ts[index],
34 ps[index],
35 ) else {
36 continue;
37 };
38 if (0..width as i64).contains(&x) && (0..height as i64).contains(&y) {
39 builder.push(x as u16, y as u16, t, p);
40 }
41 }
42 builder.build()
43 }
44}