Polars Preprocessing Examples¶
This document provides examples for using evlib's Polars-based event representations. The functions build event camera representations as Polars expressions, so they run on the CPU Polars engine and on the cudf GPU engine (engine="gpu"). The GPU engine uses cudf-polars' own default memory resource, not CUDA managed memory (UVM); see Performance: GPU memory footprint for the measured numbers and when UVM is worth adding.
Overview¶
The evlib.representations module provides Polars-based implementations of common event camera representations. Each function returns a Polars DataFrame (long format) and accepts a Polars LazyFrame or DataFrame as input (not a file path; load with evlib.load_events first). Internally the work is expressed as Polars operations, so the engine is selectable via the engine argument.
How it executes¶
- Native groupby and aggregation rather than tensor indexing.
- Lazy evaluation that defers work until the result is collected.
- Optimised data types (Int16 coordinates, Int8 polarity) for cache locality.
- A selectable engine:
engine="auto"for CPU Polars,engine="gpu"for the cudf GPU engine (its default allocator, not CUDA managed memory).
For a benchmark of these representations against tonic (NumPy), see Benchmarks. On a single transfer-bound stream the CPU Polars engine is usually the fastest evlib path; the GPU path is for compute-heavy workloads or the largest files.
Available test datasets¶
data/slider_depth/events.txt: text format (about 22MB, 1.1M events)data/eTram/h5/: HDF5 format eTram samples
Loaded event schema¶
load_events returns a Polars LazyFrame. The schema is t as a Duration (microseconds), x and y as Int16, and polarity as Int8 (-1 or +1).
import evlib
events = evlib.load_events("data/slider_depth/events.txt")
df = events.collect()
print(df.schema)
print(f"Loaded {len(df):,} events")
Voxel grid¶
create_voxel_grid builds a tonic-validated voxel grid with bilinear temporal interpolation over the whole recording.
import evlib
import evlib.representations as evr
events = evlib.load_events("data/slider_depth/events.txt")
voxel = evr.create_voxel_grid(events, height=480, width=640, n_time_bins=5)
print(f"Voxel grid entries: {len(voxel)}")
print(voxel.columns)
To run it on the GPU, pass engine="gpu":
import evlib
import evlib.representations as evr
events = evlib.load_events("data/slider_depth/events.txt")
voxel_gpu = evr.create_voxel_grid(events, height=480, width=640, n_time_bins=5, engine="gpu")
print(f"Voxel grid entries (GPU): {len(voxel_gpu)}")
Event frame¶
create_event_frame matches tonic's to_frame_numpy(n_time_bins=...): the whole recording is sliced into equal-width time bins and events are counted per (polarity, y, x) per bin.
import evlib
import evlib.representations as evr
events = evlib.load_events("data/slider_depth/events.txt")
frame = evr.create_event_frame(events, height=480, width=640, n_time_bins=10)
print(f"Event frame entries: {len(frame)}")
Time surface¶
create_time_surface builds a tonic-validated HOTS time surface (Lagorce et al. 2016). dt is the slice window and tau the decay constant, both in microseconds.
import evlib
import evlib.representations as evr
events = evlib.load_events("data/slider_depth/events.txt")
surface = evr.create_time_surface(
events, height=480, width=640, dt=10000.0, tau=50000.0
)
print(f"Time surface entries: {len(surface)}")
Stacked histogram¶
create_stacked_histogram bins events into fixed-duration windows and counts per (time_bin, polarity, y, x). This is a general representation; for the RVT-compatible stacked histogram expected by the RVT detection pipeline, use evlib.rvt (see the RVT preprocessing section below).
import evlib
import evlib.representations as evr
events = evlib.load_events("data/slider_depth/events.txt")
hist = evr.create_stacked_histogram(
events, height=480, width=640, bins=10, window_duration_ms=50.0
)
print(f"Stacked histogram entries: {len(hist)}")
RVT-compatible preprocessing¶
For the exact RVT stacked-histogram preprocessing, use evlib.rvt.process_sequence. It has a backend argument with four options: "polars" (CPU or cudf GPU), "rust" (Rust dense scatter-add, CPU), "cuda" (custom CUDA scatter-add kernel, NVIDIA GPU), and "metal" (Metal scatter-add kernel, Apple Silicon GPU). The native kernels are also exposed directly as evlib.representations_rs.stacked_histogram_dense, stacked_histogram_dense_cuda and stacked_histogram_dense_metal.
On the gen4_1mpx validation set the CUDA backend reaches parity-plus with the RVT torch-GPU reference (283.6s versus 286.3s) and the Rust CPU backend is 1.32x faster than the RVT torch-CPU reference. See Benchmarks for the full table and plots.
Filtering¶
Use evlib.filtering to filter events before building a representation. The helpers operate on the LazyFrame so the work stays lazy and GPU-collectable.
import evlib
import evlib.filtering as evf
events = evlib.load_events("data/slider_depth/events.txt")
positive = evf.filter_by_polarity(events, polarity=1)
roi = evf.filter_by_roi(events, x_min=100, x_max=500, y_min=50, y_max=400)
print(f"Positive events: {len(positive.collect()):,}")
print(f"ROI events: {len(roi.collect()):,}")
Batch processing¶
Process multiple files by looping over a glob pattern and writing each result to Parquet.
import evlib
import evlib.representations as evr
import glob
from pathlib import Path
def batch_voxel_grids(input_pattern, output_dir):
for input_file in glob.glob(input_pattern):
output_file = f"{output_dir}/{Path(input_file).stem}_voxel.parquet"
events = evlib.load_events(input_file)
voxel = evr.create_voxel_grid(events, height=480, width=640, n_time_bins=5)
voxel.write_parquet(output_file)
print(f"Processed {input_file} -> {output_file} ({len(voxel)} entries)")
Next steps¶
- Benchmark with your own data using the harnesses described in Benchmarks.
- Tune
n_time_bins,window_duration_ms,dtandtaufor your use case. - Use
engine="gpu"for compute-heavy workloads or the largest files, and the default CPU engine for single transfer-bound calls.