evt3 0.4.0

Fast EVT3 (EVT 3.0) decoder library for Prophesee and Metavision event cameras. Decodes .raw and HDF5 event data for event-based vision and neuromorphic research.
Documentation

evt3 โ€” fast EVT3 decoder for Prophesee event cameras

CI PyPI crates.io docs.rs Python versions License: MIT

Read Prophesee and Metavision event camera recordings into NumPy, CSV, or Rust. evt3 decodes the EVT3 (EVT 3.0) encoding used by Prophesee event-based vision sensors, from .raw and optionally HDF5 files. It ships as a command-line tool, a Python package, and a Rust library.

1.62x faster than the optimized C++ reference in a like-for-like full-CSV benchmark, with byte-identical checked output.

Built for event-based vision, neuromorphic engineering, and DVS data analysis when you need event streams as plain x, y, p, t arrays without installing a full camera SDK.

pip install evt3         # Python + NumPy
cargo install evt3-cli   # command-line tool
cargo add evt3           # Rust library

Features

  • ๐Ÿš€ High Performance - 55M events/second for Python decode-only and 1.62x faster than C++ for full CSV output
  • ๐Ÿ“ฆ Multiple Interfaces - CLI tool, Python bindings, Rust library
  • ๐Ÿ NumPy-native Python - stable Events arrays for analysis without clone-on-access surprises
  • ๐Ÿ”ญ AugurRS Ingress - publish decoded or transformed NumPy event arrays into AugurRS for interactive preview, 3D inspection, viewer tools, and plugin workflows
  • ๐Ÿงช Optional HDF5 Input - .h5 and .hdf5 support behind a cargo feature
  • โœ… Validated - Checked CSV output matches the C++ reference byte for byte
  • ๐Ÿ”ง Customizable - Configurable output field order

Quick Start

Install with Cargo

cargo install evt3-cli

The evt3-cli crate installs a binary named evt3. To use the decoder as a Rust library instead, depend on the evt3 crate:

cargo add evt3

One-Line Install (Linux/macOS)

curl -sSL https://raw.githubusercontent.com/muthmann/evt3/main/install.sh | bash

This downloads the binary to ~/.local/bin/evt3. You may need to add it to your PATH:

# Add to ~/.bashrc or ~/.zshrc
export PATH="$HOME/.local/bin:$PATH"

Download Pre-built Binary

Download from Releases:

Platform Binary
Linux x64 evt3-linux-x64
Linux ARM64 evt3-linux-arm64
macOS Intel evt3-macos-x64
macOS Apple Silicon evt3-macos-arm64
Windows evt3-windows-x64.exe
# Example for macOS Apple Silicon
curl -LO https://github.com/muthmann/evt3/releases/latest/download/evt3-macos-arm64
chmod +x evt3-macos-arm64
./evt3-macos-arm64 recording.raw events.csv

Python Package

Using uv (recommended):

uv pip install evt3

Or with pip:

pip install evt3

Published wheels target CPython 3.9 through 3.14, including the free-threaded CPython 3.14 build.

Note: The pip package supports .raw files only. HDF5 (.h5/.hdf5) requires building from source โ€” see HDF5 Inputs below.

Build from Source

# Clone repository
git clone https://github.com/muthmann/evt3.git
cd evt3

# Build CLI (requires Rust)
cargo build --release

# The binary is at: ./target/release/evt3
./target/release/evt3 recording.raw events.csv

# Optional HDF5 support
HDF5_DIR="$(brew --prefix hdf5)" cargo build --release -p evt3-cli --features hdf5

# Optional: Install to PATH
cp target/release/evt3 ~/.local/bin/

# Build Python package (requires Python 3.9+, uv + Rust 1.83+)
cd evt3-python
uv venv
uv pip install maturin
source .venv/bin/activate
maturin develop --release

Usage

CLI

# Decode to CSV (default: x,y,p,t)
evt3 recording.raw events.csv

# Timestamp-first format
evt3 recording.raw events.csv --format "t,x,y,p"

# Binary output (more efficient)
evt3 recording.raw events.bin

# Include trigger events
evt3 recording.raw events.csv --triggers triggers.csv

# Quiet mode
evt3 recording.raw events.csv --quiet

Python

import evt3
import numpy as np

# Decode a .raw or .h5 file โ€” format is auto-detected by extension
events = evt3.decode_file("recording.raw")
events = evt3.decode_file("recording.h5")   # requires hdf5 feature at build time
print(f"Decoded {len(events):,} events")
print(f"Sensor: {events.sensor_width}x{events.sensor_height}")

# Access as NumPy arrays (zero-copy)
x = events.x          # np.ndarray[uint16]
y = events.y          # np.ndarray[uint16]
p = events.polarity   # np.ndarray[uint8]
t = events.timestamp  # np.ndarray[uint64] (microseconds)

# Repeated access returns stable array objects
assert events.x is events.x
assert events.y is events.y
assert events.p is events.p
assert events.t is events.t

# Basic analysis
print(f"Duration: {(t[-1] - t[0]) / 1e6:.2f} seconds")
print(f"Event rate: {len(events) / ((t[-1] - t[0]) / 1e6):.0f} events/sec")

# Create pandas DataFrame
import pandas as pd
df = pd.DataFrame(events.to_dict())

# Existing decode_file code stays unchanged and now uses the optimized
# columnar decoder internally. Process bounded batches when the full recording
# does not need to stay in memory:
for batch in evt3.decode_file_batches("recording.raw", batch_bytes=8 << 20):
    process(batch.x, batch.y, batch.p, batch.t)

# Preserve external trigger events in the bounded-memory workflow:
for events, triggers in evt3.decode_file_batches_with_triggers("recording.raw"):
    process(events, triggers.timestamp, triggers.id, triggers.value)

# Preserve decoder state across arbitrary live-input chunk borders:
decoder = evt3.Decoder(sensor_width=1280, sensor_height=720)
for raw_chunk in camera_chunks:
    process(decoder.feed(raw_chunk))
decoder.finish()

Python To AugurRS

evt3 can publish decoded or transformed NumPy event arrays into a running AugurRS session. This makes Python a lightweight analysis and filtering environment while AugurRS provides the interactive event-camera application: live-style preview, 3D raw-event inspection, viewer tools, exports, and plugins.

import evt3

events = evt3.decode_file("recording.raw")

# Optional Python-side filtering or analysis.
x = events.x
y = events.y
p = events.p
t = events.t

evt3.augur.publish_events(
    x=x,
    y=y,
    p=p,
    t=t,
    geometry=events.sensor_size,
    name="recording-analysis-window",
)

You can also create an Events container from existing NumPy arrays:

events = evt3.Events.from_arrays(
    x=x,
    y=y,
    p=p,
    t=t,
    geometry=(1280, 720),
    copy=False,
)

evt3.augur.publish_events(events, name="filtered-events")

For repeated sends, reuse the loopback session:

with evt3.augur.connect() as augur:
    augur.publish_events(events, name="raw")
    augur.publish_events(filtered_events, name="filtered")

The first ingress stage is deliberately copy-based and bounded: event chunks are packed into AugurRS' 14-byte packed_xypt_v1 decoded-event transport and sent over loopback TCP. The connector validates dtype, shape, geometry, and timestamp ordering before sending so mistakes fail close to the Python code.

Rust Library

cargo add evt3
use evt3::Evt3Decoder;

let mut decoder = Evt3Decoder::new();
let result = decoder.decode_file("recording.raw")?;

println!("Decoded {} events", result.cd_events.len());
for event in result.cd_events.iter().take(10) {
    println!("x={}, y={}, p={}, t={}", 
        event.x, event.y, event.polarity, event.timestamp);
}

For live camera pipelines or embedded integrations, you can stream raw USB packet bytes directly into the decoder without converting to Vec<u16> first:

use evt3::Evt3Decoder;

let mut decoder = Evt3Decoder::new();
let mut cd_events = Vec::new();
let mut trigger_events = Vec::new();

for chunk in usb_packet_chunks {
    decoder.decode_bytes(chunk, &mut cd_events, &mut trigger_events)?;
}

decoder.finish_stream()?;

This keeps evt3 usable in incremental preview paths while preserving the existing file and word-based APIs.

Notes:

  • decode_buffer still expects 16-bit EVT3 words, not raw bytes.
  • decode_bytes expects little-endian EVT3 payload bytes and can be called with odd-sized chunks.
  • Call finish_stream() only when the stream is complete so a trailing half-word is reported as an error instead of being buffered for the next chunk.
  • .h5 and .hdf5 decoding is available when the crate or binary is built with the hdf5 feature.
  • decode_file remains source-compatible and returns the same Events API. It now decodes directly into NumPy's columnar layout and releases the Python GIL.
  • decode_file_batches is the bounded-memory option for large recordings. Use decode_file_batches_with_triggers when external trigger edges are also required. The arrays in a batch remain valid after the iterator advances, but retaining all batches naturally retains the full recording.

HDF5 Inputs

Important: HDF5 support requires libhdf5 (a native C library) and is not included in pip install evt3 or pre-built CLI binaries. It must be built from source. See docs/features/hdf5-file-support.md for the full limitations table.

# macOS
brew install hdf5
HDF5_DIR="$(brew --prefix hdf5)" cargo build --release -p evt3-cli --features hdf5
./target/release/evt3 recording.h5 events.csv

# Ubuntu / Debian
sudo apt install libhdf5-dev
cargo build --release -p evt3-cli --features hdf5
./target/release/evt3 recording.h5 events.csv

Most Prophesee HDF5 files use the ECF compression codec, which requires an additional runtime plugin:

# Build and install the ECF plugin (one-time, macOS/Linux/Windows)
./scripts/install-ecf-plugin.sh

# Then set the plugin path before running
export HDF5_PLUGIN_PATH="$HOME/.local/share/hdf5/plugin"
./target/release/evt3 recording.h5 events.csv

Notes:

  • Builds without --features hdf5 return a clear error rather than silently failing.
  • Real-data integration tests that skip still show as ok. Run with -- --show-output to see [SKIP] reasons.
  • Full plugin and dependency documentation: docs/features/hdf5-file-support.md

Benchmarks

Tested on Apple Silicon macOS with laser.raw (325 MB, 116,300,447 events). Both CLI implementations decoded the complete file, formatted the same CSV, and wrote it to /dev/null. Each mean uses five alternating measured runs after one warm-up per implementation.

Decoder Mean time Events/sec Speedup
Rust CLI 7.414 s 15.69M/s 1.62x
C++ reference (-O3 -DNDEBUG) 12.028 s 9.67M/s 1.00x

An instrumented run measured 63.3 MB maximum RSS for Rust and 28.3 MB for C++. Rust is faster in this workload; the C++ reference uses less memory. The CSV outputs had the same SHA-256 hash on an 8-MiB input prefix. Python's 2.108-second full-memory decode is reported separately because it does not format CSV and is not directly comparable with this table.

Run benchmarks yourself:

cargo bench
python benchmarks/benchmark.py --csv-comparison-only --iterations 5

Output Formats

CSV

Human-readable, with optional geometry header:

%geometry:1280,720
642,481,1,10960097
783,415,1,10960139
...

Binary (.bin)

Efficient packed format for programmatic access:

  • 8-byte magic header: EVT3BIN\0
  • 24-byte metadata: version, width, height, event count
  • Events: 14 bytes each (x:u16, y:u16, polarity:u8, pad:u8, timestamp:u64)

EVT 3.0 Format

EVT 3.0 is a 16-bit vectorized event encoding from Prophesee. This decoder supports:

Event Type Code Description
EVT_ADDR_Y 0x0 Y coordinate
EVT_ADDR_X 0x2 Single event (X + polarity)
VECT_BASE_X 0x3 Base X for vectors
VECT_12 0x4 12-event vector
VECT_8 0x5 8-event vector
EVT_TIME_LOW 0x6 Lower 12 bits of timestamp
EVT_TIME_HIGH 0x8 Upper 12 bits of timestamp
EXT_TRIGGER 0xA External trigger

For full specification: Prophesee EVT 3.0 Documentation

Project Structure

evt3/
โ”œโ”€โ”€ evt3-core/       # Rust decoder library      -> crate `evt3`
โ”œโ”€โ”€ evt3-cli/        # Command-line tool         -> crate `evt3-cli`, binary `evt3`
โ”œโ”€โ”€ evt3-python/     # Python bindings (PyO3)    -> PyPI package `evt3`
โ”œโ”€โ”€ benchmarks/      # Performance benchmarks
โ””โ”€โ”€ test_data/       # Sample EVT3 files

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

Licensed under the MIT License - see LICENSE-MIT for details.

Acknowledgments

  • Prophesee for the EVT 3.0 format specification
  • The OpenEB project for the reference implementation