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.
Files changed (147) hide show
  1. {evutils-0.3.11 → evutils-0.3.13}/.github/workflows/release.yaml +53 -0
  2. {evutils-0.3.11 → evutils-0.3.13}/CMakeLists.txt +2 -2
  3. {evutils-0.3.11 → evutils-0.3.13}/PKG-INFO +42 -3
  4. {evutils-0.3.11 → evutils-0.3.13}/README.md +35 -1
  5. {evutils-0.3.11 → evutils-0.3.13}/benchmarks/README.md +20 -6
  6. evutils-0.3.13/benchmarks/conftest.py +171 -0
  7. {evutils-0.3.11 → evutils-0.3.13}/benchmarks/readers.py +70 -25
  8. {evutils-0.3.11 → evutils-0.3.13}/benchmarks/test_compare.py +6 -4
  9. evutils-0.3.13/benchmarks/test_formats.py +187 -0
  10. evutils-0.3.13/benchmarks/test_read.py +54 -0
  11. {evutils-0.3.11 → evutils-0.3.13}/benchmarks/test_write.py +7 -5
  12. evutils-0.3.13/csrc/aer.c +70 -0
  13. {evutils-0.3.11 → evutils-0.3.13}/csrc/dat.c +1 -0
  14. {evutils-0.3.11 → evutils-0.3.13}/csrc/evt2.c +1 -0
  15. {evutils-0.3.11 → evutils-0.3.13}/csrc/evt21.c +1 -0
  16. {evutils-0.3.11 → evutils-0.3.13}/csrc/evt3.c +6 -0
  17. evutils-0.3.13/csrc/include/evutils/aer.h +55 -0
  18. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/compat.h +6 -0
  19. evutils-0.3.13/docs/source/formats.md +128 -0
  20. {evutils-0.3.11 → evutils-0.3.13}/docs/source/index.rst +1 -0
  21. {evutils-0.3.11 → evutils-0.3.13}/pyproject.toml +27 -2
  22. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/augment/_drop.py +1 -1
  23. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/chunking.py +5 -4
  24. evutils-0.3.13/src/evutils/io/_aedat.py +565 -0
  25. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_aer.py +82 -22
  26. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_bin.py +3 -1
  27. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_csv.py +7 -7
  28. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_dat.py +7 -6
  29. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_event_reader.py +70 -12
  30. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_event_writer.py +7 -7
  31. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_evt.py +9 -9
  32. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_hdf5.py +98 -29
  33. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_native_evt.py +82 -51
  34. evutils-0.3.13/src/evutils/io/_npz.py +303 -0
  35. evutils-0.3.13/src/evutils/io/_prefetch.py +147 -0
  36. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/_source.py +13 -12
  37. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/buffer.py +3 -3
  38. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/common.py +11 -11
  39. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/decoders.py +22 -3
  40. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/processing/_masking.py +1 -1
  41. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/random.py +1 -1
  42. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_frame.py +7 -6
  43. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_histogram.py +5 -4
  44. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_timesurface.py +4 -3
  45. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_tore.py +4 -3
  46. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/_voxel.py +2 -1
  47. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/torch/__init__.py +2 -1
  48. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/types.py +18 -17
  49. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/utils/_checker.py +10 -10
  50. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/open3d.py +1 -1
  51. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/plot3d.py +7 -6
  52. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/_base.py +5 -3
  53. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/metavision.py +5 -4
  54. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg.py +23 -17
  55. evutils-0.3.13/tests/aedat_synth.py +146 -0
  56. {evutils-0.3.11 → evutils-0.3.13}/tests/conftest.py +18 -5
  57. evutils-0.3.13/tests/conftest_utils.py +119 -0
  58. {evutils-0.3.11 → evutils-0.3.13}/tests/io/conftest.py +6 -4
  59. evutils-0.3.13/tests/io/test_aedat.py +145 -0
  60. evutils-0.3.13/tests/io/test_aer.py +98 -0
  61. {evutils-0.3.11 → evutils-0.3.13}/tests/io/test_csv.py +4 -3
  62. evutils-0.3.13/tests/io/test_dat.py +85 -0
  63. {evutils-0.3.11 → evutils-0.3.13}/tests/io/test_evt.py +152 -35
  64. evutils-0.3.13/tests/io/test_hdf5.py +159 -0
  65. evutils-0.3.13/tests/io/test_npz.py +99 -0
  66. evutils-0.3.13/tests/io/test_prefetch.py +147 -0
  67. evutils-0.3.13/tests/io/test_roundtrip_matrix.py +162 -0
  68. {evutils-0.3.11 → evutils-0.3.13}/tests/test_random.py +3 -3
  69. {evutils-0.3.11 → evutils-0.3.13}/tests/test_types.py +2 -2
  70. {evutils-0.3.11 → evutils-0.3.13}/tests/test_vis.py +1 -1
  71. evutils-0.3.11/benchmarks/conftest.py +0 -94
  72. evutils-0.3.11/benchmarks/test_fixed_formats.py +0 -119
  73. evutils-0.3.11/benchmarks/test_read.py +0 -38
  74. evutils-0.3.11/csrc/aer.c +0 -36
  75. evutils-0.3.11/csrc/include/evutils/aer.h +0 -35
  76. evutils-0.3.11/src/evutils/io/_aedat.py +0 -86
  77. evutils-0.3.11/src/evutils/io/_npz.py +0 -192
  78. evutils-0.3.11/tests/conftest_utils.py +0 -69
  79. evutils-0.3.11/tests/io/test_aer.py +0 -32
  80. evutils-0.3.11/tests/io/test_dat.py +0 -44
  81. evutils-0.3.11/tests/io/test_hdf5.py +0 -0
  82. evutils-0.3.11/tests/io/test_npz.py +0 -0
  83. {evutils-0.3.11 → evutils-0.3.13}/.dockerignore +0 -0
  84. {evutils-0.3.11 → evutils-0.3.13}/.github/workflows/test.yaml +0 -0
  85. {evutils-0.3.11 → evutils-0.3.13}/.gitignore +0 -0
  86. {evutils-0.3.11 → evutils-0.3.13}/.gitmodules +0 -0
  87. {evutils-0.3.11 → evutils-0.3.13}/LICENSE +0 -0
  88. {evutils-0.3.11 → evutils-0.3.13}/benchmarks/docker/Dockerfile.openeb +0 -0
  89. {evutils-0.3.11 → evutils-0.3.13}/csrc/evutils.c +0 -0
  90. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/dat.h +0 -0
  91. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evt2.h +0 -0
  92. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evt21.h +0 -0
  93. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evt3.h +0 -0
  94. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/evutils.h +0 -0
  95. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/parser.h +0 -0
  96. {evutils-0.3.11 → evutils-0.3.13}/csrc/include/evutils/types.h +0 -0
  97. {evutils-0.3.11 → evutils-0.3.13}/docker/Dockerfile +0 -0
  98. {evutils-0.3.11 → evutils-0.3.13}/docs/Makefile +0 -0
  99. {evutils-0.3.11 → evutils-0.3.13}/docs/make.bat +0 -0
  100. {evutils-0.3.11 → evutils-0.3.13}/docs/source/_static/event_hexagon_broken.webp +0 -0
  101. {evutils-0.3.11 → evutils-0.3.13}/docs/source/_templates/autoapi/python/module.rst +0 -0
  102. {evutils-0.3.11 → evutils-0.3.13}/docs/source/benchmarks/README.md +0 -0
  103. {evutils-0.3.11 → evutils-0.3.13}/docs/source/conf.py +0 -0
  104. {evutils-0.3.11 → evutils-0.3.13}/scripts/generate_benchmark_table.py +0 -0
  105. {evutils-0.3.11 → evutils-0.3.13}/scripts/generate_metadata.py +0 -0
  106. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/__init__.py +0 -0
  107. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/_jit.py +0 -0
  108. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/augment/__init__.py +0 -0
  109. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/dataset/__init__.py +0 -0
  110. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/__init__.py +0 -0
  111. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/io/encoders.py +0 -0
  112. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/processing/__init__.py +0 -0
  113. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/processing/_utils.py +0 -0
  114. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/repr/__init__.py +0 -0
  115. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/utils/__init__.py +0 -0
  116. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/__init__.py +0 -0
  117. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/__init__.py +0 -0
  118. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/.git +0 -0
  119. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/.gitignore +0 -0
  120. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/LICENSE +0 -0
  121. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/README.md +0 -0
  122. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/__init__.py +0 -0
  123. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/base/__init__.py +0 -0
  124. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/base/base_model.py +0 -0
  125. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/data/.gitignore +0 -0
  126. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/image_reconstructor.py +0 -0
  127. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/__init__.py +0 -0
  128. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/model.py +0 -0
  129. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/submodules.py +0 -0
  130. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/model/unet.py +0 -0
  131. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/options/__init__.py +0 -0
  132. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/options/inference_options.py +0 -0
  133. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/pretrained/.gitignore +0 -0
  134. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/run_reconstruction.py +0 -0
  135. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/embed_reconstructed_images_in_rosbag.py +0 -0
  136. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/extract_events_from_rosbag.py +0 -0
  137. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/image_folder_to_rosbag.py +0 -0
  138. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/resample_reconstructions.py +0 -0
  139. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/__init__.py +0 -0
  140. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/event_readers.py +0 -0
  141. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/inference_utils.py +0 -0
  142. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/loading_utils.py +0 -0
  143. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/path_utils.py +0 -0
  144. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/timers.py +0 -0
  145. {evutils-0.3.11 → evutils-0.3.13}/src/evutils/vis/reconstructor/rpg_e2vid/utils/util.py +0 -0
  146. {evutils-0.3.11 → evutils-0.3.13}/tests/io/test_bin.py +0 -0
  147. {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.11
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
  [![Test](https://github.com/mandulaj/evutils/actions/workflows/test.yaml/badge.svg)](https://github.com/mandulaj/evutils/actions/workflows/test.yaml)
70
75
 
71
76
  ## Overview
72
- EV-Utils (``evutils``) is a performant collection of utilities for working with event-based vision data. Built with minimal dependencies, it relies on compiled C backed for speed while offering a clean, modular Python interface.
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
  [![Test](https://github.com/mandulaj/evutils/actions/workflows/test.yaml/badge.svg)](https://github.com/mandulaj/evutils/actions/workflows/test.yaml)
4
4
 
5
5
  ## Overview
6
- EV-Utils (``evutils``) is a performant collection of utilities for working with event-based vision data. Built with minimal dependencies, it relies on compiled C backed for speed while offering a clean, modular Python interface.
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
- | `test_fixed_formats.py` | `evutils` read/write for DAT and AER (+ expelliarmus on DAT read) |
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: DAT reuses the shared EVT3 reference events (its 14-bit coords fit 1280×720). AER is 9-bit and timestamp-less (GenX320-class), so it uses a separate small synthetic fixture.*
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
- Persist the downloaded recording across runs (otherwise `--rm` discards the pytest cache and it re-downloads every time):
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
- docker run --rm -v evutils-cache:/work/.pytest_cache evutils-openeb
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
- return total
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
- # load_events() returns a polars LazyFrame. To avoid OOM on huge files,
62
- # we use an aggregation (pl.len()) and stream it using Polars streaming engine.
63
- import evlib
64
- import polars as pl
65
- df = evlib.load_events(str(path))
66
- if hasattr(df, "collect"):
67
- try:
68
- return df.select(pl.len()).collect(engine='streaming').item()
69
- except TypeError:
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
- n += len(reader.load_n_events(_CHUNK))
82
- return n
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 readers import ALL_FORMATS, READERS
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)