Transforms & representations

EventCV transforms return a new stream, so they chain like a pipeline. Every transform and representation is available both as a method (stream.flip_x()) and as an OpenCV-style free function (ecv.flip_x(stream)).

import eventcv as ecv
import numpy as np
from pathlib import Path

# Point PATH at your own recording (.npz / .hdf5 / .bag / .txt / .aedat / .dat).
# Here we locate the small test fixture bundled with the EventCV source.
def _fixture(rel="data/test/example.npz"):
    for base in (Path.cwd(), *Path.cwd().parents):
        if (base / rel).exists():
            return str(base / rel)
    raise FileNotFoundError(rel)

PATH = _fixture()

stream = ecv.load(PATH)
len(stream), stream.sensor_size
(106295, (640, 480))

Chaining transforms

Geometry, temporal, and polarity ops compose; each returns a new stream.

pipe = (stream
        .crop(0, 0, 320, 240)   # x, y, width, height
        .hot_pixel_filter()     # drop noisy always-on pixels
        .flip_x())
print("events:", len(stream), "->", len(pipe))
print("sensor size:", pipe.sensor_size)
events: 106295 -> 19859
sensor size: (320, 240)

Two spellings, one operation

The functional form mirrors the methods exactly, so pick whichever reads better.

a = stream.flip_x().voxel(bins=5).numpy()
b = ecv.voxel(ecv.flip_x(stream), bins=5).numpy()
np.array_equal(a, b)
True

Representations

Render the pipeline output as dense tensors ready for NumPy / PyTorch.

for name, frame in [("count", pipe.count()),
                    ("voxel(bins=5)", pipe.voxel(bins=5)),
                    ("time surface", pipe.tsurf())]:
    arr = frame.numpy()
    print(f"{name:16} {str(arr.shape):18} {arr.dtype}")
count            (1, 240, 320)      uint8
voxel(bins=5)    (5, 240, 320)      float32
time surface     (2, 240, 320)      float32

As a PyTorch dataset

open(..., repr=…) gives the reader a default representation. Every slice remembers it, so reader.slice(n).view() renders that representation instead of raw polarity (pass a name to view(...) to override). It also makes the reader a map-style dataset: len(ds) frames, ds[i] a [C, H, W] array, and ds.batch(idx) a [B, C, H, W] stack — no PyTorch needed to produce the arrays.

ds = ecv.open(PATH, dt_ms=20, repr="count")
print("frames:", len(ds))
print("ds[0]:", ds[0].shape, ds[0].dtype)
print("batch([0, 1]):", ds.batch([0, 1]).shape)
frames: 3
ds[0]: (1, 480, 640) uint8
batch([0, 1]): (2, 1, 480, 640)

With PyTorch installed, hand the reader straight to a DataLoader. With a repr set, slices are dense arrays that collate into a [B, C, H, W] tensor automatically:

import torch

ds = ecv.open("recording.hdf5", dt_ms=30, repr="count")
loader = torch.utils.data.DataLoader(ds, batch_size=32, shuffle=True)
for batch in loader:      # batch: [B, C, H, W]
    ...

Without a repr, reader[i] is a raw EventStream. Those are sparse and variable-length, so they can’t stack into a tensor — pass eventcv.collate to batch them as a list instead:

reader = ecv.open("recording.hdf5", dt_ms=30)
loader = torch.utils.data.DataLoader(reader, batch_size=32, collate_fn=ecv.collate)
for batch in loader:      # batch: list[EventStream]
    batch[0].view()