API Reference¶
The Python API lives in the top-level eventcv package. Every operation exists both as
a method on a core type (EventStream, EventFrame,
EventReader, EventPointSet, Camera)
and as an OpenCV-style free function (listed under
Functional API below); the free functions are generated
from the methods, so the two forms stay in sync.
Loading & saving¶
- eventcv.load(path: str, *, sensor_size: tuple[int, int] | None = None, time_unit: str | None = None, order: str = 'txyp', topic: str | None = None, max_events: int | None = None, offset: float | None = None, offset_s: float | None = None, offset_ms: float | None = None, offset_us: float | None = None, offset_ns: float | None = None, keys: dict[str, str] | None = None) EventStream[source]¶
Load events from any supported file, detected by its extension.
Supported today:
.npz(N-ImageNet),.txt/.csv(e.g. EV-IMOt x y p),.bag(ROSdvs_msgs/EventArray),.hdf5/.h5,.aedat(AEDAT 2.0, jAER/DAVIS),.aedat4(AEDAT 4.0, iniVation DV),.dat(Prophesee CD events), and Prophesee EVT2 and EVT3.rawrecordings.A .raw file’s encoding comes from its header, and its sensor size from
% geometrywhen present — some recorders omit it, and the size is then derived from the events themselves, which is a tight bound on what fired rather than the physical sensor. Passsensor_sizeto override.An
.aedatfile’s geometry comes from the chip named in its header (the DAVIS family; DVS128 and older chips use a different address layout and are refused rather than misread); an.aedat4file’s comes from the stream description DV writes into its header. In both, the APS frames and IMU samples are not events and so are not returned here — read them withread_frames()andread_imu().sensor_sizeandtime_unitare auto-detected when omitted and only act as overrides: rosbags carry both in the message; HDF5/text infer the time unit from the timestamps (a fractional text value means seconds) and the resolution from the coordinate range. Passingsensor_sizefor HDF5 also skips that scan.time_unitisseconds/milliseconds/microseconds/nanoseconds(orauto);order(txyp/xytp) applies to headerless text.topicselects the rosbag topic (default/davis/left/events).offsetis an absolute timestamp in the file’s own time base — the same base asstream.numpy()[:, 2]: events before it are skipped, andmax_eventsthen caps how many are kept after it — together they read a window, handy for previewing a slice of a very large file. For a recording whose timestamps are epoch-based, pass the epoch time (e.g.offset=1_587_540_271_650);<= 0reads from the start. The bareoffsetis milliseconds;offset_s,offset_ms,offset_us, andoffset_nssay which unit you mean, and passing two of them raises.The x/y/t/p columns are found automatically, whatever they’re named or nested: an HDF5 file’s datasets are searched recursively and matched by synonym (
x,x_coordinates,u, …;t/timestamp/timestamps;polarity/pol; etc.), and a single compound/structured dataset with those fields is read too — so the common ROSdvs_msgslayout (events/{x_coordinates, y_coordinates, timestamps, polarities}) just works. A.csv/.txtfile with a header row is mapped by its column names (comma or whitespace separated). When detection can’t identify the columns it raises, listing what the file contains. Passkeysto name them explicitly:keys={"x": …, "y": …, "t": …, "p": …}— for HDF5 each value is a dataset path (ordataset/fieldto pick a compound field); for text a header name or 0-based column index.keysoverrides auto-detection (andorder).
- eventcv.from_numpy(events, *, sensor_size: tuple[int, int] | None = None, time_unit: str | None = None, order: str = 'xytp') EventStream[source]¶
Build an
EventStreamfrom an in-memory(N, 4)NumPy array.The constructor mirror of
EventStream.numpy():orderdefaults toxytp(the column layoutnumpy()emits, with timestamps in microseconds), soecv.from_numpy(stream.numpy(), time_unit="us")round-trips a stream. Passorder="txyp"for arrays in the commont x y pdataset layout. Any integer or float dtype is accepted; polarity is positive when its value is greater than zero (both0/1and-1/1conventions work).sensor_sizeandtime_unitare auto-detected when omitted, exactly likeload(): the sensor is the smallest grid holding every event, and the time unit is inferred from the timestamp span (fractional values mean seconds; the inference assumes a recording of at least ~1 s, so passtime_unitexplicitly for short arrays). Events outside an explicitsensor_sizeare dropped.Example:
events = np.array([[0, 0, 100, 1], [1, 2, 250, 0]]) # x y t p stream = ecv.from_numpy(events, time_unit="us") stream.count().numpy()
- eventcv.open(path: str, *, dt_ms: float | None = None, dt_s: float | None = None, dt_us: float | None = None, dt_ns: float | None = None, max_events: int | None = None, offset: float | None = None, offset_s: float | None = None, offset_ms: float | None = None, offset_us: float | None = None, offset_ns: float | None = None, repr: str | None = None, sensor_size: tuple[int, int] | None = None, time_unit: str | None = None, order: str = 'txyp', topic: str | None = None, hot_pixel_filter: bool = False, hot_pixel_std: float = 3.0, keys: dict[str, str] | None = None, downsample: int | None = None) EventReader[source]¶
Open a file for lazy slicing without loading it whole.
Where
load()is OpenCV’simread(read the entire stream eagerly),openis itsVideoCapture: it returns anEventReaderthat points at the original file and fetches a slice on demand. HDF5 binary-searches the on-disk timestamps; Prophesee EVT3.rawuses a sparse byte/time index. In both cases a slice of a multi-gigabyte recording is decoded on demand and the file is never fully materialised. Other formats are loaded once and sliced in memory.Pass
dt_msto treat the recording as a sequence of fixed-duration frames: the reader reportsn_slicesandreader.slice(n)returns then-th frame (reader[n]works too). Framenis measured from the recording start, so you never deal with absolute timestamps (which may be epoch-based).max_eventsis the event-count twin:open(path, max_events=10_000)makes each slice exactly 10 000 consecutive events (the last one may be shorter), which keeps the event rate per frame constant instead of the duration. The two are mutually exclusive — pass one or the other, not both. Without either, slice by explicit time/count window instead.Any timescale works. Every time argument comes in four units —
dt_s,dt_ms,dt_us,dt_ns— and they mean the same thing, sodt_us=500anddt_ms=0.5open the same reader. Passing two units for the same quantity raises rather than picking one. The same applies tooffset_*here, tot0_*/t1_*onEventReader.slice(), and tostep_*/span_*onEventReader.windows(). Timestamps are stored in microseconds, so a_nsvalue is rounded to the nearest microsecond, and a duration below half a microsecond raises rather than being silently rounded up:ecv.open("rec.h5", dt_us=500) # 500 us frames ecv.open("rec.h5", dt_s=0.03).slice(t0_us=1e6, t1_us=2e6)
offsetis an absolute timestamp in the file’s own time base — exactly whatslice(t0_ms=…)takes — that moves the framing origin:slice(0),windows(), andn_slicesall begin at that time, and events before it fall outside every indexed frame. It is clamped up tot_min, so an offset before the recording is a no-op, and one past the end yields zero frames. For an epoch-based recording pass the epoch time (e.g.offset=1_587_540_271_650). It composes with eitherdt_msormax_events. The bareoffsetis milliseconds;offset_s/offset_ms/offset_us/offset_nsare explicit.sensor_sizeandtime_unitare auto-detected when omitted (seeload());order/topicmatchload(). For a multi-GB HDF5, passsensor_sizeto skip the one-time coordinate scan resolution inference needs.The x/y/t/p columns are found automatically whatever their names or nesting (HDF5 synonym/recursive/compound detection; text header rows), exactly as in
load(). Passkeys={"x": …, "y": …, "t": …, "p": …}to name them explicitly when a file’s layout can’t be guessed (HDF5 dataset paths ordataset/field; text header names or 0-based indices).Pass
hot_pixel_filter=Trueto strip stuck pixels consistently across the whole recording.openscans the file once up front, flags every pixel whose event count exceedsmean + hot_pixel_std·std(over the active pixels), and drops those pixels from every slice it returns — the filter runs before any per-slice op (efast/repr/ …). This is deliberately global: callingEventStream.hot_pixel_filter()per slice re-thresholds each window, so genuinely hot pixels survive at longdt_ms. The pre-scan is kept lightweight — it reads only the event coordinates (skipping the timestamp/polarity columns) and, for a large recording, samples windows spread evenly across it rather than every event, so the mask is a robust estimate rather than an exact tally.hot_pixel_std(default3.0) tunes how aggressive it is, matchingEventStream.hot_pixel_filter().Pass
repr(a representation name —"count","voxel","tsurf","flow"for optical flow, …) to make the reader a PyTorch-style map dataset:len(reader) == n_slices,reader[i]returns the dense[C, H, W]array for framei, andreader.batch(indices)stacks a[B, C, H, W]batch — so aDataLoadercan collate the reader directly. Usereader.with_repr(name, **opts)to set per-representation options (e.g.bins=5, orwindow=5for"flow"). Withoutrepr,reader[i]stays a rawEventStream; to still batch those through aDataLoaderpasscollate_fn=eventcv.collate(each batch is alist[EventStream], since sparse streams can’t stack into a tensor).Phase 5 algorithms apply per slice:
reader.efast()/reader.harris_corners(thr)return a new reader whose every slice is the corner sub-stream, composing withslice/windows/with_repr. To render an algorithm as a video, map it overwindows()and hand the frames toexport_png()(then assemble with ffmpeg).When
repris set,slice/slice_count/windowsalso apply it: each returns the renderedEventFrame(the rich object —.numpy(),.view(),.save()), whilereader[i]/batchstay dense NumPy arrays for theDataLoaderpath. Soopen(path, repr="mcts").slice(0)equalsopen(path).slice(0).mcts(). Withoutrepr,slice(i)returns the rawEventStream, so name the representation on the stream:data.slice(1000).view("flow")(or.slice(1000).optical_flow().view()).Time spans follow the slice window. An unset
window_ms/tau_ms/max_window_ms— onrepr=, onwith_repr, or on a representation built from a slice (reader.slice(0).mcts()) — defaults to the duration of the window the events came from, so the representation covers exactly the events it was handed:open(path, dt_ms=50, repr="mcts")encodes the full 50 ms instead of the 30 ms default (which would silently discard the oldest 20 ms of every slice).slice(t0, t1)andwindows(span_ms=…)follow their own durations the same way. Set a span explicitly to override; inmax_eventsmode there is no fixed duration, so the 30 ms defaults stand.Example:
r = eventcv.open("rec.hdf5", dt_ms=30) # resolution + time unit auto-detected r.n_slices # how many 30 ms frames r.slice(50).mcts().view() # the 50th 30 ms frame for frame in r.windows(): # walk every frame (step defaults to dt_ms) voxel = frame.voxel() # As a training dataset: ds = eventcv.open("rec.hdf5", dt_ms=30, repr="count") loader = torch.utils.data.DataLoader(ds, batch_size=32, shuffle=True) # Corner-detection video (one PNG per frame): corners = eventcv.open("rec.hdf5", dt_ms=30).efast() eventcv.export_png((w.count() for w in corners.windows()), "corners/", colormap="turbo") # Optical-flow video: eventcv.export_png((w.optical_flow() for w in r.windows()), "flow/")
downsample=k(polarity only) binsk``×``ksensor pixels into one cell while counting; withrepr="polarity"the RAW decoder feeds the histogram directly, without building the window’s events.
- eventcv.save(obj, path: str, *, topic: str | None = None, format: str | None = None, compression: int | bool | None = None) None[source]¶
Save an
EventStream,EventFrame,EventReader, orFEASTmodel topath.The mirror of
load(): the format is chosen by the file extension. Streams go to.npz/.txt/.h5/.bag(npz, HDF5, and rosbag round-trip exactly; txt storest x y pand recovers the sensor size/unit on load via inference or options). Frames (computed representations) go to.npzor.h5, preserving shape, dtype,kind, andchannel_names. A trainedFEASTmodel is written to.npz(its learned features, thresholds, and parameters) and reloaded withload_feast().topicnames the rosbag connection. Equivalent toobj.save(path)for streams/frames.E2VID export. A
.ziptarget (orformat="e2vid"on a.txt) writes the layout E2VID reads — awidth heightheader, thent x y pper event withtin float seconds — so a recording goes straight into event-to-video reconstruction without a conversion script:ecv.save(ecv.open("rec.h5"), "events.zip") # python run_reconstruction.py --input_file events.zip --fixed_duration -T 33
Passing an
EventReaderconverts it window by window, so a recording far larger than memory re-exports without being loaded, and any deferred ops on the reader (crop,mask,hot_pixel_filter, …) apply to what is written. eventcv does not read the E2VID layout back — keep an.npz/.h5if you need the recording itself.HDF5 compression. Events are stored as four columns —
x/y(uint16),t(int64) andp(uint8) — which is 13 raw bytes an event, so a large recording is enormous stored plainly..h5is therefore chunked, byte-shuffled and gzipped by default, which typically cuts it by more than half at some cost in write speed. Passcompression=Falsefor the fastest possible write, or1–9to pick a level:ecv.save(events, "rec.h5") # gzip 1, the default ecv.save(events, "rec.h5", compression=False) # fastest, largest
- eventcv.load_frame(path: str) EventFrame[source]¶
Load an
EventFramewritten bysave()(.npzor.h5).Restores the representation’s shape, dtype,
kind, andchannel_names.
- eventcv.load_feast(path: str) FEAST[source]¶
Load a
FEASTmodel written bysave()(.npz).Restores the learned features, selection thresholds, and every constructor parameter, so the reloaded model reproduces
FEAST.transform()exactly and can resume training withFEAST.fit().
- eventcv.export_png(frames, out_dir: str, *, colormap: str = 'viridis', normalize: bool = True, prefix: str = 'frame_', start: int = 0, digits: int = 5)[source]¶
Write one or many
EventFrames to numbered.pngfiles — the “frame sequence → video frames” export.framesis a singleEventFrameor any iterable of them (e.g. a generator over a reader’s windows), so a whole recording renders lazily without materialising every frame at once:r = eventcv.open("rec.hdf5", dt_ms=30) eventcv.export_png((w.count() for w in r.windows()), "out/", colormap="turbo")
Each frame is colormapped through the same path as
frame.save("x.png")(colormap:viridis/turbo/grayscale/redblue;normalizeauto-contrasts). Files are named{prefix}{index:0{digits}d}.pngcounting fromstart. Returns the list of written paths (assemble a video with, e.g.,ffmpeg -i out/frame_%05d.png out.mp4).
- eventcv.collate(batch)[source]¶
collate_fnfortorch.utils.data.DataLoaderover anEventReader.A reader opened with a representation (
open(repr=…)) yields dense[C, H, W]arrays that torch’s default collate stacks into a[B, C, H, W]tensor with no help — so you only need this for a reader opened withoutrepr, whosereader[i]is a rawEventStream. Those are variable-length and sparse, so they can’t stack into a tensor; this returns the batch as a plainlistof streams instead (dense/array batches still defer to torch’s default collate). Pass it explicitly:loader = torch.utils.data.DataLoader(reader, batch_size=32, collate_fn=eventcv.collate) for batch in loader: # batch is a list[EventStream] batch[0].view()
Devices¶
Where representations and the simulator run. The CPU is the default and the reference; see Representations for what the GPU changes numerically and when it is worth using.
- eventcv.set_device(name)¶
Sets the device every representation uses when a call does not name one.
The session starts on “cpu” — the CPU implementations are the reference, and nothing changes numerically unless this is called. EVENTCV_DEVICE sets the same thing for a whole process without touching any code.
- eventcv.get_device()¶
The device representations run on when a call does not name one.
- eventcv.gpu_available()¶
Whether device=”gpu” can actually run here — the build carries the kernels and an adapter was found. The first call opens the adapter, which takes a few milliseconds.
Viewing¶
- eventcv.play(source=None, **kwargs) None[source]¶
Open the interactive player, optionally with an
EventReader,EventStream, or path already loaded.The offline twin of
EventCamera.show(): with noreprit shows the raw event stream, polarity dots fading by age, so watching a recording takes no more setup than watching a camera:ecv.play() # choose a recording in the GUI ecv.play("rec.h5", dt_ms=5) ecv.play("rec.h5", repr="count", dt_ms=20) ecv.play(events, speed=0.25)
At
speed=1recording time matches wall time.dt_msis the accumulation width whilerefresh_hz(default 60) controls how often that window advances, so overlapping windows are cheap.fpsremains accepted for compatibility but is deprecated. The call blocks on the main thread until the window is closed.
- eventcv.save_video(source, path: str, *, fps: float = 30.0, repr: str | None = None, dt_ms: float | None = None, decay_ms: float | None = None, colormap: str = 'viridis', clim: float | None = None, max_frames: int | None = None) int[source]¶
Render
sourceas an animation, returning the number of frames written.sourceis anEventReaderor anEventStream; the container comes frompath’s extension (.gif,.apng,.mp4— the last needsffmpegonPATH).With no
reprthis writes the raw stream: every event lights its pixel in its polarity colour and fades overdecay_ms, which is the picture a camera’s own viewer shows and needs no choice of representation to be meaningful:ecv.save_video(ecv.open("rec.h5"), "raw.mp4", dt_ms=5) ecv.save_video(events, "raw.gif", dt_ms=5, decay_ms=20)
dt_msis how much of the recording each rendered frame covers — the shutter, not the playback rate, which isfps. Together they set the speed:dt_ms=5, fps=30plays six times slower than real time. Passing it here rather than atopen()is deliberate; it is a property of the view, and a reader is often opened for something else entirely.Pass
repr="count"(or any representation name) to render that instead, withcolormapandclimas inEventReader.save_video(). A reader opened withrepr=uses it unlessrepr="raw"overrides.
See :doc:video for the raw view and the interactive player’s controls.
Simulation and reconstruction¶
- eventcv.simulate(source, *, pos_thres: float = 0.2, neg_thres: float = 0.2, sigma_thres: float = 0.03, refractory_us: int = 100, cutoff_hz: float = 200.0, leak_rate_hz: float = 1.0, shot_noise_rate_hz: float = 10.0, seed: int = 0, upsample: str | None = None, max_events_per_pixel: float = 1.0, max_upsample: int | None = None, fps: float | None = None, scale: tuple[int, int] | None = None, max_frames: int | None = None, out: str | None = None, compression: int | bool | None = None, progress: bool = False, device: str | None = None, interpolate: str | None = None, interpolate_factor: int = 2)[source]¶
Simulate a DVS camera watching
source, returning the events it would have produced.sourceis a video file path (which carries its own frame rate) or an array of frames —[N, H, W]greyscale or[N, H, W, 3]RGB, integer0..255or float0..1— which needs an explicitfps. Writing.mp4and reading video both go throughffmpeg, so a video source needs it onPATH.The pixel model follows v2e (Hu et al., CVPRW 2021). Frames are linearised out of sRGB and mapped lin-log before differencing, so contrast is measured in light rather than in display-encoded values:
events = ecv.simulate("clip.mp4") # realistic defaults events = ecv.simulate(frames, fps=1000) # from an array events = ecv.simulate("clip.mp4", sigma_thres=0.0, leak_rate_hz=0, shot_noise_rate_hz=0) # an ideal, noiseless sensor
Writing straight to a file. A realistic sensor emits a lot — a second of 1080p is on the order of a hundred million events, which is many gigabytes held in RAM before you can save any of it.
out=writes each frame interval as it is produced and returns aSimulationResult(.path,.frames,.events;len()is the event count) instead of anEventStream, so memory stays flat at one interval:result = ecv.simulate("clip.mp4", out="events.h5", progress=True) reader = ecv.open(result.path, dt_ms=20)
.h5streams; other formats are buffered and written once at the end.compressionpicks the HDF5 filter (gzip 1 by default,Falsefor none), andprogress=Trueprints frames and event counts to stderr as it runs.Parameters mirror the sensor:
pos_thres/neg_thresare the log-contrast thresholds,sigma_threstheir per-pixel spread,cutoff_hzthe photoreceptor bandwidth for a white pixel,leak_rate_hzspontaneous ON events,shot_noise_rate_hzthe noise floor in dark pixels, andrefractory_usthe dead time after each event. Set the noise terms to0for a clean stream; the defaults are what a real camera does.upsamplesubdivides each frame interval before simulating, which is what keeps timestamps accurate when a lot happens between two frames:"adaptive"(the default) subdivides until no pixel would emit more thanmax_events_per_pixelper sub-interval,"off"disables it, and an integer string is a fixed factor. Each sub-step is a full pass over every pixel and the busiest pixel sets the factor, so one hard edge can cost the maximum on an otherwise quiet frame —max_upsample(default 64) caps that, trading timestamp accuracy for time.scaledecodes video at a different resolution, which is both much cheaper than resizing afterwards and the most effective way to cut the event count — halving each side quarters the pixels and roughly quarters the output.max_framesstops early, andseedmakes a run reproducible from its configuration (including across machines: the simulation runs on every core, but its noise is seeded so that the result does not depend on how many there are).device="gpu"runs the pixel model as a compute shader instead, which is worth it on a large sensor: this is the heaviest loop in the library, and the one place the GPU wins clearly. The two backends are not interchangeable to the last event, and the difference is worth knowing before you rely on it:With
shot_noise_rate_hz=0andleak_rate_hz=0they produce identical events.With
sigma_threson they produce the same events with the same polarities, and a couple of timestamps in ~25 000 land a microsecond apart — the crossing fraction is double precision on the CPU and shaders have no double precision.With noise on they draw different random numbers by construction (a per-pixel kernel cannot replay a sequential generator), so the streams agree in rate, not event for event. A GPU run is still bit-reproducible from its seed, run to run and across devices.
So compare a GPU run against a stored CPU one only with the noise terms off.
interpolateinserts learned frames before simulating, which is the other half of the timestamp-accuracy problem.upsamplesubdivides the interval, but the levels it subdivides are a straight-line blend of the two frames — right when a pixel’s intensity ramps over the gap, wrong when an edge crosses it and the pixel steps. Pointinterpolateat a RIFE ONNX export (or anything shaped like one: a frame pair in, one image out, ideally with atimestepinput) andinterpolate_factorsays how many intervals each source pair becomes:events = ecv.simulate("clip.mp4", interpolate="rife_v4.onnx", interpolate_factor=4)
The interpolated frames are fed to the simulator as ordinary source frames at proportional timestamps, exactly as v2e uses Super-SloMo — the pixel model is untouched, and
upsamplestill refines whatever is left. Frames are interpolated in luma, since that is all the simulator consumes. No weights ship with eventcv; export the model yourself.
- eventcv.reconstruct(reader, model, path: str, *, fps: float = 30.0, colormap: str = 'grayscale', max_frames: int | None = None) int[source]¶
Reconstruct an intensity video from events, returning the number of frames written.
readermust carry the representation the model expects, andmodelis anModelwrapping any ONNX graph that maps that representation to a single-channel image — E2VID and its relatives:reader = ecv.open("recording.h5", dt_ms=33, repr="voxel", bins=5) ecv.reconstruct(reader, ecv.Model("e2vid.onnx"), "out.mp4")
EventCV supplies the runner and the tensors, not the weights: there is no official E2VID ONNX export, so export one yourself with
torch.onnx.export. A recurrent model must have its hidden state exposed as explicit inputs and outputs; a stateless export (--no-recurrent) works as-is.The output format comes from
path’s extension (.gif,.apng,.mp4), and the reconstruction is rendered at its natural scale rather than auto-contrasted, so brightness stays consistent across the sequence.
- class eventcv.StatefulModel(model, state_map: dict[str, str])[source]¶
Bases:
objectDrives a recurrent ONNX model, carrying its hidden state between calls.
E2VID and its relatives take
(data, state_0, …)and return(image, new_state_0, …); what makes them recurrent is feeding those states back, which a plainModelcall deliberately does not do.state_mapsays which output feeds which input:model = ecv.StatefulModel( ecv.Model("e2vid_recurrent.onnx"), state_map={"new_state": "state"}, ) for i in range(len(reader)): image = model(reader[i]) model.reset() # before an unrelated recording
State lives here rather than inside
Modelso thatModelstays a pure function and resetting is something you ask for rather than something that happens invisibly. On the first call — and afterreset()— each state input is seeded with zeros of its declared shape, with any free dimension taken as 1.There is no official recurrent E2VID export; produce one with
torch.onnx.export, exposing the ConvLSTM states as explicit inputs and outputs. A stateless export needs none of this and works withModeldirectly.
See :doc:simulation for the pixel model and :doc:reconstruction for the ONNX export
requirements.
Models¶
- class eventcv.Model(path)¶
Bases:
objectRuns a trained ONNX network over a representation.
Deliberately a runner, not a model zoo: it takes any .onnx file and knows nothing about the architecture inside it. EventCV’s job is to produce the tensor — a voxel grid or time surface is already a dense f32 array in the layout ONNX wants — and to get out of the way.
Requires a build with the onnx feature; eventcv –version reports whether this one has it.
- inputs¶
a list of {“name”, “shape”, “dtype”}, with -1 for any dimension the graph leaves free (usually the batch axis).
- Type:
What the graph expects
- outputs¶
What the graph produces, in the same form as inputs.
- run_named(inputs)¶
Runs the model with every input bound by name, returning {name: array}.
The form __call__ cannot express. A recurrent graph takes its hidden state as extra inputs and returns the updated state as extra outputs; feeding those back is what StatefulModel does on top of this.
Requires a build with the onnx feature (the published wheels have it). See
:doc:models for scope and usage.
E2VID export¶
A .zip target — or format="e2vid" on a .txt — writes the layout
E2VID reads: a width height header line, then one
whitespace-separated t x y p row per event with t in float seconds and p as 0/1,
ascending in time. That removes the conversion script between an EventCV recording and
event-to-video reconstruction.
ecv.save(stream, "events.zip") # from a stream
ecv.save(ecv.open("rec.h5"), "events.zip") # or straight from a reader
# python run_reconstruction.py --input_file events.zip --fixed_duration -T 33
Passing an EventReader converts it window by window, so a recording far
larger than memory re-exports without being loaded, and any deferred ops on the reader (crop,
mask, hot_pixel_filter, …) apply to what gets written. The export is one-way — EventCV does
not read the E2VID layout back, so keep an .npz/.h5 if you need the recording itself.
Region-of-interest masking¶
A mask is a plain (H, W) boolean NumPy array — True where events are kept — so it composes
with NumPy’s own operators (|, &, ~) and can be built, drawn, or loaded. Pass one to
mask(), to mask() (deferred onto every
slice), or to ecv.stream(mask=…), where events outside it are dropped as they are decoded — so
they never reach a record= file or the windows a loop reads.
import eventcv as ecv
aperture = ecv.circle_mask((640, 480), cx=320, cy=240, r=230) # a circular sensor aperture
aperture &= ~ecv.rect_mask((640, 480), 0, 0, 64, 64) # minus a hot corner
ecv.save_mask(aperture, "aperture.png") # reuse it next session
ecv.open("rec.h5", dt_ms=30).mask(aperture) # every slice, lazily
ecv.stream(dt_ms=50, mask=aperture, record="session.h5") # live, before the recorder
sensor_size is (width, height) — the same order as everywhere else in eventcv — while the array
you get back is (H, W), like any other NumPy image. Coordinates are continuous rather than pixel
indices: a pixel is kept when its centre falls inside the shape, and geometry off the sensor is
clamped. An 8-bit map works anywhere a boolean one does (any non-zero value keeps the pixel), so a
mask binarised elsewhere is passed straight in. A mask that isn’t the size of the sensor raises,
rather than silently dropping every event.
To draw one instead, draw_mask() (or
draw_mask()) opens the viewer on a still frame, and
draw_mask() draws over the live camera and applies the result. Drag to
keep an area, shift+drag to drop one, e/r/f to switch between ellipse, rectangle, and
freehand, a/c to select all or clear, z to undo; whatever stays bright is what the mask
keeps. Enter accepts, Esc returns None.
- eventcv.circle_mask(sensor_size: tuple[int, int], cx: float, cy: float, r: float)[source]¶
Build a circular ROI mask of radius
rcentred on(cx, cy).Returns an
(H, W)boolean NumPy array —Truewhere events are kept — ready forEventStream.mask(),EventReader.mask(), orecv.stream(mask=…). Note the two orders:sensor_sizeis(width, height)like everywhere else in eventcv, while the array that comes back is(H, W)like every other NumPy image.Masks are plain arrays, so they compose with NumPy’s own operators —
|unions,&intersects,~inverts:aperture = ecv.circle_mask((640, 480), cx=320, cy=240, r=230) aperture &= ~ecv.rect_mask((640, 480), 0, 0, 64, 64) # minus a corner ecv.load("rec.npz").mask(aperture)
Coordinates are continuous, not pixel indices: a pixel is kept when its centre falls inside the shape, and geometry reaching off the sensor is clamped. See also
ellipse_mask(),rect_mask(),polygon_mask(),load_mask(), and — to draw one by hand —EventStream.draw_mask()/EventCamera.draw_mask().
- eventcv.ellipse_mask(sensor_size: tuple[int, int], cx: float, cy: float, rx: float, ry: float)[source]¶
Build an elliptical ROI mask centred on
(cx, cy)with semi-axesrx,ry.The axis-independent form of
circle_mask(); see it for the conventions.
- eventcv.rect_mask(sensor_size: tuple[int, int], x0: float, y0: float, width: float, height: float)[source]¶
Build a rectangular ROI mask keeping the
width``×``heightbox at(x0, y0).Unlike
EventStream.crop(), which shrinks the sensor grid, a mask keeps the coordinates (and the sensor size) unchanged and only drops events. Seecircle_mask()for the conventions.
- eventcv.polygon_mask(sensor_size: tuple[int, int], points)[source]¶
Build an ROI mask keeping the interior of the closed polygon through
points.pointsis a sequence of(x, y)vertices; the last joins back to the first. Filled by the even-odd rule, so a self-intersecting outline leaves holes. Seecircle_mask()for the conventions.
- eventcv.save_mask(mask, path: str) None[source]¶
Write an ROI mask to an 8-bit greyscale
.png— white keeps, black drops.Lets an ROI drawn or computed once be reused across sessions (and checked in any image viewer). Read it back with
load_mask().
- eventcv.load_mask(path: str)[source]¶
Read an ROI mask from a
.pngas an(H, W)boolean array.A pixel is kept where the image is non-black and not fully transparent, so a mask binarised in any other tool loads as-is — greyscale, palette, or colour, with or without alpha:
ecv.stream(dt_ms=50, mask=ecv.load_mask("aperture.png"))
The counterpart of
save_mask().
Motion estimation¶
- class eventcv.Tracker(*, connectivity=8, min_area=4, max_distance=20.0, max_missed=3)¶
Bases:
objectFollows connected components across frames, keeping their identities.
Connected-component labels are assigned in scan order and renumber whenever anything moves, so they mean nothing across time. A tracker adds the memory.
- reset()¶
Forgets every track. Ids continue from where they left off, so an old track and a new one can never be confused in a log.
- tracks¶
The live tracks without advancing the tracker.
- update(frame)¶
Segments frame, matches its blobs to the existing tracks, and returns the live tracks as a list of dicts: id, centroid, velocity, area, age, missed.
See :doc:motion for contrast maximisation and the tracker’s association rule.
Networking¶
Core types¶
- class eventcv.EventStream¶
Bases:
object- atsurf(*, tau_ms=None, tau_s=None, tau_us=None, tau_ns=None, device=None)¶
Averaged time surface — the per-pixel mean of exp(-age/tau_ms) over all events (two polarity channels, float32). Brighter where activity recurs; see tsurf.
- background_activity_filter(dt, *, neighbourhood='block')¶
Background-activity (nearest-neighbour) noise filter: keeps an event only if a neighbouring pixel fired within dt (raw timestamp units, e.g. microseconds).
neighbourhood=”block” (the default) counts all eight surrounding pixels; “cross” counts only the four edge-adjacent ones, which is the stricter test and retains measurably more signal at matched noise rejection on uncorrelated shot noise — it is what tonic and jAER use. The default is “block” because that is what this filter has always done.
- concat(others)¶
Concatenates this stream with others (argument order; sensor = element-wise max).
- contrast_maximise(*, model='translation', objective='variance', camera=None, blur_sigma=None, initial=None, initial_step=50.0, max_iterations=200, tolerance=0.001, time_reference='midpoint')¶
Recovers the camera motion that makes the warped events sharpest.
Returns a dict with params (px/s for translation, rad/s for rotation), score, score_at_rest and improvement — check improvement before trusting params, since at or below 1.0 the optimiser found no motion and stopped wherever it happened to be.
- count(*, normalize=False, device=None)¶
Event-count image — one channel of total events per pixel (both polarities). Raw uint64 counts by default; uint8 rescaled to the busiest pixel when normalize=True.
- countmask(*, pct=99.0, white_frame=False, device=None)¶
Count-mask image (GEPT, Sec. 3.2) — red holds the per-pixel positive-event count, blue the negative one, and green is a binary activity mask at full scale wherever any event landed. Both count planes are clipped and normalized by a single scale: the pct-th percentile of the non-zero counts of the two planes pooled together. Timestamps are not used. white_frame inverts to a white background — the black-background default is the form downstream descriptor models expect.
- crop(x0, y0, w, h)¶
Keeps events inside the w`×`h window at (x0, y0), shifted to a new origin.
- decimate(k)¶
Keeps every k-th event by index.
- draw_mask(representation=None, *, colormap='viridis', normalize=None)¶
Draws a region of interest over this stream and returns it as an (H, W) boolean mask — the interactive way to build the argument mask() takes. Shows the same view view() would (pass a representation name to choose another), then: drag to keep an area, shift+drag to drop one, e/r/f to switch between ellipse, rectangle, and freehand, a/c to select all or clear, z to undo. Whatever stays bright is what the mask keeps.
Enter accepts and returns the mask; closing the window or pressing Esc returns None.
- efast()¶
eFAST event corner detector (Mueggler et al., BMVC 2017). Keeps the events sitting on a moving corner, tested on two Bresenham rings over the per-polarity surface of active events.
- event_drop(p=0.1, *, seed=0)¶
Drops each event independently with probability p.
- event_rate(bin_ms=None, *, bin_s=None, bin_us=None, bin_ns=None)¶
Activity over time, binned into fixed intervals.
Returns a dict of numpy arrays — t (each bin’s left edge, µs), count, positive, negative, and rate (events/second) — so it plots directly: plt.plot(*r[“t”] / 1e6, r[“rate”]). The spatial counterpart is pixel_counts() on a reader, which totals events per pixel over a whole file.
- filter_polarity(polarity)¶
Keeps only events of the given polarity (nonzero / True = ON, 0 / False = OFF).
- flip_x()¶
Mirrors horizontally (x → width-1-x).
- flip_y()¶
Mirrors vertically (y → height-1-y).
- harris_corners(threshold=0.0)¶
Harris corner score on the Surface of Active Events: keeps events whose normalised Harris response det/trace² - k of the SAE-ramp structure tensor exceeds threshold. The default threshold=0 keeps corners (rank-2, R>0) and rejects straight edges (rank-1, R<0). The score is bounded to [-0.04, 0.21], so raise threshold within that range to be stricter; above 0.21 nothing is kept.
- hot_pixel_filter(n_std=3.0)¶
Hot-pixel removal: drops pixels whose event count exceeds mean + n_std·std over the active pixels (default n_std=3.0).
- invert_polarity()¶
Flips every event’s polarity.
- iwe(params, *, model='translation', camera=None)¶
Builds the image of warped events for an explicit motion — the picture the objective scores.
- mask(mask)¶
Keeps events inside the region of interest: a (H, W) boolean mask (or 8-bit map, where non-zero keeps the pixel) covering this stream’s sensor. Build one with eventcv.circle_mask / ellipse_mask / rect_mask / polygon_mask, draw one with draw_mask(), or load one with eventcv.load_mask.
- mcts(*, max_window_ms=None, max_window_s=None, max_window_us=None, max_window_ns=None, windows_ms=None, windows_s=None, windows_us=None, windows_ns=None)¶
Multi-channel time surface (SuperEvent) — per polarity, one linear-decay channel per time window, float32. The windows are 5 log-spaced spans from 1 ms up to max_window_ms (default: the slice window, else 30 ms), or exactly the spans in windows_ms=[…] (2N channels, used in the order given); pass one form, not both.
- normalize_time()¶
Shifts timestamps so the earliest event starts at zero.
- optical_flow(*, window=3)¶
Dense Lucas-Kanade optical flow on the time surface.
Returns a two-channel (flow_x, flow_y) frame in pixels/ms; window is the half-width of the least-squares neighbourhood.
- pixel_dropout(p=0.1, *, seed=0)¶
Silences a random p fraction of pixels for the whole stream (dead-pixel simulation).
- play(*, fps=None, dt_ms=30.0, decay_ms=None, speed=1.0, refresh_hz=60.0, loop_=False)¶
Opens the playback GUI over this in-memory stream.
The in-memory counterpart of EventReader.play — same raw polarity-and-decay view, no file and no reader needed. At speed=1, recording time matches wall time; loop_ restarts at the end. Explicit fps= preserves the old fixed-frame-rate timing and is deprecated.
- random_crop(width, height, *, seed=0)¶
Takes a random width × height crop; a window at least as large as the sensor is a no-op.
- random_flip_x(p=0.5, *, seed=0)¶
Mirrors left-right with probability p.
- random_flip_y(p=0.5, *, seed=0)¶
Mirrors top-bottom with probability p.
- random_polarity_flip(p=0.5, *, seed=0)¶
Inverts every polarity with probability p (all events, not a random subset).
- raw_image(*, decay_ms=None)¶
The raw polarity image of this stream as an (H, W, 3) uint8 array — what view(“raw”) displays, handed back instead of shown.
Events are coloured by polarity and dimmed by age, measured back from the newest event, so the picture reads as “what the sensor just saw” rather than as a flat accumulation.
- redblue(*, pct=99.0, white_frame=True, device=None)¶
White-background red/blue counts, independently percentile-normalized per polarity. The stronger normalized polarity wins (positive on ties); timestamps are unused. white_frame is accepted for compatibility but the background is always white.
- refractory_filter(dt, *, refresh='kept')¶
Refractory-period filter: suppresses a pixel’s events for dt after it fires.
refresh=”kept” (the default) measures the dead time from the pixel’s last emitted event, the hardware definition — a suppressed event does not restart it. refresh=”all” restarts it on every arriving event, so the pixel must fall silent for dt before it emits again; that is what tonic and evlib do, and it suppresses more under a noise burst. The two are indistinguishable on clean data.
- repr¶
The default representation name carried from open(repr=…) / with_repr (what view()/flatten() render), or None for a raw stream.
- resize(width, height)¶
Event-domain resize to a width`×`height grid (rebinned, not interpolated).
- rotate90(k)¶
Rotates by k * 90° clockwise (quarter turns swap the sensor dims).
- save(path, *, topic=None, format=None, compression=None)¶
Saves the stream to path, format chosen by extension (.npz/.txt/.h5/.bag, or .zip for E2VID) — the counterpart of eventcv.load. npz/HDF5/rosbag round-trip exactly; topic names the rosbag connection; format overrides the extension; compression picks the HDF5 filter (default gzip 1, False for none).
- save_video(path, *, fps=30.0, dt_ms=30.0, decay_ms=None, repr=None, colormap='viridis', clim=None, max_frames=None)¶
Renders this stream as an animation, returning the number of frames written.
The in-memory counterpart of EventReader.save_video, and raw by default for the same reason: looking at a recording should not require first deciding how to represent it. dt_ms is how much of the stream each frame covers; fps is how fast they play back.
- scale(sx, sy)¶
Scales the sensor by (sx, sy).
- sort_by_time()¶
Returns a copy reordered by ascending timestamp (stable).
- spatial_jitter(sigma=1.0, *, seed=0)¶
Jitters each event’s position by a gaussian offset of standard deviation sigma pixels.
- time_jitter(sigma_ms=None, *, sigma_s=None, sigma_us=None, sigma_ns=None, seed=0)¶
Jitters each event’s timestamp by a gaussian offset, re-sorting afterwards.
- time_reversal(p=0.5, *, seed=0)¶
Plays the stream backwards with probability p, inverting polarity to match.
- time_scale(factor)¶
Scales every timestamp by factor (rounded).
- time_shift(dt=None, *, dt_s=None, dt_ms=None, dt_us=None, dt_ns=None)¶
Shifts every timestamp by dt. The positional form is microseconds; dt_s/dt_ms/dt_us/ dt_ns take any unit, and unlike a window duration this one may be negative.
- time_window(t0=None, t1=None, *, t0_s=None, t0_ms=None, t0_us=None, t0_ns=None, t1_s=None, t1_ms=None, t1_us=None, t1_ns=None)¶
Keeps events whose timestamp lies in the half-open window [t0, t1). Positional bounds are microseconds (the unit timestamps are stored in); t0_s/t0_ms/t0_us/t0_ns and the t1 equivalents take any unit.
- translate(dx, dy)¶
Translates by (dx, dy); events shifted off the sensor are dropped.
- transpose()¶
Reflects across the main diagonal ((x, y) → (y, x)); swaps the sensor dims.
- undistort(camera)¶
Rectifies events with a Camera’s intrinsics + distortion (lens undistortion).
- view(representation=None, *, colormap='viridis', normalize=None, decay_ms=None)¶
Opens the interactive viewer on this stream. Pass a representation name to choose what to show — stream.view(“flow”), stream.view(“count”), stream.view(“voxel”), … — or omit it to use the stream’s stored representation (from open(repr=…)), falling back to the polarity image. (Equivalent to stream.<repr>().view().)
stream.view(“raw”) shows the events themselves — polarity dots faded by age, the camera viewer’s own picture — rather than any representation of them.
- warp_affine(matrix)¶
Applies a 2×3 affine matrix [[a,b,c],[d,e,f]] (rounded, no interpolation).
- warp_perspective(matrix)¶
Applies a 3×3 perspective (homography) matrix.
- class eventcv.EventFrame¶
Bases:
object- connected_components(*, connectivity=8)¶
Connected-component labelling (Phase 5): treats any non-zero pixel as foreground and labels each 4- or 8-connected blob 1..=k, background 0. Returns a single-channel u64 frame.
- draw_mask(*, colormap='viridis', normalize=True)¶
Draws a region of interest over this frame and returns it as an (H, W) boolean mask. See EventStream.draw_mask for the controls; Esc (or closing the window) returns None.
- resize(width, height, *, pooling='average')¶
Resize spatial dimensions using average or sum pooling on shrinking axes.
- save(path, *, colormap='viridis', normalize=True)¶
Saves the frame to path. .npz/.h5 store the raw array (shape, dtype, kind, channel_names) for eventcv.load_frame; .png writes a colormapped 2-D view (colormap = viridis/turbo/grayscale/redblue; normalize auto-contrasts).
- view(*, colormap='viridis', normalize=True)¶
Opens the interactive GPU viewer. Image reprs are shown colour-mapped (colormap: viridis/turbo/grayscale/redblue; normalize auto-contrasts); volumetric reprs become an orbitable 3-D point cloud (drag to rotate, Esc to close).
- class eventcv.EventReader¶
Bases:
objectLazy, seekable handle over a file’s events — the VideoCapture to load’s imread. Slices are fetched on demand (HDF5 by binary-searching its timestamps on disk), so multi-GB files never need to be fully resident. Opening with dt_ms fixes a frame duration so slice(n) returns the n-th frame (like seeking a video).
- background_activity_filter(dt, *, neighbourhood='block')¶
Background-activity (nearest-neighbour) noise filter, per slice. neighbourhood is “block” (default, all eight neighbours) or “cross” (the four edge-adjacent ones).
- batch(indices)¶
Renders slice indices into one dense [B, C, H, W] array — the explicit-batch path for training. Requires a representation (open(repr=…) / with_repr). indices is any int sequence (list / range / …); each is a dt_ms frame index.
- crop(x0, y0, w, h)¶
Crops each slice to the w`×`h window at (x0, y0) (see [EventStream::crop]).
- decimate(k)¶
Keeps every k-th event of each slice.
- dt(unit='ms')¶
The fixed slice duration set at open, in unit (“s”, “ms”, “us”, “ns”), or None if the reader is in max_events mode.
- dt_ms¶
The fixed slice duration set at open, or None if it was not given.
- duration(unit='ms')¶
How long the recording runs, in unit (“s”, “ms”, “us”, “ns”).
- efast()¶
Returns a new reader whose every slice is passed through [EventStream::efast], so the reader yields corner sub-streams (chain .count() / with_repr to visualise them).
- event_drop(p=0.1, *, seed=0)¶
Drops each event in a slice independently with probability p.
- filter_polarity(polarity)¶
Keeps only events of the given polarity per slice (nonzero / True = ON).
- flip_x()¶
Mirrors each slice horizontally.
- flip_y()¶
Mirrors each slice vertically.
- harris_corners(threshold=0.0)¶
Returns a new reader whose every slice is passed through [EventStream::harris_corners].
- hot_pixel_filter(n_std=3.0)¶
Hot-pixel removal applied to each slice (per-slice statistics; distinct from open(hot_pixel_filter=True), which computes one mask over the whole recording).
- invert_polarity()¶
Flips every event’s polarity, per slice.
- mask(mask)¶
Keeps each slice’s events inside the region of interest — a (H, W) boolean mask (or 8-bit map, where non-zero keeps the pixel) covering this recording’s sensor. Applied lazily to every slice, so open(…).mask(roi) reads a whole recording through the ROI.
- max_events¶
The fixed events-per-slice set at open(max_events=…), or None if it was not given.
- n_slices¶
Number of fixed slices spanning the recording (requires open(dt_ms=…) or open(max_events=…)).
- normalize_time()¶
Shifts each slice’s timestamps so its earliest event starts at zero.
- offset¶
The absolute framing origin (ms, the file’s time base) set at open(offset=…), or None if it was not given.
- pixel_dropout(p=0.1, *, seed=0)¶
Silences a random p fraction of pixels in each slice.
- play(*, fps=None, dt_ms=None, decay_ms=None, repr=None, colormap='viridis', clim=None, speed=1.0, refresh_hz=60.0, loop_=False, max_frames=None)¶
Opens the recording in the interactive player — the offline twin of camera.show().
With no representation this is the raw view: polarity dots fading over decay_ms, the same [RawSurface](eventcv_core::viz::RawSurface) the live viewer draws, so watching a file needs no more setup than watching a camera. Pass repr=”count” (or any representation name) to play that instead. speed multiplies the playback rate — 0.25 for a quarter-speed look at a fast scene — and dt_ms sets the window without reopening the reader.
Blocks on the main thread until the window is closed. At speed=1, recording time matches wall time. Explicit fps= preserves the old fixed-frame-rate timing and is deprecated.
- random_crop(width, height, *, seed=0)¶
Takes a random width × height crop of each slice.
- random_flip_x(p=0.5, *, seed=0)¶
Mirrors each slice left-right with probability p.
- random_flip_y(p=0.5, *, seed=0)¶
Mirrors each slice top-bottom with probability p.
- random_polarity_flip(p=0.5, *, seed=0)¶
Inverts every polarity in a slice with probability p.
- read_camera_info()¶
The camera intrinsics the recording carries, or None when it carries none. Only ROS bags record them today; AEDAT does not.
- read_frames(*, t0_us=0, t1_us=Ellipsis)¶
The intensity (APS) frames recorded alongside the events, as [(t_us, EventFrame), …].
A DAVIS records a greyscale video beside its event stream; this is that video. Sources without one (.npz, Prophesee, HDF5, …) return an empty list rather than an error. The window defaults to the whole recording, which on a multi-gigabyte file is a great many frames — pass t0_us/t1_us for anything larger than a demo.
- read_imu(*, t0_us=0, t1_us=Ellipsis)¶
The IMU samples recorded alongside the events, as a dict of numpy arrays: t (µs), angular_velocity (rad/s) and linear_acceleration (m/s², gravity included), both [N, 3]. Empty for sources without an IMU.
- refractory_filter(dt, *, refresh='kept')¶
Refractory-period filter, per slice. refresh is “kept” (default, the dead time runs from the last emitted event) or “all” (every arriving event restarts it).
- repr¶
The per-slice representation name set at open/with_repr, or None (raw streams).
- resize(width, height)¶
Event-domain resize of each slice to a width`×`height grid.
- rotate90(k)¶
Rotates each slice by k * 90° clockwise.
- save(path, *, format=None)¶
Converts the whole recording to path without loading it — slices are read in order and appended as they go, with any deferred ops (crop, mask, hot_pixel_filter, …) applied.
Targets E2VID’s interchange (.zip, or format=”e2vid” for a .txt), which is what a multi-gigabyte recording usually needs converting to. Saving to a round-tripping format works from a slice: eventcv.save(reader.slice(0), “frame.npz”).
- save_video(path, *, fps=30.0, colormap='viridis', clim=None, max_frames=None, repr=None, dt_ms=None, decay_ms=None)¶
Renders every slice and writes them as one animation. Format comes from path’s extension: .gif, .apng/.png, or .mp4 (which pipes to a system ffmpeg).
Needs a representation — open(repr=…) or with_repr(…) — since a video is a sequence of rendered frames.
clim fixes the value that maps to the top of the colormap, held across every frame. Left as None, it is calibrated from the first few slices and then held, because auto-contrast computed per frame makes the brightness pump visibly over a sequence. Pass clim=0 to get the per-frame behaviour back, or an explicit value to match two videos to the same scale. The raw stream. With no representation — neither open(repr=…) nor repr= here — the events are drawn directly: each one lights its pixel in its polarity colour and fades over decay_ms, which is the view a camera’s own viewer shows and the one that needs no choice of representation to look at a recording. repr=”raw” asks for it explicitly even when the reader carries a representation. dt_ms sets the window here rather than at open, so the playback rate is a property of the video and not of the reader.
- scale(sx, sy)¶
Scales each slice’s sensor by (sx, sy).
- slice(index=None, *, t0_ms=None, t0_s=None, t0_us=None, t0_ns=None, t1_ms=None, t1_s=None, t1_us=None, t1_ns=None)¶
One slice. With a positional index n (requires open(dt_ms=…) or open(max_events=…)), returns the n-th fixed frame — the dt_ms-long window [t_min + n·dt, t_min + (n+1)·dt), or the max_events-sized event chunk [n·N, (n+1)·N); negative n counts from the end. Otherwise returns the half-open time window [t0_ms, t1_ms), with omitted bounds extending to the recording’s start / end. Bounds take any unit — t0_s / t0_ms / t0_us / t0_ns, and the same for t1. When the reader carries a representation (open(repr=…) / with_repr) the slice is rendered to that EventFrame; otherwise it is a raw EventStream.
- slice_count(i0, i1)¶
Events whose index lies in [i0, i1) (clamped to the file). Rendered to the reader’s EventFrame when a representation is set (open(repr=…) / with_repr), else a raw EventStream.
- sort_by_time()¶
Reorders each slice by ascending timestamp (stable).
- spatial_jitter(sigma=1.0, *, seed=0)¶
Jitters event positions in each slice by a gaussian of standard deviation sigma pixels.
- time_jitter(sigma_ms=None, *, sigma_s=None, sigma_us=None, sigma_ns=None, seed=0)¶
Jitters event timestamps in each slice, re-sorting afterwards.
- time_reversal(p=0.5, *, seed=0)¶
Plays each slice backwards with probability p, inverting polarity to match.
- time_scale(factor)¶
Scales each slice’s timestamps by factor.
- time_shift(dt=None, *, dt_s=None, dt_ms=None, dt_us=None, dt_ns=None)¶
Shifts each slice’s timestamps by dt — positionally in microseconds, or in any unit via dt_s/dt_ms/dt_us/dt_ns.
- time_span(unit='ms')¶
The recording’s (first, last) timestamp in unit — “s”, “ms” (the default), “us”, or “ns”. The unit-flexible form of time_span_ms.
- time_window(t0=None, t1=None, *, t0_s=None, t0_ms=None, t0_us=None, t0_ns=None, t1_s=None, t1_ms=None, t1_us=None, t1_ns=None)¶
Keeps each slice’s events whose timestamp lies in [t0, t1) — positionally in microseconds, or in any unit via t0_s/t0_ms/t0_us/t0_ns and the t1 equivalents.
- translate(dx, dy)¶
Translates each slice by (dx, dy).
- transpose()¶
Reflects each slice across the main diagonal.
- undistort(camera)¶
Undistorts each slice with a Camera’s intrinsics + distortion.
- warp_affine(matrix)¶
Applies a 2×3 affine matrix to each slice.
- warp_perspective(matrix)¶
Applies a 3×3 perspective matrix to each slice.
- windows(*, step_ms=None, step_s=None, step_us=None, step_ns=None, span_ms=None, span_s=None, span_us=None, span_ns=None)¶
Lazy iterator of consecutive windows: each is [start, start + span_ms) and start advances by step_ms. step_ms defaults to the dt_ms set at open (so windows() walks every slice(n)), and span_ms defaults to step_ms (non-overlapping). For a max_events reader, windows() without arguments walks the fixed-count slices instead. Streams a multi-GB file window-by-window without loading it. Each item is a rendered EventFrame when the reader carries a representation (open(repr=…) / with_repr), else a raw EventStream.
- with_repr(repr, *, bins=None, window_ms=None, window_s=None, window_us=None, window_ns=None, tau_ms=None, tau_s=None, tau_us=None, tau_ns=None, max_window_ms=None, max_window_s=None, max_window_us=None, max_window_ns=None, windows_ms=None, windows_s=None, windows_us=None, windows_ns=None, window=None, normalize=None, pct=None, white_frame=None, downsample=None)¶
Returns a new reader over the same file that renders each slice with repr (unset params take their method defaults; unset time spans follow the reader’s dt). The dataset-mode counterpart of open(repr=…), but with per-representation options: e.g. reader.with_repr(“voxel”, bins=5).
- class eventcv.Camera(fx, fy, cx, cy, *, k1=0.0, k2=0.0, p1=0.0, p2=0.0, k3=0.0)¶
Bases:
objectPinhole intrinsics + Brown–Conrady distortion, e.g. an EV-IMO calib.txt (fx fy cx cy k1 k2 p1 p2). Pass to stream.undistort(camera).
- undistort_point(u, v)¶
Maps a distorted pixel (u, v) to its undistorted location.
- class eventcv.EventCamera¶
Bases:
objectA live USB event camera — the streaming twin of [PyEventReader]. Open one with eventcv.stream(…).
- backlog¶
Buffers waiting in the driver’s ring. The pump thread drains it continuously, so this stays near zero unless the camera is producing faster than events can be decoded and written.
- bias_state¶
What the adaptive-bias controller is doing, or None when the camera was opened without stream(adaptive_bias=…).
A dict of the last measured event_rate (events/second), the target_rate band being aimed at, whether it is still calibrating, the five bias values the controller drives, its authority, and n_slow_steps — how many times the slow loop has had to shift the operating point.
For the first second or so calibrating is true: the controller is measuring the scene at the camera’s stock biases and has changed nothing. target_rate afterwards is the band it settled on, which is the number to look at if the results surprise you.
authority says how the fast loop is coping, and is the field to watch:
“tracking” — fine: the rate is in your band, or the loop is on its way there.
“starved” / “flooded” — the refractory bias is at one end of its travel and the rate is still out of band, so the slow loop is shifting the operating point to help.
“hunting” — the loop keeps reversing without ever landing in the band, which means no bias setting reaches it. Almost always a sign that target_rate does not match what the scene can produce; measure the scene unbiased and re-bracket it.
Note event_rate was measured over the period before the biases now reported were applied — the two come from consecutive periods, not the same one.
- close()¶
Stops the pump, closes the device, and finishes a stream(record=…) file if one is open. Idempotent; further use raises.
Dropping the camera does the same thing, so a throwaway eventcv.stream(…) still releases the device and closes its recording — but only at whatever point Python collects it. Call this (or use with eventcv.stream(…) as camera:) to control when that happens.
- draw_mask(representation=None, *, decay_ms=None, colormap='viridis', normalize=True)¶
Draws a region of interest over the live view and returns it as an (H, W) boolean mask, applying it to this camera as well — so the next read(), loop, or record= window is already filtered. Everything show() takes applies here too (the view you draw over).
Drag to keep an area, shift+drag to drop one, e/r/f to switch between ellipse, rectangle, and freehand, a/c to select all or clear, z to undo. Whatever stays bright is what the mask keeps. Enter accepts; closing the window or Esc returns None and leaves the camera’s current mask alone.
- mask¶
The region-of-interest mask events are filtered through — an (H, W) boolean array, or None when the whole sensor is in use.
Assign one to change it mid-session (camera.mask = eventcv.circle_mask(…), or None to clear it); stream(mask=…) sets it at open. Masked events are dropped as they are decoded, so they never reach a record= file, the windows a loop reads, or show() — and they cost nothing downstream. Where stream(roi=…) is a rectangle, blocked on-chip wherever the sensor can do it, this is any shape and always enforced on the host — so it can be changed while the camera runs.
- n_frames¶
How many APS frames this camera has decoded, including any dropped from the backlog.
- n_imu¶
How many IMU samples this camera has decoded, including any dropped from the backlog.
- n_recorded¶
Events written so far by stream(record=…) (0 when the camera isn’t recording).
- n_skimmed_events¶
Times the driver’s ring overflowed and dropped events before a window — the loss n_skipped can’t prevent, because it happens upstream of eventcv. Non-zero means the camera outran the decoder or the recording; lower the event rate, or record uncompressed. Events discarded on purpose by latest=True, counted exactly.
n_skipped counts whole windows the consumer never saw; this counts the events inside the ones dropped before a window was ever built. Together they are the whole of what this library throws away deliberately — n_overflows is the part the driver threw away, which only it can see.
- n_skipped¶
Windows a stream(latest=True) loop skipped to stay live — the count of frames the loop was too slow to process. Always 0 without latest=True (that mode never skips). A steadily rising value means the per-window work is slower than the camera; recordings stay complete either way.
- read(*, timeout_ms=None)¶
Returns the next window for a while loop — an EventFrame when the camera was opened with a representation (stream(repr=…)), else a raw EventStream. The window spans exactly the dt_ms / max_events chosen at stream(…) (so stream(dt_ms=50, repr=”mcts”).read() hands back one mcts frame per ~50 ms of events).
Blocks until that window is ready. Pass timeout_ms to cap the wait: if no window completes within that many milliseconds read returns None, so the loop can do other work or exit instead of blocking forever on an idle scene. (timeout_ms is a wait cap, not the window length.) Ctrl+C breaks out. Raises if the camera is closed.
By default windows are returned in order, so a loop slower than the camera falls further and further behind live. stream(latest=True) returns the newest completed window instead, counting what it skipped in n_skipped — record= still archives every window either way.
- read_frames()¶
The APS frames the sensor produced alongside the windows read so far, as [(t_us, EventFrame), …], and clears them. Needs stream(frames=True) and a DAVIS; anything else returns an empty list.
Frames arrive with the window that covers them, so collect after each read(). Only the most recent few dozen are kept: an uncollected backlog is dropped rather than grown, the same trade stream(latest=True) makes for events.
- read_imu()¶
The IMU samples alongside the windows read so far, as a dict of numpy arrays — t (µs), angular_velocity (rad/s) and linear_acceleration (m/s²), both [N, 3] — and clears them. Same shape eventcv.read_imu returns for a file. Needs stream(imu=True).
- record(path, *, seconds=None, compression=None)¶
Records the live stream to path (format by extension, like eventcv.save). Blocks until seconds elapses (if given) or Ctrl+C, then returns the number of events saved.
HDF5 targets (.h5 / .hdf5) are written continuously, window-by-window straight to disk via an EventSink, and flushed roughly once a second — so a long session never piles up in memory and a crash keeps everything captured so far. compression is an optional gzip level (0..=9) for that path; omit it for the fastest, uncompressed writes. Other formats (npz/txt/bag can’t be appended incrementally) buffer the whole recording in memory and write once at the end, ignoring compression.
Only events are saved — a DAVIS346’s APS frames and IMU samples are dropped.
- roi¶
…}` — or None when the whole sensor is in use.
applied is “hardware” when the sensor is blocking those pixels on-chip (they cost no USB bandwidth, no decoding, and nothing downstream) or “host” when eventcv is filtering them as they are decoded, because this sensor has no region masks. Both drop the same events; only the saving differs. Unlike mask, this is fixed when the camera is opened — the sensor writes its region masks during the opening handshake.
- Type:
The stream(roi=…) rectangle, as `{“rect”
- Type:
(x0, y0, width, height), “applied”
- show(representation=None, *, decay_ms=None, colormap='viridis', normalize=True, dt_ms=30.0, refresh_hz=60.0)¶
Opens the interactive live viewer. With no representation, shows the raw event stream (polarity dots with exponential decay — the default view). Pass a representation name (“count”, “tencode”, “voxel”, …) to render that live instead, “raw” to force the raw view, or “aps” to watch the sensor’s own greyscale video (DAVIS only — every other camera shows nothing, having no APS array). The event view shares the recording player’s accumulation, refresh, statistics, and processor controls; its bounded freshness queue keeps capture and record= running while display is paused. Blocks until the window is closed. decay_ms tunes the raw fade; colormap / normalize apply to representations and APS.
- class eventcv.EventSink(path, *, format=None, compression=None)¶
Bases:
objectStreams EventStream windows into an event file — the event-level twin of FrameSink. Where FrameSink appends computed representations, this appends the raw events, so a live camera (or any window source) can be recorded to disk continuously without holding the whole session in memory. The sensor size and time base are taken from the first appended stream, and the file reads straight back with eventcv.open / eventcv.load.
Every event format eventcv.save writes can be appended this way, chosen by the path’s extension exactly as save chooses it: .h5, .npz, .txt/.csv, .bag, .aedat, .aedat4, .dat, .raw and E2VID’s .zip. What differs is when the file becomes readable — HDF5, text, AEDAT and the Prophesee formats append straight to disk, while npz and rosbag have a header or an index to write at the end and are only complete once finish() runs.
Behind a Mutex only to satisfy pyo3’s Sync requirement on a #[pyclass]: a sink is a single writer by construction, and the zip encoder E2VID’s export uses is Send but not Sync.
- append(stream)¶
Appends one EventStream window to the end of the file. Empty windows are a no-op.
- finish()¶
Flushes and closes the file; further appends raise. Idempotent.
- flush()¶
Forces buffered events out to disk without closing, so a crash mid-recording keeps everything appended so far. Call periodically during a long capture. For npz and rosbag the bytes go out but the file stays incomplete until finish().
- class eventcv.FrameSink(path)¶
Bases:
objectStreams EventFrame`s into one extendable `[N, C, H, W] HDF5 dataset — for writing a representation window-by-window over a whole recording. The dtype and [C, H, W] shape are fixed by the first appended frame.
- append(frame)¶
Appends one frame as the next slice; every frame must match the first’s dtype + shape.
- finish()¶
Flushes and closes the file; further appends raise. Idempotent.
FEAST feature learning¶
FEAST is an unsupervised, online event-feature extractor (Afshar et al.,
Event-based Feature Extraction Using Adaptive Selection Thresholds,
Sensors 2020 /
arXiv:1907.07853). Unlike the stateless stream/frame ops,
it is a stateful, trainable model (scikit-learn-style fit/transform): fit adapts a set
of feature prototypes and their selection thresholds event-by-event, and transform then maps
each event to its nearest learned feature.
import eventcv as ecv
stream = ecv.load("recording.npz")
feast = ecv.FEAST(n_features=100, patch=11, tau_ms=30.0, per_polarity=True, seed=0)
feast.fit(stream, epochs=2) # online, unsupervised; returns the miss rate
ids = feast.transform(stream) # (N,) nearest-feature id per event (-1 at borders)
hist = feast.histogram(stream) # pooled feature-event counts (classifier input)
imgs = feast.feature_images() # (n_features_total, patch, patch) learned patches
ecv.save(feast, "model.npz") # persist the trained model
feast = ecv.load_feast("model.npz") # ...and reload it
- class eventcv.FEAST(n_features=100, patch=11, tau_ms=None, eta=0.001, delta_i=0.001, delta_e=0.003, per_polarity=True, seed=0, *, tau_s=None, tau_us=None, tau_ns=None)¶
Bases:
objectFEAST feature extractor (Afshar et al., 2020) — an unsupervised, online, sklearn-style model. fit adapts feature weights and selection thresholds event-by-event (repeatable across recordings/epochs); transform then maps each event to its nearest feature id.
- feature_images()¶
The learned features as an (n_features_total, patch, patch) float32 array — reproduces the paper’s feature grids for visualisation.
- fit(stream, epochs=1)¶
Trains on stream for epochs online passes; returns the final-epoch miss rate (the fraction of in-bounds events matching no feature), which settles low as the network converges. Call repeatedly to train across multiple recordings.
- get_params()¶
The constructor parameters as a dict (sklearn-style); consumed by eventcv.save.
- histogram(stream)¶
Pooled feature-event counts over stream (the paper’s classifier input). Shape (n_features_total,), dtype uint32.
- missed_rate¶
Miss rate recorded by the most recent fit (0.0 before any training).
- thresholds¶
The current selection thresholds, shape (n_features_total,), dtype float32.
- transform(stream)¶
Feature id for every event (nearest feature by cosine distance; -1 for border events). Shape (len(stream),), dtype int32; ids are population * n_features + feature.
Live camera streaming¶
eventcv.stream(...) opens a USB event camera (Prophesee EVK4/EVK3-HD, iniVation
DVXplorer/DAVIS346, CenturyArks) as a live EventCamera — the streaming twin of
open(). It yields the same EventStream windows the file readers do,
so every representation, transform, feature detector, and viewer composes on a live feed:
import eventcv as ecv
with ecv.stream(dt_ms=50, repr="mcts", record="session.h5") as cam:
while running:
infer(cam.read().numpy()) # a representation per window; raw events archived as you go
record() is the one-shot form for scripts that only want a file — it opens the
camera, captures for seconds, and closes it before returning, so the recording is complete and the
device free by the next line:
ecv.record("session.h5", seconds=10)
reader = ecv.open("session.h5", dt_ms=50)
Windowing mirrors open (dt_ms or max_events), repr= and its options render each window,
record= archives the raw events, latest=True keeps a slow loop on live data,
max_event_rate / roi cap what the sensor emits in hardware, and mask= restricts it to an
arbitrarily shaped region of interest on the host. See the
streaming guide for the full picture — recording, staying live under load, source
caps, and troubleshooting. These functions are built into wheels that include camera support; on
Linux the camera needs udev rules for non-root USB access.
Adaptive biasing¶
Fixed biases make the same scene produce wildly different event rates as the light changes.
adaptive_bias=True measures the rate and retunes the sensor’s bias currents as it runs, after
Nair et al., Enhancing Visual Place Recognition via Fast and Slow Adaptive Biasing in Event
Cameras (IROS 2024) — a fast loop mapping the rate onto the refractory period several times a
second, and a slow loop shifting the photoreceptor and threshold biases when the fast one runs out
of travel. It starts from the camera’s stock biases, so enabling it never jumps the picture, and
bias_state reports what it is doing.
with ecv.stream(dt_ms=50, adaptive_bias={"target_rate": (3e4, 1.2e5)}) as cam:
while running:
infer(cam.read().numpy())
print(cam.bias_state) # event_rate, the five bias values, authority, n_slow_steps
It measures your scene first. For about the first second the controller changes nothing and
just watches, then centres its band on the rate the camera was actually producing — so
adaptive_bias=True means “hold the event rate wherever this scene started”, with no numbers to
pick. Asking for a rate a scene cannot supply at any bias setting is the one way to get bad results
(it drives the sensor into amplifying its own noise), and measuring first avoids it. Pass
target_rate only for a specific absolute rate; that skips the measurement. bias_state reports
the band chosen, whether it is still calibrating, and an authority of "hunting" if the band
turns out to be unreachable anyway.
Supported on the iniVation DAVIS346 and the Prophesee EVK4, each with its own defaults. Measured on a DAVIS346 over a static indoor scene, the median rate held within 1.2x across runs where the unbiased camera wandered 5.7x; on an EVK4, 1.8x against 4.2x. Other cameras raise rather than silently doing nothing.
- eventcv.stream(serial: str | None = None, *, dt_ms: float | None = None, dt_s: float | None = None, dt_us: float | None = None, dt_ns: float | None = None, max_events: int | None = None, repr: str | None = None, bins: int | None = None, window_ms: float | None = None, window_s: float | None = None, window_us: float | None = None, window_ns: float | None = None, tau_ms: float | None = None, tau_s: float | None = None, tau_us: float | None = None, tau_ns: float | None = None, max_window_ms: float | None = None, max_window_s: float | None = None, max_window_us: float | None = None, max_window_ns: float | None = None, window: int | None = None, normalize: bool | None = None, pct: float | None = None, white_frame: bool | None = None, record: str | None = None, compression: int | None = None, latest: bool = False, max_event_rate: float | None = None, roi: tuple[int, int, int, int] | None = None, mask=None, adaptive_bias: bool | dict | None = None, decay_ms: float | None = None, decay_s: float | None = None, decay_us: float | None = None, decay_ns: float | None = None, frames: bool = False, imu: bool = False) EventCamera[source]¶
Open a live USB event camera — the streaming twin of
open().Where
open()turns a file into sliceableEventReaderwindows,streamturns a camera into a liveEventCamerathat yields the very sameEventStreamwindows — so every representation, transform, feature detector, and viewer in eventcv composes on a live feed exactly as it does on a recording.serialselects a specific device (fromlist_cameras());Noneopens the first found. Windowing mirrorsopen(): passdt_msfor fixed-duration windows ormax_eventsfor a fixed event count (mutually exclusive; defaultdt_ms=30). Only non-empty windows are yielded, so a loop never spins on idle time.Pass
repr(a representation name —"count","voxel","tencode", …) to make iteration (andEventCamera.read()) yield renderedEventFrames instead of raw streams, mirroringopen(repr=…). The per-representation options are the same onesEventReader.with_repr()takes —bins,window_ms,tau_ms,max_window_ms,window(for"flow"),normalize,pct, andwhite_frame.decay_mssets the fade time constant of the rawEventCamera.show()view.Close the camera when you are done with it. The device and any
record=file are released when the camera is closed — byEventCamera.close(), by leaving awithblock, or, if you do neither, whenever Python happens to collect the object. Prefer the first two, and preferrecord()overstream(...).record(...)when a script only wants a file:with ecv.stream(dt_ms=50) as cam: # closed on the way out ... ecv.record("session.h5", seconds=10) # opened, recorded, and closed in one call
Time spans follow the capture window. An unset
window_ms/tau_ms/max_window_msdefaults todt_ms, so a live representation covers exactly the events it was handed:stream(dt_ms=50, repr="tencode")encodes the full 50 ms instead of the 30 ms default (which would silently discard the oldest 20 ms of every window). Set one explicitly to override —stream(dt_ms=50, repr="tencode", window_ms=20)keeps only the newest 20 ms. Inmax_eventsmode there is no fixed duration, so the 30 ms defaults stand.Pass
record(a path in any event formatsave()writes —.h5,.npz,.txt,.csv,.bag,.aedat,.aedat4,.dat,.raw) to archive the session while you work: every window the loop reads has its raw events appended to that file first, so arepr=loop processes representations live and still keeps the full-resolution recording for later. Writing happens in Rust as each window is decoded — no per-window Python round trip — and is flushed about once a second, so a crash keeps everything up to a second ago.Formats differ in when the file becomes readable, which matters for a capture that might be interrupted: HDF5, text, AEDAT and the Prophesee formats append straight to disk and are a valid recording after every window, while npz and rosbag have a header or an index to write at the end and are only complete once the camera is closed.
compressionis an optional gzip level (0..=9) for HDF5; omit it for the fastest writes. The file is closed byEventCamera.close()or thewithblock, andEventCamera.n_recordedcounts what has been written. Iteration and the event GUI both keep this background recorder running;EventCamera.record()is the blocking alternative when no interactive view or loop is needed.Capping the source. Every event costs time to decode, window, and render, so the cheapest event is one the camera never sends.
max_event_rate(events per second) enables the sensor’s on-chip event-rate controller, androi=(x0, y0, width, height)masks every pixel outside that rectangle, so neither costs the host anything. A saturating scene on a 1280×720 sensor can emit far more than one core can decode, and capping the source is the only fix that keeps the events you do get contiguous rather than punched full of dropout holes:ecv.stream(dt_ms=50, max_event_rate=40_000_000) # 40 Mev/s ceiling, enforced on-chip ecv.stream(dt_ms=50, roi=(320, 180, 640, 360)) # centre quarter only
Both are on-chip on the sensors built around Prophesee’s pipeline — the EVK4, the EVK3 HD, and the CenturyArks VGA. The iniVation cameras (DVXplorer, DAVIS346) have neither, so there
roi=falls back to a host-side mask (the same events are dropped, but only after crossing the cable and being decoded) and warns that it did;max_event_ratestill raises, since capping the rate on the host would save none of the work it exists to avoid.EventCamera.roireports the rectangle and whether it was"hardware"or"host".Region of interest.
masktakes an arbitrarily shaped ROI — an(H, W)boolean array (or 8-bit map, where non-zero keeps the pixel) covering the sensor. Events outside it are dropped as they are decoded, so they never reach arecord=file, the windows your loop reads, orEventCamera.show(), and they cost nothing downstream. Whereroi=is a rectangle fixed at open,maskis any shape and can be changed while the camera runs — so it suits sensors whose useful data is a circle:aperture = ecv.circle_mask((640, 480), cx=320, cy=240, r=230) with ecv.stream(dt_ms=50, mask=aperture, record="session.h5") as cam: for events in cam: # masked already, as is the recording track(events)
Set
EventCamera.maskto change it mid-session (Noneclears it), or draw one over the live view withEventCamera.draw_mask(). Build masks withcircle_mask(),ellipse_mask(),rect_mask(),polygon_mask(), or load one withload_mask().Adaptive biasing. A camera left on fixed biases produces wildly different event rates as the light changes — the same scene can starve at dusk and saturate in sun, and anything downstream that was tuned on one no longer works on the other.
adaptive_bias=Truecloses the loop: eventcv measures the event rate and retunes the sensor’s bias currents as it runs, so the stream stays comparable across conditions. Two loops do it, after Nair et al., Enhancing Visual Place Recognition via Fast and Slow Adaptive Biasing in Event Cameras (IROS 2024) — a fast one that maps the rate onto the refractory period several times a second, and a slow one that shifts the photoreceptor and threshold biases whenever the fast one runs out of room:ecv.stream(adaptive_bias=True) # the paper's tuning ecv.stream(adaptive_bias={"target_rate": (2e5, 1e6)}) # aim for a quieter stream
It starts from whatever biases the camera is already running (its stock configuration) and adjusts from there, so turning it on never jumps the picture. Pass a dict to override any of
period_ms,target_rate(a(low, high)band in events/second),throttle_range,max_slew,patience,step, orlimits;EventCamera.bias_statereports what the controller is doing, including anauthorityof"hunting"when the band you asked for is not reachable at all. Supported on the iniVation DAVIS346 and the Prophesee EVK4; other cameras raise rather than silently doing nothing.Defaults are per sensor, so
adaptive_bias=Truemeans something sensible on either — the EVK4 has 10x the pixels and plain byte registers rather than the DAVIS346’s 2041-step current ladder, so its rates, step sizes and bounds all differ. Anything you pass overrides just that field and leaves the rest to the camera.limitstakes either one(low, high)for every bias or a dict naming them (photoreceptor,follower,on_threshold,off_threshold); the per-bias form matters on an IMX636, whose ON and OFF thresholds must stay on opposite sides of itsdiffreference.It measures your scene first. For about the first second the controller changes nothing and just watches, then centres its target band on the rate the camera was actually producing. So
adaptive_bias=Truemeans “hold the event rate wherever this scene started” — the reference condition is whatever it saw at startup, and the loops then work to keep later conditions looking like it. That is what you want for consistency across lighting, and it avoids the trap of asking for a rate the scene cannot supply at any bias setting, which would drive the sensor into the regime where it is amplifying its own noise.Pass
target_rateonly when you need a specific absolute rate; doing so skips the measurement.bias_statereports the band that was chosen, andcalibratingwhile it is still measuring:ecv.stream(adaptive_bias=True) # hold this scene's rate ecv.stream(adaptive_bias={"target_rate": (3e4, 1.2e5)}) # hold this exact band ecv.stream(adaptive_bias={"calibrate": 20}) # measure for longer first
This complements
max_event_raterather than replacing it: the rate limiter is a hard on-chip ceiling that discards events once you exceed it, while adaptive biasing changes what the pixels produce in the first place, so the events you keep stay evenly spread rather than clipped.Decoding, and any
record=writing, run on a background thread that owns the camera, so the driver’s ring is drained continuously no matter what your loop is doing — the loop only collects windows that are already decoded. On a Prophesee EVK4 atdt_ms=50this leaves ~80% of each window’s wall-clock budget free for your own work: 40 ms of per-frame processing still holds 19.9 of an ideal 20 fps with the ring empty. The event GUI uses this same pump;EventCamera.record(),EventCamera.draw_mask(), APS viewing, andEventCamera.close()take the camera back directly.Windows are delivered in order, so a loop whose per-window work is slower than the camera still falls behind — the thread buffers a few windows, then applies backpressure, and under sustained overload the driver’s ring is what finally overflows (watch
EventCamera.n_overflows). Passlatest=Trueto trade completeness for freshness instead: reads hand back the newest decoded window and drop what it overtook. Buffers queued behind the freshest one are skimmed — decoded far enough to keep the sensor’s stream state exact, then discarded without being built into windows — which is both cheaper than building results nobody reads and, more importantly, keeps the driver’s ring drained. That matters because a full ring backs pressure up into the sensor, where events are lost with nothing able to count them. Two counters say what went:EventCamera.n_skippedcounts whole windows passed over andEventCamera.n_skimmed_eventscounts events discarded before a window was built. Both are exact;EventCamera.n_overflowsremains the part the driver dropped, which only it sees.Skimming is off while
record=is open, because the file is promised every window and a skimmed buffer’s events are gone before the recorder could archive them. With a recording attached the loop still gets the newest window and the file still gets all of them, at the old cost — asking for a complete recording is asking for complete decoding to go with it.The returned camera is a context manager and an iterator:
# Live raw event view (the default) — polarity dots with exponential decay: eventcv.stream().show() # 30 ms window, up to 60 refreshes/s # Live representation view: eventcv.stream(dt_ms=30).show("count", refresh_hz=60) # Iterate windows and run any eventcv op on the live feed: with eventcv.stream(dt_ms=30) as cam: for events in cam: # each `events` is a 30 ms EventStream corners = events.efast() if done: break # A `while` loop that returns one representation per time window — `read()` blocks for # the next window (here one MCTS frame per ~50 ms of events): cam = eventcv.stream(dt_ms=50, repr="mcts") while running: frame = cam.read() # EventFrame (mcts); use max_events=N for count windows infer(frame.numpy()) # Process representations live *and* archive the raw events in the same loop: with eventcv.stream(dt_ms=50, repr="mcts", record="session.h5") as cam: while running: infer(cam.read().numpy()) # raw events for this window are already on disk # `latest=True` keeps slow work on live data — the file still gets every window: with eventcv.stream(dt_ms=30, repr="count", record="session.h5", latest=True) as cam: while running: slow_inference(cam.read().numpy()) print(cam.n_skipped, "windows skipped,", cam.n_recorded, "events recorded") # Record continuously, straight to disk (window-by-window, never buffering the whole # session), stopping after 10 s or on Ctrl+C. `eventcv.record` is the one-shot form of # this and closes the camera for you: eventcv.record("session.h5", seconds=10) # Or drive the recorder yourself with an EventSink, mixing capture and processing: with eventcv.stream(dt_ms=50) as cam, eventcv.EventSink("session.h5") as sink: for events in cam: sink.append(events) # to disk track(events) # ...and process the same window live
APS frames and IMU. A DAVIS produces a greyscale video and an IMU stream alongside its events. Pass
frames=True/imu=Trueto collect them, then take them withEventCamera.read_frames()andEventCamera.read_imu()— the same shapesread_frames()andread_imu()return for a recording, so code written against a file works unchanged on a live camera:with ecv.stream(dt_ms=50, frames=True, imu=True) as cam: for events in cam: for t_us, frame in cam.read_frames(): # EventFrame, uint16 greyscale show(frame.numpy()) gyro = cam.read_imu()["angular_velocity"] ecv.stream(frames=True).show("aps") # watch the APS video live
Both are off by default: copying a frame out of every decode buffer is not free, and other sensors have neither stream. Collect them as you read — only the most recent few dozen frames and a few thousand samples are held, and the oldest are dropped rather than buffered forever.
Requires a build with camera support and, on Linux, udev rules for USB access.
- eventcv.record(path: str, *, seconds: float | None = None, serial: str | None = None, dt_ms: float | None = None, dt_s: float | None = None, dt_us: float | None = None, dt_ns: float | None = None, max_events: int | None = None, compression: int | None = None, max_event_rate: float | None = None, roi: tuple[int, int, int, int] | None = None, mask=None, adaptive_bias: bool | dict | None = None) int[source]¶
Record a camera to
pathin one call, and return the number of events saved.The one-shot form of
stream(...).record(...), and the one to reach for when a script only wants a file: the camera is opened, captured from forseconds(or untilCtrl+C), and closed before this returns — so the recording is complete and the device is free by the time the next line runs:ecv.record("session.h5", seconds=10) reader = ecv.open("session.h5", dt_ms=50) # safe: nothing is still writing
The format follows the extension, as in
save(), and every one of them is written continuously, window-by-window — a recording is never held in memory, however long it runs.compressionis an optional gzip level (0..=9) for HDF5. npz and bag are the two that only become readable once the recording closes, since both have something to write at the end.Everything
stream()takes about what the sensor sends applies —serial,dt_ms/max_events,roi,mask,max_event_rate,adaptive_bias. The representation and viewer options do not, since nothing is displayed. Open a camera withstream()instead when you want to process windows as they arrive.
- eventcv.list_cameras() list[dict][source]¶
List every connected, supported USB event camera.
Returns one dict per device with keys
kind(machine name, e.g."prophesee_evk4"),name(model),serial,bus,address, andspeed. A device’sserialcan be passed tostream()to select it when several are attached; an empty list means no supported camera was found.Asking never fails. An empty list is the answer on a machine with no camera, in a container without USB passthrough, or on a CI runner with no USB subsystem at all — none of which are errors. The only exception raised is when the build has no camera support compiled in.
On Linux, accessing the device needs udev rules. If a camera is plugged in but not listed, this emits a
RuntimeWarningsaying so — install the rules (see the neuromorphic-drivers README) and re-plug. It is a warning rather than an error because the list is genuinely empty either way; the warning only tells you why.
Functional (OpenCV-style) API¶
Each function below forwards to the identically named method on the object passed as its
first argument — e.g. eventcv.voxel(stream, bins=5) is stream.voxel(bins=5). They are
generated by introspecting the compiled types, so this list always matches the methods
above. Applying a stream op to an EventReader — e.g.
eventcv.hot_pixel_filter(reader) — returns a new lazy reader that applies the op to every
slice it yields (identical to reader.hot_pixel_filter()), so filtering/geometry compose
with slice/windows/with_repr without loading the file.
- class eventcv.SimulationResult¶
Bases:
objectWhat simulate(…, out=…) returns in place of an EventStream: the file it wrote and what went into it. len(result) is the event count.
- events¶
Events written.
- frames¶
Source frames processed.
- path¶
The file that was written.
- eventcv.circle_mask(sensor_size: tuple[int, int], cx: float, cy: float, r: float)[source]¶
Build a circular ROI mask of radius
rcentred on(cx, cy).Returns an
(H, W)boolean NumPy array —Truewhere events are kept — ready forEventStream.mask(),EventReader.mask(), orecv.stream(mask=…). Note the two orders:sensor_sizeis(width, height)like everywhere else in eventcv, while the array that comes back is(H, W)like every other NumPy image.Masks are plain arrays, so they compose with NumPy’s own operators —
|unions,&intersects,~inverts:aperture = ecv.circle_mask((640, 480), cx=320, cy=240, r=230) aperture &= ~ecv.rect_mask((640, 480), 0, 0, 64, 64) # minus a corner ecv.load("rec.npz").mask(aperture)
Coordinates are continuous, not pixel indices: a pixel is kept when its centre falls inside the shape, and geometry reaching off the sensor is clamped. See also
ellipse_mask(),rect_mask(),polygon_mask(),load_mask(), and — to draw one by hand —EventStream.draw_mask()/EventCamera.draw_mask().
- eventcv.bag_topics(path: str) list[tuple[str, str]][source]¶
List every topic in a ROS bag with its message type.
Bags disagree about names — a DAVIS recorded through
dvs_ros_driverpublishes/dvs/eventswhereload()defaults to/davis/left/events— so this is usually the first thing to call on an unfamiliar file:>>> ecv.bag_topics("recording.bag") [('/dvs/camera_info', 'sensor_msgs/CameraInfo'), ('/dvs/events', 'dvs_msgs/EventArray'), ('/dvs/image_raw', 'sensor_msgs/Image'), ('/dvs/imu', 'sensor_msgs/Imu')]
- eventcv.ellipse_mask(sensor_size: tuple[int, int], cx: float, cy: float, rx: float, ry: float)[source]¶
Build an elliptical ROI mask centred on
(cx, cy)with semi-axesrx,ry.The axis-independent form of
circle_mask(); see it for the conventions.
- eventcv.load_mask(path: str)[source]¶
Read an ROI mask from a
.pngas an(H, W)boolean array.A pixel is kept where the image is non-black and not fully transparent, so a mask binarised in any other tool loads as-is — greyscale, palette, or colour, with or without alpha:
ecv.stream(dt_ms=50, mask=ecv.load_mask("aperture.png"))
The counterpart of
save_mask().
- eventcv.polygon_mask(sensor_size: tuple[int, int], points)[source]¶
Build an ROI mask keeping the interior of the closed polygon through
points.pointsis a sequence of(x, y)vertices; the last joins back to the first. Filled by the even-odd rule, so a self-intersecting outline leaves holes. Seecircle_mask()for the conventions.
- eventcv.read_camera_info(source, *, topic: str | None = None)[source]¶
Read camera intrinsics from a recording, or
Noneif it carries none.Returns a
Cameraready to pass tocontrast_maximise(model="rotation", camera=…), which needs intrinsics to map pixels onto rays.sourceis a path or an openEventReader; only ROS bags (sensor_msgs/CameraInfo) record intrinsics today. Distortion coefficients are not applied — build aCamerayourself from the bag’sDif you need undistortion.
- eventcv.read_frames(source, *, topic: str | None = None, t0_us: int = 0, t1_us: int | None = None) list[tuple[int, EventFrame]][source]¶
Read DAVIS APS frames as
[(t_us, EventFrame), …].The greyscale images a DAVIS captures alongside its events — the reference the simulator is calibrated against, since the frames and the events see the same scene through the same lens.
sourceis a path or an openEventReader; ROS bags and AEDAT 2.0 recordings carry frames, and any other format returns an empty list. Frames come back at the sensor’s own precision, soframe.numpy()isuint8from a bag (ROSmono8) anduint16from AEDAT (a 10-bit ADC).topicselects the bag topic and defaults to its onlysensor_msgs/Imagetopic when there is exactly one; it does not apply to other formats. The time window matters: these recordings run to tens of gigabytes, and whole chunks outside it are skipped without being decoded. From a bag onlymono8/rgb8/bgr8are supported; any other encoding raises rather than being misread.
- eventcv.read_imu(source, *, topic: str | None = None, t0_us: int = 0, t1_us: int | None = None) dict[source]¶
Read IMU samples as a dict of arrays.
Keys are
t(µs),angular_velocityandlinear_acceleration, the latter two[N, 3]in rad/s and m/s². On a DAVIS this is the only independent measurement of how the camera moved, which makes it the ground truth forcontrast_maximise():imu = ecv.read_imu("recording.bag", t0_us=t0, t1_us=t1) truth = imu["angular_velocity"].mean(axis=0)
sourceis a path or an openEventReader; ROS bags and AEDAT 2.0 recordings carry an IMU, and any other format returns empty arrays.Orientation is not returned: on a DAVIS it is dead-reckoned from this same gyro, so it is not an independent quantity. Temperature is not returned either.
- eventcv.record(path: str, *, seconds: float | None = None, serial: str | None = None, dt_ms: float | None = None, dt_s: float | None = None, dt_us: float | None = None, dt_ns: float | None = None, max_events: int | None = None, compression: int | None = None, max_event_rate: float | None = None, roi: tuple[int, int, int, int] | None = None, mask=None, adaptive_bias: bool | dict | None = None) int[source]¶
Record a camera to
pathin one call, and return the number of events saved.The one-shot form of
stream(...).record(...), and the one to reach for when a script only wants a file: the camera is opened, captured from forseconds(or untilCtrl+C), and closed before this returns — so the recording is complete and the device is free by the time the next line runs:ecv.record("session.h5", seconds=10) reader = ecv.open("session.h5", dt_ms=50) # safe: nothing is still writing
The format follows the extension, as in
save(), and every one of them is written continuously, window-by-window — a recording is never held in memory, however long it runs.compressionis an optional gzip level (0..=9) for HDF5. npz and bag are the two that only become readable once the recording closes, since both have something to write at the end.Everything
stream()takes about what the sensor sends applies —serial,dt_ms/max_events,roi,mask,max_event_rate,adaptive_bias. The representation and viewer options do not, since nothing is displayed. Open a camera withstream()instead when you want to process windows as they arrive.
- eventcv.rect_mask(sensor_size: tuple[int, int], x0: float, y0: float, width: float, height: float)[source]¶
Build a rectangular ROI mask keeping the
width``×``heightbox at(x0, y0).Unlike
EventStream.crop(), which shrinks the sensor grid, a mask keeps the coordinates (and the sensor size) unchanged and only drops events. Seecircle_mask()for the conventions.
- eventcv.save_mask(mask, path: str) None[source]¶
Write an ROI mask to an 8-bit greyscale
.png— white keeps, black drops.Lets an ROI drawn or computed once be reused across sessions (and checked in any image viewer). Read it back with
load_mask().
- eventcv.atsurf(obj, *args, **kwargs)¶
Averaged time surface — the per-pixel mean of exp(-age/tau_ms) over all events
Free-function form of
obj.atsurf(*args, **kwargs).
- eventcv.background_activity_filter(obj, *args, **kwargs)¶
Background-activity (nearest-neighbour) noise filter: keeps an event only if a
Free-function form of
obj.background_activity_filter(*args, **kwargs).
- eventcv.batch(obj, *args, **kwargs)¶
Renders slice indices into one dense [B, C, H, W] array — the explicit-batch path
Free-function form of
obj.batch(*args, **kwargs).
- eventcv.concat(obj, *args, **kwargs)¶
Concatenates this stream with others (argument order; sensor = element-wise max).
Free-function form of
obj.concat(*args, **kwargs).
- eventcv.connected_components(obj, *args, **kwargs)¶
Connected-component labelling (Phase 5): treats any non-zero pixel as foreground and labels
Free-function form of
obj.connected_components(*args, **kwargs).
- eventcv.contrast_maximise(obj, *args, **kwargs)¶
Recovers the camera motion that makes the warped events sharpest.
Free-function form of
obj.contrast_maximise(*args, **kwargs).
- eventcv.count(obj, *args, **kwargs)¶
Event-count image — one channel of total events per pixel (both polarities). Raw
Free-function form of
obj.count(*args, **kwargs).
- eventcv.countmask(obj, *args, **kwargs)¶
Count-mask image (GEPT, Sec. 3.2) — red holds the per-pixel positive-event count, blue the
Free-function form of
obj.countmask(*args, **kwargs).
- eventcv.crop(obj, *args, **kwargs)¶
Keeps events inside the w`×`h window at (x0, y0), shifted to a new origin.
Free-function form of
obj.crop(*args, **kwargs).
- eventcv.decimate(obj, *args, **kwargs)¶
Keeps every k-th event by index.
Free-function form of
obj.decimate(*args, **kwargs).
- eventcv.draw_mask(obj, *args, **kwargs)¶
Draws a region of interest over this stream and returns it as an (H, W) boolean mask —
Free-function form of
obj.draw_mask(*args, **kwargs).
- eventcv.dt(obj, *args, **kwargs)¶
The fixed slice duration set at open, in unit (“s”, “ms”, “us”, “ns”), or
Free-function form of
obj.dt(*args, **kwargs).
- eventcv.duration(obj, *args, **kwargs)¶
How long the recording runs, in unit (“s”, “ms”, “us”, “ns”).
Free-function form of
obj.duration(*args, **kwargs).
- eventcv.efast(obj, *args, **kwargs)¶
eFAST event corner detector (Mueggler et al., BMVC 2017). Keeps the events sitting on a
Free-function form of
obj.efast(*args, **kwargs).
- eventcv.event_drop(obj, *args, **kwargs)¶
Drops each event independently with probability p.
Free-function form of
obj.event_drop(*args, **kwargs).
- eventcv.event_rate(obj, *args, **kwargs)¶
Activity over time, binned into fixed intervals.
Free-function form of
obj.event_rate(*args, **kwargs).
- eventcv.filter_polarity(obj, *args, **kwargs)¶
Keeps only events of the given polarity (nonzero / True = ON, 0 / False = OFF).
Free-function form of
obj.filter_polarity(*args, **kwargs).
- eventcv.flatten(obj, *args, **kwargs)¶
Calls
obj.flatten(...).Free-function form of
obj.flatten(*args, **kwargs).
- eventcv.flip_x(obj, *args, **kwargs)¶
Mirrors horizontally (x → width-1-x).
Free-function form of
obj.flip_x(*args, **kwargs).
- eventcv.flip_y(obj, *args, **kwargs)¶
Mirrors vertically (y → height-1-y).
Free-function form of
obj.flip_y(*args, **kwargs).
- eventcv.harris_corners(obj, *args, **kwargs)¶
Harris corner score on the Surface of Active Events: keeps events whose normalised Harris
Free-function form of
obj.harris_corners(*args, **kwargs).
- eventcv.hot_pixel_filter(obj, *args, **kwargs)¶
Hot-pixel removal: drops pixels whose event count exceeds mean + n_std·std over the
Free-function form of
obj.hot_pixel_filter(*args, **kwargs).
- eventcv.invert_polarity(obj, *args, **kwargs)¶
Flips every event’s polarity.
Free-function form of
obj.invert_polarity(*args, **kwargs).
- eventcv.iwe(obj, *args, **kwargs)¶
Builds the image of warped events for an explicit motion — the picture the objective scores.
Free-function form of
obj.iwe(*args, **kwargs).
- eventcv.mask(obj, *args, **kwargs)¶
Keeps events inside the region of interest: a (H, W) boolean mask (or 8-bit map, where
Free-function form of
obj.mask(*args, **kwargs).
- eventcv.mcts(obj, *args, **kwargs)¶
Multi-channel time surface (SuperEvent) — per polarity, one linear-decay channel per time
Free-function form of
obj.mcts(*args, **kwargs).
- eventcv.normalize_time(obj, *args, **kwargs)¶
Shifts timestamps so the earliest event starts at zero.
Free-function form of
obj.normalize_time(*args, **kwargs).
- eventcv.numpy(obj, *args, **kwargs)¶
Calls
obj.numpy(...).Free-function form of
obj.numpy(*args, **kwargs).
- eventcv.optical_flow(obj, *args, **kwargs)¶
Dense Lucas-Kanade optical flow on the time surface.
Free-function form of
obj.optical_flow(*args, **kwargs).
- eventcv.pixel_dropout(obj, *args, **kwargs)¶
Silences a random p fraction of pixels for the whole stream (dead-pixel simulation).
Free-function form of
obj.pixel_dropout(*args, **kwargs).
- eventcv.pset(obj, *args, **kwargs)¶
Calls
obj.pset(...).Free-function form of
obj.pset(*args, **kwargs).
- eventcv.random_crop(obj, *args, **kwargs)¶
Takes a random width × height crop; a window at least as large as the sensor is a no-op.
Free-function form of
obj.random_crop(*args, **kwargs).
- eventcv.random_flip_x(obj, *args, **kwargs)¶
Mirrors left-right with probability p.
Free-function form of
obj.random_flip_x(*args, **kwargs).
- eventcv.random_flip_y(obj, *args, **kwargs)¶
Mirrors top-bottom with probability p.
Free-function form of
obj.random_flip_y(*args, **kwargs).
- eventcv.random_polarity_flip(obj, *args, **kwargs)¶
Inverts every polarity with probability p (all events, not a random subset).
Free-function form of
obj.random_polarity_flip(*args, **kwargs).
- eventcv.raw_image(obj, *args, **kwargs)¶
The raw polarity image of this stream as an (H, W, 3) uint8 array — what
Free-function form of
obj.raw_image(*args, **kwargs).
- eventcv.redblue(obj, *args, **kwargs)¶
White-background red/blue counts, independently percentile-normalized per polarity.
Free-function form of
obj.redblue(*args, **kwargs).
- eventcv.refractory_filter(obj, *args, **kwargs)¶
Refractory-period filter: suppresses a pixel’s events for dt after it fires.
Free-function form of
obj.refractory_filter(*args, **kwargs).
- eventcv.resize(obj, *args, **kwargs)¶
Event-domain resize to a width`×`height grid (rebinned, not interpolated).
Free-function form of
obj.resize(*args, **kwargs).
- eventcv.rotate90(obj, *args, **kwargs)¶
Rotates by k * 90° clockwise (quarter turns swap the sensor dims).
Free-function form of
obj.rotate90(*args, **kwargs).
- eventcv.scale(obj, *args, **kwargs)¶
Scales the sensor by (sx, sy).
Free-function form of
obj.scale(*args, **kwargs).
- eventcv.slice(obj, *args, **kwargs)¶
One slice. With a positional index n (requires open(dt_ms=…) or
Free-function form of
obj.slice(*args, **kwargs).
- eventcv.slice_count(obj, *args, **kwargs)¶
Events whose index lies in [i0, i1) (clamped to the file). Rendered to the reader’s
Free-function form of
obj.slice_count(*args, **kwargs).
- eventcv.sort_by_time(obj, *args, **kwargs)¶
Returns a copy reordered by ascending timestamp (stable).
Free-function form of
obj.sort_by_time(*args, **kwargs).
- eventcv.spatial_jitter(obj, *args, **kwargs)¶
Jitters each event’s position by a gaussian offset of standard deviation sigma pixels.
Free-function form of
obj.spatial_jitter(*args, **kwargs).
- eventcv.tencode(obj, *args, **kwargs)¶
Calls
obj.tencode(...).Free-function form of
obj.tencode(*args, **kwargs).
- eventcv.time_jitter(obj, *args, **kwargs)¶
Jitters each event’s timestamp by a gaussian offset, re-sorting afterwards.
Free-function form of
obj.time_jitter(*args, **kwargs).
- eventcv.time_reversal(obj, *args, **kwargs)¶
Plays the stream backwards with probability p, inverting polarity to match.
Free-function form of
obj.time_reversal(*args, **kwargs).
- eventcv.time_scale(obj, *args, **kwargs)¶
Scales every timestamp by factor (rounded).
Free-function form of
obj.time_scale(*args, **kwargs).
- eventcv.time_shift(obj, *args, **kwargs)¶
Shifts every timestamp by dt. The positional form is microseconds; dt_s/dt_ms/dt_us/
Free-function form of
obj.time_shift(*args, **kwargs).
- eventcv.time_span(obj, *args, **kwargs)¶
The recording’s (first, last) timestamp in unit — “s”, “ms” (the default), “us”,
Free-function form of
obj.time_span(*args, **kwargs).
- eventcv.time_window(obj, *args, **kwargs)¶
Keeps events whose timestamp lies in the half-open window [t0, t1). Positional bounds are
Free-function form of
obj.time_window(*args, **kwargs).
- eventcv.translate(obj, *args, **kwargs)¶
Translates by (dx, dy); events shifted off the sensor are dropped.
Free-function form of
obj.translate(*args, **kwargs).
- eventcv.transpose(obj, *args, **kwargs)¶
Reflects across the main diagonal ((x, y) → (y, x)); swaps the sensor dims.
Free-function form of
obj.transpose(*args, **kwargs).
- eventcv.tsurf(obj, *args, **kwargs)¶
Calls
obj.tsurf(...).Free-function form of
obj.tsurf(*args, **kwargs).
- eventcv.undistort(obj, *args, **kwargs)¶
Rectifies events with a Camera’s intrinsics + distortion (lens undistortion).
Free-function form of
obj.undistort(*args, **kwargs).
- eventcv.undistort_point(obj, *args, **kwargs)¶
Maps a distorted pixel (u, v) to its undistorted location.
Free-function form of
obj.undistort_point(*args, **kwargs).
- eventcv.view(obj, *args, **kwargs)¶
Opens the interactive viewer on this stream. Pass a representation name to choose what
Free-function form of
obj.view(*args, **kwargs).
- eventcv.voxel(obj, *args, **kwargs)¶
Calls
obj.voxel(...).Free-function form of
obj.voxel(*args, **kwargs).
- eventcv.warp_affine(obj, *args, **kwargs)¶
Applies a 2×3 affine matrix [[a,b,c],[d,e,f]] (rounded, no interpolation).
Free-function form of
obj.warp_affine(*args, **kwargs).
- eventcv.warp_perspective(obj, *args, **kwargs)¶
Applies a 3×3 perspective (homography) matrix.
Free-function form of
obj.warp_perspective(*args, **kwargs).
- eventcv.windows(obj, *args, **kwargs)¶
Lazy iterator of consecutive windows: each is [start, start + span_ms) and
Free-function form of
obj.windows(*args, **kwargs).
- eventcv.with_repr(obj, *args, **kwargs)¶
Returns a new reader over the same file that renders each slice with repr (unset
Free-function form of
obj.with_repr(*args, **kwargs).