EVT 2.0 and 3.0¶
frames2py.adapters.evt.open(path, *, sensor_size=None, batch_size=None) reads Prophesee
RAW files in EVT 2.0 or EVT 3.0 and yields their CD (contrast detection) events. The decoder
is Frames2Py's own, in NumPy; the evt extra installs nothing and exists so the install
command stays the same if that ever changes.
import frames2py
from frames2py.adapters import evt
# The header of this EVT 2.0 file has no geometry, so sensor_size is required.
# An Accumulator's windowed kernels span everything since construction, so the totals
# below cover the whole file however the reader batches it.
with evt.open("sparklers_100k.evt2.raw", sensor_size=(640, 480), batch_size=10_000) as reader:
acc = frames2py.Accumulator(reader.sensor_size, "polarity")
for events in reader:
acc.accumulate(events)
frame = acc.read()
print("sensor:", reader.sensor_size, "frame:", frame.shape)
print("events:", int(frame.sum()), "OFF:", int(frame[..., 0].sum()), "ON:", int(frame[..., 1].sum()))
print("watermark:", acc.watermark)
sensor: (640, 480) frame: (480, 640, 2)
events: 100000 OFF: 68073 ON: 31927
watermark: 913728417
The file in this example is the first 100,000 CD events of Prophesee's CC0 sparklers.raw
sample, recorded with a 640x480 Gen3.0 sensor and committed to the repository as
tests/data/sparklers_100k.evt2.raw. Its header has no geometry line, so the example passes
sensor_size. When a header lacks geometry, take the sensor's resolution from the camera's
specification (for example 640x480 for Prophesee Gen3.0 sensors, 1280x720 for the Sony
IMX636); Frames2Py never guesses it. The Adapters page explains
why the example counts with an Accumulator; Seeing every
publication has the same count through an Engine.
The header¶
Header lines start with %. A % end line ends the header; otherwise it ends at the first
line that doesn't start with %.
- Version.
% evt 2.0/% evt 3.0, or% format EVT2/% format EVT3, picks the decoder. A missing version, any other version (EVT 2.1 included), anevtand aformatline that disagree, or any header keyword given two different values raiseValueErrorfromopen(). - Geometry comes from
% geometry WxHor the format line'swidth=andheight=. A header without geometry (like the example's) needssensor_size, and nothing can check it; when the header has geometry, asensor_sizeyou pass must match it.
Only CD events are decoded. Triggers and monitoring words are skipped.
Timestamps¶
Timestamps are the sensor clock as recorded, in microseconds. They are not shifted to start at the first TIME_HIGH, and nothing is sorted or clamped. The decoder unwraps the formats' narrow time counters into continuous 64-bit microseconds, by these rules:
EVT 2.0. An event's time is the last TIME_HIGH (time bits 33..6) joined to its own 6 low
bits. The TIME_HIGH counter wraps after 2^34 µs (about 4.8 hours). A TIME_HIGH lower than the
previous one by at least (2^28 - 1) × 64 − 10000 µs is read as a wrap, and 2^34 µs is added
from then on; any other backward step is kept as a backward step. This is OpenEB 5.2.0's rule.
CD events before the first TIME_HIGH are dropped, since their time is unknown.
EVT 3.0. An event's time is TIME_HIGH (bits 23..12) joined to TIME_LOW (bits 11..0), plus 2^24 µs (about 16.8 s) for each counter wrap.
- A TIME_HIGH step from 4095 to 0, or any backward step of more than 3840 units, is a wrap. Every other backward step is kept, and you see it as a backward jump in time.
- OpenEB 5.2.0's decoder reads any backward step of 2048 units or more as a wrap, while its own validator reports steps of 2048 to 3840 as violations. Frames2Py follows the validator: reading such a step as a wrap would turn a visible backward jump into a silent 16.8 s forward one. The format page says each TIME_HIGH value is sent 256 times, every 16 µs, so a genuine wrap is 4095 to 0; this rule still accepts one across which up to 254 TIME_HIGH values were lost.
- A TIME_HIGH that changes the value sets TIME_LOW to 0 until the next TIME_LOW. A TIME_LOW lower than the one before it is kept (the format page allows it across event sources).
- Decoding starts at the first TIME_HIGH. After it, events before the first EVT_ADDR_Y, and vector words before the first VECT_BASE_X, are dropped, as OpenEB does: their row or base is unknown.
EVT 3.0 vectors and rows¶
- Vector words follow the format page: each VECT_12 or VECT_8 emits its set bits at the current base, then moves the base on by 12 or 8. No 12 + 12 + 8 grouping is assumed and nothing is dropped near the right edge (OpenEB's default decoder does both).
- Rows of type 0x1, which the format page reserves, emit nothing, and vector words inside them don't move the base (OpenEB's behaviour).
- A row at or beyond the sensor height is decoded and yielded; the Accumulator or Engine counts its events as out of bounds.
- A vector event whose x would exceed 65535, which only a corrupt stream produces, raises
ValueError.
Streaming¶
The file is read 1 MiB at a time. A read can end anywhere: a partial word carries into the
next read, so the events never depend on the read size. A partial word at the end of the
file is ignored, so a truncated file yields a prefix of the full file's events. With
batch_size=None, EVT 2.0 yields one array per 1 MiB read and EVT 3.0 one per 32,768 words
(64 KiB).
Agreement with OpenEB¶
On the four real EVT recordings in the test registry, the output equals OpenEB 5.2.0's
default decode event for event. None of them contains a case where the rules above differ
from OpenEB's. The cases where they do differ, and the cases the format page leaves open
where Frames2Py follows OpenEB, are pinned by 40 crafted inputs with OpenEB 5.2.0's recorded
output for each (tests/data/evt_golden/ in the repository).