OpenEB Conformance¶
tests/test_openeb_conformance.py checks that evlib's EVT2 and EVT3 binary decoders produce the same event stream as the reference decoders in OpenEB, Prophesee's own open-source SDK. It is a decode-correctness check, separate from the RVT preprocessing benchmark on the RVT Pipeline page: this harness only checks that the raw (x, y, polarity, t) tuples evlib reads out of a .raw file match what OpenEB reads out of the same file.
What "byte-identical decode" means¶
Both decoders are reduced to the same canonical form before comparison, defined in tests/conformance/canonical.py:
- Every event is a 4-tuple
(x, y, pol, t)withpolin{0, 1}andtan integer microsecond timestamp. - The events are sorted into a total order by
(t, x, y, pol), so timestamp ties do not make the comparison order-dependent. - The sorted events are packed into a fixed-width little-endian byte stream (
x:u16, y:u16, pol:u8, t:i64, no padding) and hashed with SHA-256.
"Byte-identical decode" means this SHA-256 digest matches between evlib's decode and the reference decoder's decode: every event, in the same canonical order, with the same coordinates, polarity and timestamp. The digest also records n_events, the sensor geometry, and the first and last 16 events (head/tail) in canonical order, so a mismatch can be localised without re-running both decoders.
evlib's side of the comparison is tests/conformance/evlib_stream.py:evlib_digest, which calls evlib.load_events(path, sort=False) and maps evlib's -1/+1 polarity to the canonical 0/1 encoding before hashing.
Committed reference digests¶
The reference digests live under tests/conformance/reference/, one JSON file per sample (80_balls.evt2.json, val_night_011.evt2.json, pedestrians.evt3.json, sample_data.aedat4.json, test-minimal.aedat4.json). Each file holds the canonical digest (n_events, geometry, stream_sha256, head, tail) plus provenance: for EVT2/EVT3 samples, the OpenEB decoder name and the lib/openeb commit the digest was generated against; for the AEDAT4 samples, the dv_processing oracle used instead (OpenEB does not decode AEDAT4).
These files are committed so the conformance test can run anywhere without needing a local OpenEB checkout, a C++ compiler, or the reference decoders at test time. tests/test_openeb_conformance.py only reads the committed JSON and decodes the sample with evlib; it never builds or runs OpenEB.
The following block computes the same canonical digest against the tracked 80_balls.raw EVT2 sample, to show the structure of what gets compared:
from tests.conformance.evlib_stream import evlib_digest
digest = evlib_digest("data/prophesee/samples/evt2/80_balls.raw")
print(f"n_events: {digest['n_events']}")
print(f"geometry: {digest['geometry']}")
print(f"stream_sha256: {digest['stream_sha256'][:16]}...")
print(f"first event (x, y, pol, t): {digest['head'][0]}")
Regenerating the digests¶
The digests are local-only to generate: tests/conformance/generate_reference.py compiles and runs the OpenEB standalone sample decoders (and, for AEDAT4, dv_processing) against a local lib/openeb checkout, so it needs that checkout and a C++ compiler, and it is never run in CI.
# Full diff against one sample: decode both sides, sort canonically, and report
# the first event index where evlib and the reference disagree.
.venv/bin/python tests/conformance/generate_reference.py --verify 80_balls.evt2
# Regenerate and overwrite every committed digest.
.venv/bin/python tests/conformance/generate_reference.py --all --update
--verify KEY decodes both sides in full and stops at the first mismatched event, which is the tool to reach for when test_evlib_matches_openeb_reference fails and the test's own head/tail diff is not enough to explain why. --all --update re-runs every sample in tests/conformance/canonical.py:SAMPLES and rewrites its reference JSON; --sample KEY [--update] does the same for one sample. Without --update, generate_reference.py refuses to overwrite a digest whose hash has changed, so a silent regression can't slip into the committed reference by accident.
What runs in CI versus locally¶
tests/test_openeb_conformance.py is parametrised over every entry in canonical.SAMPLES, and each case skips itself with pytest.skip when its sample file is absent, rather than failing:
| Sample | Format | Path | Tracked in git? | Runs in CI? |
|---|---|---|---|---|
80_balls.evt2 |
EVT2 | data/prophesee/samples/evt2/80_balls.raw |
yes | yes |
val_night_011.evt2 |
EVT2 | tests/data/eTram/raw/val_2/val_night_011.raw |
yes | yes |
pedestrians.evt3 |
EVT3 | data/prophesee/samples/evt3/pedestrians.raw |
no (gitignored) | no, skips |
sample_data.aedat4 |
AEDAT4 | lib/dv-processing/tests/io/test_files/sample_data.aedat4 |
no (gitignored) | no, skips |
test-minimal.aedat4 |
AEDAT4 | lib/dv-processing/tests/io/test_files/test-minimal.aedat4 |
no (gitignored) | no, skips |
The two EVT2 samples are tracked, so the gate always runs in CI. The EVT3 sample is gitignored, so its case skips in CI and only runs on a machine that has the file locally; the same applies to the two AEDAT4 samples, which live under the gitignored lib/dv-processing checkout. This keeps the CI conformance gate real (it always exercises the tracked EVT2 path) without requiring every contributor, or every CI runner, to carry the larger EVT3 and AEDAT4 fixtures.