evutils 0.3.11__tar.gz → 0.3.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {evutils-0.3.11 → evutils-0.3.13}/.github/workflows/release.yaml +53 -0
- {evutils-0.3.11 → evutils-0.3.13}/CMakeLists.txt +2 -2
- {evutils-0.3.11 → evutils-0.3.13}/PKG-INFO +42 -3
- {evutils-0.3.11 → evutils-0.3.13}/README.md +35 -1
- {evutils-0.3.11 → evutils-0.3.13}/benchmarks/README.md +20 -6
- evutils-0.3.13/benchmarks/conftest.py +171 -0
- {evutils-0.3.11 → evutils-0.3.13}/benchmarks/readers.py +70 -25
- {evutils-0.3.11 → evutils-0.3.13}/benchmarks/test_compare.py +6 -4
- evutils-0.3.13/benchmarks/test_formats.py +187 -0
- evutils-0.3.13/benchmarks/test_read.py +54 -0
- {evutils-0.3.11 → evutils-0.3.13}/benchmarks/test_write.py +7 -5
- evutils-0.3.13/csrc/aer.c +70 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/dat.c +1 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/evt2.c +1 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/evt21.c +1 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/evt3.c +6 -0
- evutils-0.3.13/csrc/include/evutils/aer.h +55 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/compat.h +6 -0
- evutils-0.3.13/docs/source/formats.md +128 -0
- {evutils-0.3.11 → evutils-0.3.13}/docs/source/index.rst +1 -0
- {evutils-0.3.11 → evutils-0.3.13}/pyproject.toml +27 -2
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/augment/_drop.py +1 -1
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/chunking.py +5 -4
- evutils-0.3.13/src/evutils/io/_aedat.py +565 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_aer.py +82 -22
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_bin.py +3 -1
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_csv.py +7 -7
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_dat.py +7 -6
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_event_reader.py +70 -12
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_event_writer.py +7 -7
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_evt.py +9 -9
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_hdf5.py +98 -29
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_native_evt.py +82 -51
- evutils-0.3.13/src/evutils/io/_npz.py +303 -0
- evutils-0.3.13/src/evutils/io/_prefetch.py +147 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_source.py +13 -12
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/buffer.py +3 -3
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/common.py +11 -11
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/decoders.py +22 -3
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/processing/_masking.py +1 -1
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/random.py +1 -1
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_frame.py +7 -6
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_histogram.py +5 -4
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_timesurface.py +4 -3
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_tore.py +4 -3
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_voxel.py +2 -1
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/torch/__init__.py +2 -1
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/types.py +18 -17
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/utils/_checker.py +10 -10
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/open3d.py +1 -1
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/plot3d.py +7 -6
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/_base.py +5 -3
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/metavision.py +5 -4
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg.py +23 -17
- evutils-0.3.13/tests/aedat_synth.py +146 -0
- {evutils-0.3.11 → evutils-0.3.13}/tests/conftest.py +18 -5
- evutils-0.3.13/tests/conftest_utils.py +119 -0
- {evutils-0.3.11 → evutils-0.3.13}/tests/io/conftest.py +6 -4
- evutils-0.3.13/tests/io/test_aedat.py +145 -0
- evutils-0.3.13/tests/io/test_aer.py +98 -0
- {evutils-0.3.11 → evutils-0.3.13}/tests/io/test_csv.py +4 -3
- evutils-0.3.13/tests/io/test_dat.py +85 -0
- {evutils-0.3.11 → evutils-0.3.13}/tests/io/test_evt.py +152 -35
- evutils-0.3.13/tests/io/test_hdf5.py +159 -0
- evutils-0.3.13/tests/io/test_npz.py +99 -0
- evutils-0.3.13/tests/io/test_prefetch.py +147 -0
- evutils-0.3.13/tests/io/test_roundtrip_matrix.py +162 -0
- {evutils-0.3.11 → evutils-0.3.13}/tests/test_random.py +3 -3
- {evutils-0.3.11 → evutils-0.3.13}/tests/test_types.py +2 -2
- {evutils-0.3.11 → evutils-0.3.13}/tests/test_vis.py +1 -1
- evutils-0.3.11/benchmarks/conftest.py +0 -94
- evutils-0.3.11/benchmarks/test_fixed_formats.py +0 -119
- evutils-0.3.11/benchmarks/test_read.py +0 -38
- evutils-0.3.11/csrc/aer.c +0 -36
- evutils-0.3.11/csrc/include/evutils/aer.h +0 -35
- evutils-0.3.11/src/evutils/io/_aedat.py +0 -86
- evutils-0.3.11/src/evutils/io/_npz.py +0 -192
- evutils-0.3.11/tests/conftest_utils.py +0 -69
- evutils-0.3.11/tests/io/test_aer.py +0 -32
- evutils-0.3.11/tests/io/test_dat.py +0 -44
- evutils-0.3.11/tests/io/test_hdf5.py +0 -0
- evutils-0.3.11/tests/io/test_npz.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/.dockerignore +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/.github/workflows/test.yaml +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/.gitignore +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/.gitmodules +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/LICENSE +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/benchmarks/docker/Dockerfile.openeb +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/evutils.c +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/dat.h +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evt2.h +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evt21.h +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evt3.h +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evutils.h +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/parser.h +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/types.h +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/docker/Dockerfile +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/docs/Makefile +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/docs/make.bat +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/docs/source/_static/event_hexagon_broken.webp +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/docs/source/_templates/autoapi/python/module.rst +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/docs/source/benchmarks/README.md +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/docs/source/conf.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/scripts/generate_benchmark_table.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/scripts/generate_metadata.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/_jit.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/augment/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/dataset/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/encoders.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/processing/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/processing/_utils.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/utils/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/.git +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/.gitignore +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/LICENSE +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/README.md +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/base/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/base/base_model.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/data/.gitignore +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/image_reconstructor.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/model.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/submodules.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/unet.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/options/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/options/inference_options.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/pretrained/.gitignore +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/run_reconstruction.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/embed_reconstructed_images_in_rosbag.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/extract_events_from_rosbag.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/image_folder_to_rosbag.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/resample_reconstructions.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/__init__.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/event_readers.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/inference_utils.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/loading_utils.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/path_utils.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/timers.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/util.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/tests/io/test_bin.py +0 -0
- {evutils-0.3.11 → evutils-0.3.13}/uv.lock +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
name: Release & Publish Docs
|
|
2
2
|
|
|
3
|
+
concurrency:
|
|
4
|
+
group: release-pages-${{ github.ref }}
|
|
5
|
+
cancel-in-progress: false
|
|
6
|
+
|
|
3
7
|
on:
|
|
4
8
|
release:
|
|
5
9
|
types: [published]
|
|
@@ -141,3 +145,52 @@ jobs:
|
|
|
141
145
|
- name: Deploy to GitHub Pages
|
|
142
146
|
id: deployment
|
|
143
147
|
uses: actions/deploy-pages@v5
|
|
148
|
+
|
|
149
|
+
test_pypi_wheels:
|
|
150
|
+
name: Test PyPI Wheel (${{ matrix.os }}, Py ${{ matrix.python }})
|
|
151
|
+
needs: publish_pypi
|
|
152
|
+
runs-on: ${{ matrix.os }}
|
|
153
|
+
if: github.event_name == 'release'
|
|
154
|
+
strategy:
|
|
155
|
+
fail-fast: false
|
|
156
|
+
matrix:
|
|
157
|
+
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-14]
|
|
158
|
+
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
159
|
+
steps:
|
|
160
|
+
- name: Checkout repository
|
|
161
|
+
uses: actions/checkout@v7
|
|
162
|
+
with:
|
|
163
|
+
submodules: recursive
|
|
164
|
+
|
|
165
|
+
- name: Install uv
|
|
166
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
167
|
+
with:
|
|
168
|
+
enable-cache: false
|
|
169
|
+
|
|
170
|
+
- name: Set up Python
|
|
171
|
+
uses: actions/setup-python@v6
|
|
172
|
+
with:
|
|
173
|
+
python-version: ${{ matrix.python }}
|
|
174
|
+
|
|
175
|
+
- name: Install evutils from PyPI
|
|
176
|
+
shell: bash
|
|
177
|
+
run: |
|
|
178
|
+
# Retry loop to wait for PyPI index to update with the new release
|
|
179
|
+
# GITHUB_REF_NAME contains the tag name (e.g. v1.0.0). We strip the 'v' for pip.
|
|
180
|
+
VERSION=${GITHUB_REF_NAME#v}
|
|
181
|
+
for i in {1..12}; do
|
|
182
|
+
uv pip install --system "evutils[test]==$VERSION" --no-cache && break
|
|
183
|
+
echo "Waiting for PyPI index..."
|
|
184
|
+
sleep 10
|
|
185
|
+
done
|
|
186
|
+
|
|
187
|
+
- name: Remove local source tree
|
|
188
|
+
shell: bash
|
|
189
|
+
run: |
|
|
190
|
+
# Remove local source so Python is forced to import from the installed PyPI wheel
|
|
191
|
+
rm -rf src/evutils
|
|
192
|
+
|
|
193
|
+
- name: Run tests against PyPI wheel
|
|
194
|
+
shell: bash
|
|
195
|
+
run: |
|
|
196
|
+
python -m pytest -s -v tests/
|
|
@@ -40,8 +40,8 @@ endif()
|
|
|
40
40
|
# ctypes loader finds it next to evutils/__init__.py.
|
|
41
41
|
if(DEFINED SKBUILD_PROJECT_NAME)
|
|
42
42
|
install(TARGETS evutils_native
|
|
43
|
-
LIBRARY DESTINATION "${SKBUILD_PROJECT_NAME}"
|
|
44
|
-
RUNTIME DESTINATION "${SKBUILD_PROJECT_NAME}")
|
|
43
|
+
LIBRARY DESTINATION "${SKBUILD_PROJECT_NAME}/io"
|
|
44
|
+
RUNTIME DESTINATION "${SKBUILD_PROJECT_NAME}/io")
|
|
45
45
|
else()
|
|
46
46
|
# Standalone `cmake --build build` leaves the .so in build/ where the
|
|
47
47
|
# loader's dev-mode search also looks.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: evutils
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.13
|
|
4
4
|
Summary: Utilities for event-based vision
|
|
5
5
|
Keywords: events,dvs,neuromorphic,vision,event-based
|
|
6
6
|
Author-Email: Jakub Mandula <jakub.mandula@pbl.ee.ethz.ch>
|
|
@@ -31,6 +31,9 @@ Requires-Python: >=3.10
|
|
|
31
31
|
Requires-Dist: numpy>=2.0.0
|
|
32
32
|
Requires-Dist: opencv-python>=4.2
|
|
33
33
|
Requires-Dist: numba>=0.57
|
|
34
|
+
Provides-Extra: aedat
|
|
35
|
+
Requires-Dist: lz4>=4.0; extra == "aedat"
|
|
36
|
+
Requires-Dist: zstandard>=0.22; python_version < "3.14" and extra == "aedat"
|
|
34
37
|
Provides-Extra: pandas
|
|
35
38
|
Requires-Dist: pandas>=2.3.3; extra == "pandas"
|
|
36
39
|
Provides-Extra: torch
|
|
@@ -46,10 +49,12 @@ Requires-Dist: tqdm>=4.41; extra == "vis"
|
|
|
46
49
|
Provides-Extra: test
|
|
47
50
|
Requires-Dist: pytest>=9.0.2; extra == "test"
|
|
48
51
|
Requires-Dist: pytest-benchmark>=5.2.3; extra == "test"
|
|
52
|
+
Requires-Dist: zstandard>=0.22; python_version < "3.14" and extra == "test"
|
|
49
53
|
Provides-Extra: compare
|
|
50
54
|
Requires-Dist: expelliarmus>=1.1.0; extra == "compare"
|
|
51
55
|
Requires-Dist: evlib>=0.12.0; python_version >= "3.11" and extra == "compare"
|
|
52
56
|
Requires-Dist: evlib>=0.8.0; python_version < "3.11" and extra == "compare"
|
|
57
|
+
Requires-Dist: evt3>=0.3; extra == "compare"
|
|
53
58
|
Provides-Extra: docs
|
|
54
59
|
Requires-Dist: sphinx>=8.0.0; extra == "docs"
|
|
55
60
|
Requires-Dist: sphinx-autoapi>=3.6.1; extra == "docs"
|
|
@@ -59,7 +64,7 @@ Requires-Dist: sphinx_book_theme>=1.0.0; extra == "docs"
|
|
|
59
64
|
Requires-Dist: pydata_sphinx_theme>=0.19.0; extra == "docs"
|
|
60
65
|
Requires-Dist: myst-parser>=4.0.0; extra == "docs"
|
|
61
66
|
Provides-Extra: all
|
|
62
|
-
Requires-Dist: evutils[hdf5,pandas,torch,vis]; extra == "all"
|
|
67
|
+
Requires-Dist: evutils[aedat,hdf5,pandas,torch,vis]; extra == "all"
|
|
63
68
|
Provides-Extra: dev
|
|
64
69
|
Requires-Dist: evutils[all,docs,test]; extra == "dev"
|
|
65
70
|
Description-Content-Type: text/markdown
|
|
@@ -69,7 +74,13 @@ Description-Content-Type: text/markdown
|
|
|
69
74
|
[](https://github.com/mandulaj/evutils/actions/workflows/test.yaml)
|
|
70
75
|
|
|
71
76
|
## Overview
|
|
72
|
-
EV-Utils (
|
|
77
|
+
EV-Utils (`evutils`) is a performant collection of utilities for working with event-based vision data. Built with minimal dependencies, it relies on a compiled C backend for speed while offering a clean, modular Python interface.
|
|
78
|
+
|
|
79
|
+
### Core Philosophy
|
|
80
|
+
* **Fast & Lightweight:** Highly optimized C parsers for zero-bottleneck data ingestion.
|
|
81
|
+
* **Minimal Footprint:** Core features run entirely on NumPy and Numba.
|
|
82
|
+
* **Lazy Loading:** All heavy integrations (PyTorch, Pandas, HDF5, Polars, etc.) are lazy-loaded. If you don't use them, you don't need them installed, and they won't slow down import times.
|
|
83
|
+
* **Simple & Extensible:** Clean modular APIs.
|
|
73
84
|
|
|
74
85
|
|
|
75
86
|
|
|
@@ -80,6 +91,8 @@ This project draws inspiration from several excellent libraries in the event-bas
|
|
|
80
91
|
* [event_utils](https://github.com/TimoStoff/event_utils)
|
|
81
92
|
* [evlib](https://github.com/tallamjr/evlib)
|
|
82
93
|
* [expelliarmus](https://github.com/open-neuromorphic/expelliarmus)
|
|
94
|
+
* [event-vision-library](https://github.com/shiba24/event-vision-library)
|
|
95
|
+
* [evt3](https://github.com/muthmann/evt3)
|
|
83
96
|
* [openeb](https://github.com/prophesee-ai/openeb)
|
|
84
97
|
|
|
85
98
|
|
|
@@ -134,6 +147,20 @@ Wrappers for various dataset loaders -->
|
|
|
134
147
|
|
|
135
148
|
The `io` module provides methods for reading and writing events into various event formats. It provides a simple `.read()` and `.write()` interface as well as more advanced interfaces using iterators and slicing.
|
|
136
149
|
|
|
150
|
+
Supported formats (see the [formats documentation](https://mandulaj.github.io/evutils/formats.html) for details):
|
|
151
|
+
|
|
152
|
+
| Format | Extensions | Read | Write | Notes |
|
|
153
|
+
|---|---|:---:|:---:|---|
|
|
154
|
+
| EVT3 / EVT2.1 / EVT2 (Prophesee RAW) | `.raw`, `.evt*` | ✅ | ✅ | native C decoder, external triggers |
|
|
155
|
+
| DAT (Prophesee) | `.dat` | ✅ | ✅ | native C decoder |
|
|
156
|
+
| AER (Prophesee) | `.aer` | ✅ | ✅ | timestamp generation selectable |
|
|
157
|
+
| AEDAT 1.0 / 2.0 / 3.1 / 4.0 | `.aedat`, `.aedat4` | ✅ | 🚧 | AEDAT4 compression: `evutils[aedat]` |
|
|
158
|
+
| HDF5 (DSEC/RVT layout) | `.h5`, `.hdf5` | ✅ | ✅ | `evutils[hdf5]`, ms-index random access |
|
|
159
|
+
| HDF5 (Prophesee layout) | `.h5`, `.hdf5` | ✅ | 🚧 | ECF-compressed files need the ECF plugin |
|
|
160
|
+
| NPZ | `.npz` | ✅ | ✅ | streaming, `np.load`-compatible |
|
|
161
|
+
| CSV / TXT | `.csv`, `.txt` | ✅ | ✅ | `evutils[pandas]` |
|
|
162
|
+
| BIN | `.bin` | 🚧 | 🚧 | planned |
|
|
163
|
+
|
|
137
164
|
```python
|
|
138
165
|
from evutils.io import EventReader
|
|
139
166
|
|
|
@@ -191,6 +218,18 @@ uv run pytest benchmarks/ --benchmark-group-by=param:fmt # compare libraries
|
|
|
191
218
|
The benchmarks download a real Prophesee recording on first use. Optional cross-library comparisons run automatically once the libraries are installed (`uv pip install -e ".[compare]"`); OpenEB/Metavision is compared via the Docker image in `benchmarks/docker/`. See [`benchmarks/README.md`](benchmarks/README.md) for details.
|
|
192
219
|
|
|
193
220
|
|
|
221
|
+
|
|
222
|
+
## & Roadmap
|
|
223
|
+
We aim for universal event format support, prioritizing blazing fast read/write speeds, completeness, and extensibility.
|
|
224
|
+
- [x] Universal format support (`.raw`, `.evt2`, `.dat`, `.aedat4`, `.hdf5`, `.npz`, `.csv`, etc.)
|
|
225
|
+
- [x] Full Read/Write parity where possible
|
|
226
|
+
- [x] Chunked & Streaming access
|
|
227
|
+
- [x] External trigger data parsing
|
|
228
|
+
- [ ] **Random access / Timestamp indexing** (Big TODO for the future)
|
|
229
|
+
- [ ] **Arbitrary input sources:** memory-mapped IO, pure in-memory streams, HTTP streams
|
|
230
|
+
- [ ] **On-the-fly Compression wrappers:** passing file handles through `zstd` or `lz4` compression transparently before decoding
|
|
231
|
+
|
|
232
|
+
|
|
194
233
|
## Acknowledgements
|
|
195
234
|
|
|
196
235
|
Thanks to all the contributors for supporting this project:
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
[](https://github.com/mandulaj/evutils/actions/workflows/test.yaml)
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
|
-
EV-Utils (
|
|
6
|
+
EV-Utils (`evutils`) is a performant collection of utilities for working with event-based vision data. Built with minimal dependencies, it relies on a compiled C backend for speed while offering a clean, modular Python interface.
|
|
7
|
+
|
|
8
|
+
### Core Philosophy
|
|
9
|
+
* **Fast & Lightweight:** Highly optimized C parsers for zero-bottleneck data ingestion.
|
|
10
|
+
* **Minimal Footprint:** Core features run entirely on NumPy and Numba.
|
|
11
|
+
* **Lazy Loading:** All heavy integrations (PyTorch, Pandas, HDF5, Polars, etc.) are lazy-loaded. If you don't use them, you don't need them installed, and they won't slow down import times.
|
|
12
|
+
* **Simple & Extensible:** Clean modular APIs.
|
|
7
13
|
|
|
8
14
|
|
|
9
15
|
|
|
@@ -14,6 +20,8 @@ This project draws inspiration from several excellent libraries in the event-bas
|
|
|
14
20
|
* [event_utils](https://github.com/TimoStoff/event_utils)
|
|
15
21
|
* [evlib](https://github.com/tallamjr/evlib)
|
|
16
22
|
* [expelliarmus](https://github.com/open-neuromorphic/expelliarmus)
|
|
23
|
+
* [event-vision-library](https://github.com/shiba24/event-vision-library)
|
|
24
|
+
* [evt3](https://github.com/muthmann/evt3)
|
|
17
25
|
* [openeb](https://github.com/prophesee-ai/openeb)
|
|
18
26
|
|
|
19
27
|
|
|
@@ -68,6 +76,20 @@ Wrappers for various dataset loaders -->
|
|
|
68
76
|
|
|
69
77
|
The `io` module provides methods for reading and writing events into various event formats. It provides a simple `.read()` and `.write()` interface as well as more advanced interfaces using iterators and slicing.
|
|
70
78
|
|
|
79
|
+
Supported formats (see the [formats documentation](https://mandulaj.github.io/evutils/formats.html) for details):
|
|
80
|
+
|
|
81
|
+
| Format | Extensions | Read | Write | Notes |
|
|
82
|
+
|---|---|:---:|:---:|---|
|
|
83
|
+
| EVT3 / EVT2.1 / EVT2 (Prophesee RAW) | `.raw`, `.evt*` | ✅ | ✅ | native C decoder, external triggers |
|
|
84
|
+
| DAT (Prophesee) | `.dat` | ✅ | ✅ | native C decoder |
|
|
85
|
+
| AER (Prophesee) | `.aer` | ✅ | ✅ | timestamp generation selectable |
|
|
86
|
+
| AEDAT 1.0 / 2.0 / 3.1 / 4.0 | `.aedat`, `.aedat4` | ✅ | 🚧 | AEDAT4 compression: `evutils[aedat]` |
|
|
87
|
+
| HDF5 (DSEC/RVT layout) | `.h5`, `.hdf5` | ✅ | ✅ | `evutils[hdf5]`, ms-index random access |
|
|
88
|
+
| HDF5 (Prophesee layout) | `.h5`, `.hdf5` | ✅ | 🚧 | ECF-compressed files need the ECF plugin |
|
|
89
|
+
| NPZ | `.npz` | ✅ | ✅ | streaming, `np.load`-compatible |
|
|
90
|
+
| CSV / TXT | `.csv`, `.txt` | ✅ | ✅ | `evutils[pandas]` |
|
|
91
|
+
| BIN | `.bin` | 🚧 | 🚧 | planned |
|
|
92
|
+
|
|
71
93
|
```python
|
|
72
94
|
from evutils.io import EventReader
|
|
73
95
|
|
|
@@ -125,6 +147,18 @@ uv run pytest benchmarks/ --benchmark-group-by=param:fmt # compare libraries
|
|
|
125
147
|
The benchmarks download a real Prophesee recording on first use. Optional cross-library comparisons run automatically once the libraries are installed (`uv pip install -e ".[compare]"`); OpenEB/Metavision is compared via the Docker image in `benchmarks/docker/`. See [`benchmarks/README.md`](benchmarks/README.md) for details.
|
|
126
148
|
|
|
127
149
|
|
|
150
|
+
|
|
151
|
+
## & Roadmap
|
|
152
|
+
We aim for universal event format support, prioritizing blazing fast read/write speeds, completeness, and extensibility.
|
|
153
|
+
- [x] Universal format support (`.raw`, `.evt2`, `.dat`, `.aedat4`, `.hdf5`, `.npz`, `.csv`, etc.)
|
|
154
|
+
- [x] Full Read/Write parity where possible
|
|
155
|
+
- [x] Chunked & Streaming access
|
|
156
|
+
- [x] External trigger data parsing
|
|
157
|
+
- [ ] **Random access / Timestamp indexing** (Big TODO for the future)
|
|
158
|
+
- [ ] **Arbitrary input sources:** memory-mapped IO, pure in-memory streams, HTTP streams
|
|
159
|
+
- [ ] **On-the-fly Compression wrappers:** passing file handles through `zstd` or `lz4` compression transparently before decoding
|
|
160
|
+
|
|
161
|
+
|
|
128
162
|
## Acknowledgements
|
|
129
163
|
|
|
130
164
|
Thanks to all the contributors for supporting this project:
|
|
@@ -40,13 +40,13 @@ pytest benchmarks/ -k "not evlib and not openeb"
|
|
|
40
40
|
|
|
41
41
|
| File | What it benchmarks |
|
|
42
42
|
|------|--------------------|
|
|
43
|
-
| `test_read.py` | `evutils` decode throughput (evt2/evt21/evt3), asserts count vs reference |
|
|
43
|
+
| `test_read.py` | `evutils` decode throughput on the full real recordings (evt2/evt21/evt3), asserts count vs reference |
|
|
44
44
|
| `test_write.py` | `evutils` encode throughput (payload = first 5M events of the real evt3 file) |
|
|
45
|
-
| `
|
|
46
|
-
| `test_compare.py` | third-party readers from `readers.py` (auto-skip if not installed) |
|
|
45
|
+
| `test_formats.py` | **uniform per-format read/write**: the same 5M real events transcoded into every format (EVT3/EVT2.1/EVT2, DAT, AER, NPZ, HDF5, CSV, AEDAT4), so numbers are comparable across formats; expelliarmus (DAT) and evlib (HDF5, AEDAT4) compared on the identical files |
|
|
46
|
+
| `test_compare.py` | third-party readers from `readers.py` on the full real recordings (auto-skip if not installed) |
|
|
47
47
|
| `readers.py` | adapter registry — one entry per external library |
|
|
48
48
|
|
|
49
|
-
*Note:
|
|
49
|
+
*Note: in `test_formats.py` AER is the one lossy transcode — the format has no timestamps and 9-bit coordinates, so the same events are written with coordinates masked to 0–511 (identical event count).*
|
|
50
50
|
|
|
51
51
|
## Comparing Against Other Libraries
|
|
52
52
|
|
|
@@ -98,10 +98,24 @@ The container's default command is `pytest benchmarks/ --benchmark-group-by=para
|
|
|
98
98
|
|
|
99
99
|
### Useful Variations
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
The recordings are **not** baked into the image (`.dockerignore` excludes the
|
|
102
|
+
data and cache directories), so the fixture tries to download them on first
|
|
103
|
+
use. If the container has no network access, mount the host's already-cached
|
|
104
|
+
recordings and point `EVUTILS_BENCH_DATA` at them:
|
|
102
105
|
|
|
103
106
|
```bash
|
|
104
|
-
|
|
107
|
+
# after benchmarks have run at least once on the host:
|
|
108
|
+
docker run --rm \
|
|
109
|
+
-v "$(pwd)/.pytest_cache/d/event_files:/data:ro" \
|
|
110
|
+
-e EVUTILS_BENCH_DATA=/data \
|
|
111
|
+
evutils-openeb
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Alternatively, persist the container's own download across runs with a named
|
|
115
|
+
volume (note the workdir is `/home/user/work`):
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
docker run --rm -v evutils-cache:/home/user/work/.pytest_cache evutils-openeb
|
|
105
119
|
```
|
|
106
120
|
|
|
107
121
|
Run only the OpenEB comparison (skip the `evutils` rows):
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Benchmark fixtures.
|
|
2
|
+
|
|
3
|
+
``EventFile`` and ``real_event_files`` are duplicated from ``tests/conftest.py``
|
|
4
|
+
on purpose: pytest only shares conftest fixtures downward, and ``benchmarks/``
|
|
5
|
+
is a sibling of ``tests/``, so there is no clean way to reuse them without a
|
|
6
|
+
repo-root conftest. Keep the two copies in sync.
|
|
7
|
+
"""
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pytest
|
|
13
|
+
from typing import Any, cast
|
|
14
|
+
|
|
15
|
+
from evutils.io import EventReader
|
|
16
|
+
|
|
17
|
+
sys.path.append(str(Path(__file__).parent.parent / "tests"))
|
|
18
|
+
from conftest_utils import EventFile, download_and_extract_gdrive, load_event_files # type: ignore
|
|
19
|
+
|
|
20
|
+
#: Cap on how many events are held in memory for the write benchmarks. Large
|
|
21
|
+
#: enough for a stable throughput measurement, small enough to stay light.
|
|
22
|
+
N_REFERENCE = 5_000_000
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope='session')
|
|
26
|
+
def real_event_files(request: Any, benchmark_dataset: str) -> dict[str, list[Any]]:
|
|
27
|
+
"""Real Prophesee recordings (downloaded + cached on first use).
|
|
28
|
+
|
|
29
|
+
Returns ``{format: [EventFile(path, count)]}``.
|
|
30
|
+
|
|
31
|
+
Set ``EVUTILS_BENCH_DATA`` to a directory that already contains the
|
|
32
|
+
extracted recordings (+ JSON metadata) to skip the download entirely --
|
|
33
|
+
useful in offline environments such as the OpenEB benchmark container
|
|
34
|
+
(mount the host cache there).
|
|
35
|
+
"""
|
|
36
|
+
import os
|
|
37
|
+
override = os.environ.get("EVUTILS_BENCH_DATA")
|
|
38
|
+
if override:
|
|
39
|
+
data_dir = Path(override)
|
|
40
|
+
if not data_dir.is_dir():
|
|
41
|
+
pytest.skip(f"EVUTILS_BENCH_DATA={override} is not a directory")
|
|
42
|
+
return cast(dict[str, list[Any]], load_event_files(data_dir))
|
|
43
|
+
|
|
44
|
+
if benchmark_dataset == "large":
|
|
45
|
+
temp_dir = request.config.cache.mkdir("event_files_huge")
|
|
46
|
+
file_id = "1QPuilR1VD0rKyhVOlu1Y-HtkO4ZBc2Cz"
|
|
47
|
+
|
|
48
|
+
json_files = list(temp_dir.glob("*.json"))
|
|
49
|
+
if not json_files:
|
|
50
|
+
download_and_extract_gdrive(file_id, temp_dir, "huge.tar.zst")
|
|
51
|
+
|
|
52
|
+
return cast(dict[str, list[Any]], load_event_files(temp_dir))
|
|
53
|
+
else:
|
|
54
|
+
temp_dir = request.config.cache.mkdir("event_files")
|
|
55
|
+
file_id = "1uhOsWbp2o3CktsHrFkzGCNFbx0bQLsct"
|
|
56
|
+
filenames = {
|
|
57
|
+
'evt3': "hand_evt3.raw",
|
|
58
|
+
'evt21': "hand_evt21.raw",
|
|
59
|
+
'evt2': "hand_evt2.raw",
|
|
60
|
+
}
|
|
61
|
+
paths = {fmt: temp_dir / name for fmt, name in filenames.items()}
|
|
62
|
+
|
|
63
|
+
for key, path in paths.items():
|
|
64
|
+
if not path.exists():
|
|
65
|
+
download_and_extract_gdrive(file_id, temp_dir, "hand.tar.zst")
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
return cast(dict[str, list[Any]], load_event_files(temp_dir))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.fixture(scope='session')
|
|
72
|
+
def reference_events(real_event_files: dict[str, list[Any]]) -> Any:
|
|
73
|
+
"""First ``N_REFERENCE`` events of the real EVT3 recording as an AoS array.
|
|
74
|
+
|
|
75
|
+
Decoded once and reused as the payload for every write benchmark, so the
|
|
76
|
+
write benchmarks measure encoding cost only (not decoding).
|
|
77
|
+
"""
|
|
78
|
+
parts = []
|
|
79
|
+
total = 0
|
|
80
|
+
if 'evt3' not in real_event_files or not real_event_files['evt3']:
|
|
81
|
+
pytest.skip("No evt3 files available for reference events")
|
|
82
|
+
# Use the hand_evt3.raw file for reference events
|
|
83
|
+
ef = next((f for f in real_event_files['evt3'] if 'hand' in f.path.name), real_event_files['evt3'][0])
|
|
84
|
+
with EventReader(ef.path, n_events=1_000_000) as reader:
|
|
85
|
+
for chunk in reader:
|
|
86
|
+
parts.append(chunk.to_aos())
|
|
87
|
+
total += len(chunk)
|
|
88
|
+
if total >= N_REFERENCE:
|
|
89
|
+
break
|
|
90
|
+
return np.concatenate(parts)[:N_REFERENCE]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def pytest_addoption(parser: Any) -> None:
|
|
94
|
+
parser.addoption(
|
|
95
|
+
"--rounds", action="store", default=4, type=int, help="Number of benchmark rounds"
|
|
96
|
+
)
|
|
97
|
+
parser.addoption(
|
|
98
|
+
"--dataset", action="store", default="small", choices=["small", "large"],
|
|
99
|
+
help="Dataset size to use for benchmarks ('small' or 'large')"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@pytest.fixture(scope="session")
|
|
103
|
+
def benchmark_rounds(request: Any) -> int:
|
|
104
|
+
return int(request.config.getoption("--rounds"))
|
|
105
|
+
|
|
106
|
+
@pytest.fixture(scope="session")
|
|
107
|
+
def benchmark_dataset(request: Any) -> str:
|
|
108
|
+
return str(request.config.getoption("--dataset"))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.hookimpl(hookwrapper=True)
|
|
112
|
+
def pytest_benchmark_group_stats(config: Any, benchmarks: list[dict[str, Any]], group_by: str) -> Any:
|
|
113
|
+
"""Guard against a pytest-benchmark crash with ``--benchmark-group-by=param:<name>``.
|
|
114
|
+
|
|
115
|
+
Benchmarks collected from non-parametrized tests have ``params = None``,
|
|
116
|
+
which the plugin's param-grouping code cannot handle (TypeError) -- and the
|
|
117
|
+
crash also swallows the whole error/summary report. Give such benchmarks an
|
|
118
|
+
explicit null param instead, so they group under ``<name>=None``.
|
|
119
|
+
"""
|
|
120
|
+
if isinstance(group_by, str) and group_by.startswith("param:"):
|
|
121
|
+
names = group_by.split(":", 1)[1].split(",")
|
|
122
|
+
for bench in benchmarks:
|
|
123
|
+
if bench.get("params") is None:
|
|
124
|
+
bench["params"] = {}
|
|
125
|
+
for name in names:
|
|
126
|
+
bench["params"].setdefault(name, None)
|
|
127
|
+
yield
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.fixture(scope="session")
|
|
131
|
+
def uniform_files(reference_events: Any, tmp_path_factory: Any) -> dict[str, Any]:
|
|
132
|
+
"""The same ``N_REFERENCE`` real events transcoded into every format.
|
|
133
|
+
|
|
134
|
+
All per-format read/write benchmarks (benchmarks/test_formats.py) operate
|
|
135
|
+
on these files, so throughput numbers are comparable across formats: same
|
|
136
|
+
events, same count. AER is the one lossy exception -- it has no timestamps
|
|
137
|
+
and 9-bit coordinates, so its file carries the same events with coordinates
|
|
138
|
+
masked to 0..511 (the event count is identical).
|
|
139
|
+
"""
|
|
140
|
+
from evutils.io import EventWriter
|
|
141
|
+
|
|
142
|
+
sys.path.append(str(Path(__file__).parent.parent / "tests"))
|
|
143
|
+
from aedat_synth import make_aedat4 # type: ignore
|
|
144
|
+
|
|
145
|
+
d = tmp_path_factory.mktemp("uniform")
|
|
146
|
+
ev = reference_events
|
|
147
|
+
files = {}
|
|
148
|
+
|
|
149
|
+
for fmt in ("evt3", "evt21", "evt2"):
|
|
150
|
+
files[fmt] = d / f"ref_{fmt}.raw"
|
|
151
|
+
with EventWriter(files[fmt], width=1280, height=720, format=fmt) as w:
|
|
152
|
+
w.write(ev)
|
|
153
|
+
|
|
154
|
+
for fmt, name in (("dat", "ref.dat"), ("npz", "ref.npz"),
|
|
155
|
+
("hdf5", "ref.h5"), ("csv", "ref.csv")):
|
|
156
|
+
files[fmt] = d / name
|
|
157
|
+
with EventWriter(files[fmt], width=1280, height=720) as w:
|
|
158
|
+
w.write(ev)
|
|
159
|
+
|
|
160
|
+
aer = ev.copy()
|
|
161
|
+
aer["x"] &= 0x1FF
|
|
162
|
+
aer["y"] &= 0x1FF
|
|
163
|
+
files["aer"] = d / "ref.aer"
|
|
164
|
+
with EventWriter(files["aer"]) as w:
|
|
165
|
+
w.write(aer)
|
|
166
|
+
|
|
167
|
+
files["aedat4"] = d / "ref.aedat4"
|
|
168
|
+
files["aedat4"].write_bytes(
|
|
169
|
+
make_aedat4(ev["t"], ev["x"], ev["y"], ev["p"], events_per_packet=65536)
|
|
170
|
+
)
|
|
171
|
+
return files
|
|
@@ -12,7 +12,7 @@ helper below; the benchmark harness stays the same.
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
from dataclasses import dataclass
|
|
15
|
-
from typing import Callable
|
|
15
|
+
from typing import Any, Callable
|
|
16
16
|
|
|
17
17
|
ALL_FORMATS = ("evt3", "evt2", "evt21")
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ ALL_FORMATS = ("evt3", "evt2", "evt21")
|
|
|
20
20
|
_CHUNK = 5_000_000
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def _count(obj) -> int:
|
|
23
|
+
def _count(obj: Any) -> int:
|
|
24
24
|
"""Best-effort event count across the return types the libraries use
|
|
25
25
|
(numpy structured array, polars DataFrame, dict of columns, ...)."""
|
|
26
26
|
try:
|
|
@@ -39,15 +39,29 @@ def _count(obj) -> int:
|
|
|
39
39
|
# --------------------------------------------------------------------------- #
|
|
40
40
|
# Per-library read helpers: (path, fmt) -> event count
|
|
41
41
|
# --------------------------------------------------------------------------- #
|
|
42
|
-
def _read_expelliarmus(path: str, fmt: str) -> int:
|
|
42
|
+
def _read_expelliarmus(path: str, fmt: str) -> tuple[int, int]:
|
|
43
43
|
# https://github.com/open-neuromorphic/expelliarmus (evt2, evt3, dat)
|
|
44
|
+
from expelliarmus import Wizard # type: ignore
|
|
45
|
+
import numpy as np
|
|
46
|
+
wiz = Wizard(encoding=fmt)
|
|
47
|
+
wiz.set_file(str(path))
|
|
48
|
+
arr = wiz.read()
|
|
49
|
+
total = len(arr)
|
|
50
|
+
n_pos = int(np.count_nonzero(arr['p'] == 1))
|
|
51
|
+
return total, n_pos
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _read_expelliarmus_chunked(path: str, fmt: str) -> tuple[int, int]:
|
|
44
55
|
from expelliarmus import Wizard
|
|
56
|
+
import numpy as np
|
|
45
57
|
wiz = Wizard(encoding=fmt)
|
|
46
58
|
wiz.set_file(str(path))
|
|
47
59
|
total = 0
|
|
60
|
+
n_pos = 0
|
|
48
61
|
for chunk in wiz.read_chunk():
|
|
49
62
|
total += len(chunk)
|
|
50
|
-
|
|
63
|
+
n_pos += int(np.count_nonzero(chunk['p'] == 1))
|
|
64
|
+
return total, n_pos
|
|
51
65
|
|
|
52
66
|
|
|
53
67
|
# NOTE: tonic (https://github.com/neuromorphs/tonic) has no standalone EVT/.raw
|
|
@@ -56,44 +70,75 @@ def _read_expelliarmus(path: str, fmt: str) -> int:
|
|
|
56
70
|
# without the torchdata overhead), so it is intentionally not registered.
|
|
57
71
|
|
|
58
72
|
|
|
59
|
-
def _read_evlib(path: str, fmt: str) -> int:
|
|
60
|
-
# https://github.com/tallamjr/evlib (Rust-backed). Auto-detects the format
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
import
|
|
64
|
-
|
|
65
|
-
df =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# Fallback for older Polars versions
|
|
71
|
-
return df.select(pl.len()).collect(streaming=True).item()
|
|
72
|
-
return _count(df)
|
|
73
|
+
def _read_evlib(path: str, fmt: str) -> tuple[int, int]:
|
|
74
|
+
# https://github.com/tallamjr/evlib (Rust-backed). Auto-detects the format.
|
|
75
|
+
import evlib # type: ignore
|
|
76
|
+
import polars as pl # type: ignore
|
|
77
|
+
import numpy as np
|
|
78
|
+
lf = evlib.load_events(str(path))
|
|
79
|
+
df = lf.collect(engine="streaming")
|
|
80
|
+
total = len(df)
|
|
81
|
+
p_array = df["polarity"].to_numpy()
|
|
82
|
+
n_pos = int(np.sum(p_array == 1))
|
|
83
|
+
return total, n_pos
|
|
73
84
|
|
|
74
85
|
|
|
75
|
-
def _read_openeb(path: str, fmt: str) -> int:
|
|
86
|
+
def _read_openeb(path: str, fmt: str) -> tuple[int, int]:
|
|
76
87
|
# OpenEB / Metavision SDK (not on PyPI -- see benchmarks/docker).
|
|
77
|
-
from metavision_core.event_io import RawReader
|
|
88
|
+
from metavision_core.event_io import RawReader # type: ignore
|
|
89
|
+
import numpy as np
|
|
78
90
|
reader = RawReader(str(path))
|
|
79
91
|
n = 0
|
|
92
|
+
n_pos = 0
|
|
80
93
|
while not reader.is_done():
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
chunk = reader.load_n_events(_CHUNK)
|
|
95
|
+
n += len(chunk)
|
|
96
|
+
if len(chunk) > 0:
|
|
97
|
+
n_pos += int(np.count_nonzero(chunk['p']))
|
|
98
|
+
return n, n_pos
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _read_evt3(path: str, fmt: str) -> tuple[int, int]:
|
|
102
|
+
import evt3 # type: ignore
|
|
103
|
+
import numpy as np
|
|
104
|
+
if fmt != "evt3":
|
|
105
|
+
raise ValueError(f"evt3 package does not support {fmt}")
|
|
106
|
+
events = evt3.decode_file(str(path))
|
|
107
|
+
total = len(events)
|
|
108
|
+
n_pos = int(np.count_nonzero(events.p))
|
|
109
|
+
return total, n_pos
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _read_event_vision_library(path: str, fmt: str) -> tuple[int, int]:
|
|
113
|
+
# event-vision-library by shiba24 (installs as evlib, shadows Rust evlib)
|
|
114
|
+
import evlib.codec.fileformat as ff # type: ignore
|
|
115
|
+
import numpy as np
|
|
116
|
+
if fmt != "evt3":
|
|
117
|
+
raise ValueError(f"event-vision-library benchmark snippet only supports evt3")
|
|
118
|
+
it = ff.IteratorEvt3Event(str(path))
|
|
119
|
+
total = 0
|
|
120
|
+
n_pos = 0
|
|
121
|
+
for chunk in it:
|
|
122
|
+
total += len(chunk.x)
|
|
123
|
+
n_pos += int(np.sum(chunk.p))
|
|
124
|
+
return total, n_pos
|
|
83
125
|
|
|
84
126
|
|
|
85
127
|
@dataclass(frozen=True)
|
|
86
128
|
class Reader:
|
|
87
129
|
name: str
|
|
88
|
-
formats: tuple # subset of ALL_FORMATS this library can read
|
|
89
|
-
read: Callable[[str, str], int]
|
|
130
|
+
formats: tuple[str, ...] # subset of ALL_FORMATS this library can read
|
|
131
|
+
read: Callable[[str, str], tuple[int, int]]
|
|
90
132
|
|
|
91
133
|
|
|
92
134
|
#: Third-party readers benchmarked against evutils. evutils itself is measured
|
|
93
135
|
#: separately in test_read.py; with ``--benchmark-group-by=param:fmt`` all rows
|
|
94
136
|
#: for a given format line up in one table.
|
|
95
137
|
READERS = [
|
|
96
|
-
Reader("expelliarmus", ("evt2", "evt3"), _read_expelliarmus),
|
|
138
|
+
Reader("expelliarmus-read_all", ("evt2", "evt3"), _read_expelliarmus),
|
|
139
|
+
Reader("expelliarmus-chunked", ("evt2", "evt3"), _read_expelliarmus_chunked),
|
|
97
140
|
Reader("evlib", ("evt2", "evt3"), _read_evlib),
|
|
98
141
|
Reader("openeb", ALL_FORMATS, _read_openeb),
|
|
142
|
+
Reader("evt3", ("evt3",), _read_evt3),
|
|
143
|
+
Reader("event-vision-library", ("evt3",), _read_event_vision_library),
|
|
99
144
|
]
|
|
@@ -16,12 +16,14 @@ it appears in the same grouped table as the readers below.
|
|
|
16
16
|
"""
|
|
17
17
|
import pytest
|
|
18
18
|
|
|
19
|
-
from
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from readers import ALL_FORMATS, READERS # type: ignore
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
@pytest.mark.parametrize("fmt", ALL_FORMATS)
|
|
23
25
|
@pytest.mark.parametrize("reader", READERS, ids=lambda r: r.name)
|
|
24
|
-
def test_read_compare(benchmark, benchmark_rounds, real_event_files, reader, fmt):
|
|
26
|
+
def test_read_compare(benchmark: Any, benchmark_rounds: int, real_event_files: dict[str, list[Any]], reader: Any, fmt: str) -> None:
|
|
25
27
|
if fmt not in reader.formats:
|
|
26
28
|
pytest.skip(f"{reader.name} does not support {fmt}")
|
|
27
29
|
|
|
@@ -31,7 +33,7 @@ def test_read_compare(benchmark, benchmark_rounds, real_event_files, reader, fmt
|
|
|
31
33
|
ef = next((f for f in real_event_files[fmt] if 'hand' in f.path.name), real_event_files[fmt][0])
|
|
32
34
|
|
|
33
35
|
try:
|
|
34
|
-
n = benchmark.pedantic(
|
|
36
|
+
n, n_pos = benchmark.pedantic(
|
|
35
37
|
lambda: reader.read(ef.path, fmt),
|
|
36
38
|
rounds=benchmark_rounds, iterations=1, warmup_rounds=1,
|
|
37
39
|
)
|
|
@@ -39,4 +41,4 @@ def test_read_compare(benchmark, benchmark_rounds, real_event_files, reader, fmt
|
|
|
39
41
|
pytest.skip(f"{reader.name} not available: {exc}")
|
|
40
42
|
|
|
41
43
|
assert n > 0
|
|
42
|
-
benchmark.extra_info.update(library=reader.name, fmt=fmt, n_events=n)
|
|
44
|
+
benchmark.extra_info.update(library=reader.name, fmt=fmt, n_events=n, n_pos=n_pos)
|