evutils 0.3.10__tar.gz → 0.3.11__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 (148) hide show
  1. {evutils-0.3.10 → evutils-0.3.11}/.github/workflows/release.yaml +3 -3
  2. {evutils-0.3.10 → evutils-0.3.11}/PKG-INFO +11 -28
  3. {evutils-0.3.10 → evutils-0.3.11}/README.md +4 -22
  4. evutils-0.3.11/benchmarks/README.md +131 -0
  5. evutils-0.3.11/benchmarks/conftest.py +94 -0
  6. evutils-0.3.11/benchmarks/docker/Dockerfile.openeb +56 -0
  7. {evutils-0.3.10 → evutils-0.3.11}/benchmarks/readers.py +14 -3
  8. {evutils-0.3.10 → evutils-0.3.11}/benchmarks/test_compare.py +6 -3
  9. evutils-0.3.11/benchmarks/test_fixed_formats.py +119 -0
  10. {evutils-0.3.10 → evutils-0.3.11}/benchmarks/test_read.py +14 -10
  11. evutils-0.3.11/benchmarks/test_write.py +57 -0
  12. evutils-0.3.11/csrc/aer.c +36 -0
  13. evutils-0.3.11/csrc/dat.c +66 -0
  14. {evutils-0.3.10 → evutils-0.3.11}/csrc/evt3.c +4 -0
  15. evutils-0.3.11/csrc/include/evutils/aer.h +35 -0
  16. evutils-0.3.11/csrc/include/evutils/dat.h +40 -0
  17. {evutils-0.3.10 → evutils-0.3.11}/docs/source/conf.py +14 -5
  18. {evutils-0.3.10 → evutils-0.3.11}/pyproject.toml +37 -6
  19. evutils-0.3.11/scripts/generate_benchmark_table.py +117 -0
  20. evutils-0.3.11/scripts/generate_metadata.py +102 -0
  21. evutils-0.3.11/src/evutils/_jit.py +34 -0
  22. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/augment/_drop.py +3 -2
  23. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/chunking.py +11 -12
  24. evutils-0.3.11/src/evutils/io/_aedat.py +86 -0
  25. evutils-0.3.11/src/evutils/io/_aer.py +245 -0
  26. evutils-0.3.11/src/evutils/io/_bin.py +86 -0
  27. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/io/_csv.py +66 -14
  28. evutils-0.3.11/src/evutils/io/_dat.py +320 -0
  29. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/io/_event_reader.py +203 -72
  30. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/io/_event_writer.py +49 -26
  31. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/io/_evt.py +217 -70
  32. evutils-0.3.11/src/evutils/io/_hdf5.py +295 -0
  33. evutils-0.3.11/src/evutils/io/_native_evt.py +981 -0
  34. evutils-0.3.11/src/evutils/io/_npz.py +192 -0
  35. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/io/_source.py +48 -2
  36. evutils-0.3.11/src/evutils/io/buffer.py +203 -0
  37. evutils-0.3.11/src/evutils/io/common.py +321 -0
  38. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/io/decoders.py +61 -37
  39. evutils-0.3.11/src/evutils/io/encoders.py +85 -0
  40. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/processing/_masking.py +8 -1
  41. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/processing/_utils.py +3 -0
  42. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/random.py +4 -7
  43. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/repr/_frame.py +32 -37
  44. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/repr/_histogram.py +23 -22
  45. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/repr/_timesurface.py +6 -6
  46. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/repr/_tore.py +12 -11
  47. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/repr/_voxel.py +6 -7
  48. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/torch/__init__.py +1 -1
  49. evutils-0.3.11/src/evutils/types.py +298 -0
  50. evutils-0.3.11/src/evutils/utils/_checker.py +105 -0
  51. evutils-0.3.11/src/evutils/vis/__init__.py +9 -0
  52. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/open3d.py +12 -2
  53. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/plot3d.py +32 -1
  54. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/_base.py +23 -6
  55. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/metavision.py +34 -15
  56. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg.py +51 -5
  57. {evutils-0.3.10 → evutils-0.3.11}/tests/conftest.py +7 -22
  58. evutils-0.3.11/tests/conftest_utils.py +69 -0
  59. evutils-0.3.11/tests/io/test_aer.py +32 -0
  60. evutils-0.3.11/tests/io/test_dat.py +44 -0
  61. evutils-0.3.11/tests/io/test_evt.py +303 -0
  62. {evutils-0.3.10 → evutils-0.3.11}/uv.lock +598 -507
  63. evutils-0.3.10/benchmarks/README.md +0 -102
  64. evutils-0.3.10/benchmarks/conftest.py +0 -75
  65. evutils-0.3.10/benchmarks/docker/Dockerfile.openeb +0 -57
  66. evutils-0.3.10/benchmarks/test_write.py +0 -31
  67. evutils-0.3.10/src/evutils/io/_aedat.py +0 -33
  68. evutils-0.3.10/src/evutils/io/_aer.py +0 -32
  69. evutils-0.3.10/src/evutils/io/_bin.py +0 -31
  70. evutils-0.3.10/src/evutils/io/_dat.py +0 -30
  71. evutils-0.3.10/src/evutils/io/_hdf5.py +0 -187
  72. evutils-0.3.10/src/evutils/io/_native_evt.py +0 -481
  73. evutils-0.3.10/src/evutils/io/_npz.py +0 -30
  74. evutils-0.3.10/src/evutils/io/buffer.py +0 -98
  75. evutils-0.3.10/src/evutils/io/common.py +0 -207
  76. evutils-0.3.10/src/evutils/io/encoders.py +0 -68
  77. evutils-0.3.10/src/evutils/types.py +0 -127
  78. evutils-0.3.10/src/evutils/utils/_checker.py +0 -32
  79. evutils-0.3.10/src/evutils/vis/__init__.py +0 -5
  80. evutils-0.3.10/tests/io/test_dat.py +0 -0
  81. evutils-0.3.10/tests/io/test_evt.py +0 -203
  82. {evutils-0.3.10 → evutils-0.3.11}/.dockerignore +0 -0
  83. {evutils-0.3.10 → evutils-0.3.11}/.github/workflows/test.yaml +0 -0
  84. {evutils-0.3.10 → evutils-0.3.11}/.gitignore +0 -0
  85. {evutils-0.3.10 → evutils-0.3.11}/.gitmodules +0 -0
  86. {evutils-0.3.10 → evutils-0.3.11}/CMakeLists.txt +0 -0
  87. {evutils-0.3.10 → evutils-0.3.11}/LICENSE +0 -0
  88. {evutils-0.3.10 → evutils-0.3.11}/csrc/evt2.c +0 -0
  89. {evutils-0.3.10 → evutils-0.3.11}/csrc/evt21.c +0 -0
  90. {evutils-0.3.10 → evutils-0.3.11}/csrc/evutils.c +0 -0
  91. {evutils-0.3.10 → evutils-0.3.11}/csrc/include/evutils/compat.h +0 -0
  92. {evutils-0.3.10 → evutils-0.3.11}/csrc/include/evutils/evt2.h +0 -0
  93. {evutils-0.3.10 → evutils-0.3.11}/csrc/include/evutils/evt21.h +0 -0
  94. {evutils-0.3.10 → evutils-0.3.11}/csrc/include/evutils/evt3.h +0 -0
  95. {evutils-0.3.10 → evutils-0.3.11}/csrc/include/evutils/evutils.h +0 -0
  96. {evutils-0.3.10 → evutils-0.3.11}/csrc/include/evutils/parser.h +0 -0
  97. {evutils-0.3.10 → evutils-0.3.11}/csrc/include/evutils/types.h +0 -0
  98. {evutils-0.3.10 → evutils-0.3.11}/docker/Dockerfile +0 -0
  99. {evutils-0.3.10 → evutils-0.3.11}/docs/Makefile +0 -0
  100. {evutils-0.3.10 → evutils-0.3.11}/docs/make.bat +0 -0
  101. {evutils-0.3.10 → evutils-0.3.11}/docs/source/_static/event_hexagon_broken.webp +0 -0
  102. {evutils-0.3.10 → evutils-0.3.11}/docs/source/_templates/autoapi/python/module.rst +0 -0
  103. {evutils-0.3.10 → evutils-0.3.11}/docs/source/benchmarks/README.md +0 -0
  104. {evutils-0.3.10 → evutils-0.3.11}/docs/source/index.rst +0 -0
  105. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/__init__.py +0 -0
  106. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/augment/__init__.py +0 -0
  107. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/dataset/__init__.py +0 -0
  108. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/io/__init__.py +0 -0
  109. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/processing/__init__.py +0 -0
  110. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/repr/__init__.py +0 -0
  111. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/utils/__init__.py +0 -0
  112. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/__init__.py +0 -0
  113. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/.git +0 -0
  114. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/.gitignore +0 -0
  115. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/LICENSE +0 -0
  116. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/README.md +0 -0
  117. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/__init__.py +0 -0
  118. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/base/__init__.py +0 -0
  119. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/base/base_model.py +0 -0
  120. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/data/.gitignore +0 -0
  121. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/image_reconstructor.py +0 -0
  122. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/model/__init__.py +0 -0
  123. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/model/model.py +0 -0
  124. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/model/submodules.py +0 -0
  125. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/model/unet.py +0 -0
  126. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/options/__init__.py +0 -0
  127. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/options/inference_options.py +0 -0
  128. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/pretrained/.gitignore +0 -0
  129. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/run_reconstruction.py +0 -0
  130. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/embed_reconstructed_images_in_rosbag.py +0 -0
  131. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/extract_events_from_rosbag.py +0 -0
  132. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/image_folder_to_rosbag.py +0 -0
  133. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/scripts/resample_reconstructions.py +0 -0
  134. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/utils/__init__.py +0 -0
  135. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/utils/event_readers.py +0 -0
  136. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/utils/inference_utils.py +0 -0
  137. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/utils/loading_utils.py +0 -0
  138. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/utils/path_utils.py +0 -0
  139. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/utils/timers.py +0 -0
  140. {evutils-0.3.10 → evutils-0.3.11}/src/evutils/vis/reconstructor/rpg_e2vid/utils/util.py +0 -0
  141. {evutils-0.3.10 → evutils-0.3.11}/tests/io/conftest.py +0 -0
  142. {evutils-0.3.10 → evutils-0.3.11}/tests/io/test_bin.py +0 -0
  143. {evutils-0.3.10 → evutils-0.3.11}/tests/io/test_csv.py +0 -0
  144. {evutils-0.3.10 → evutils-0.3.11}/tests/io/test_hdf5.py +0 -0
  145. {evutils-0.3.10 → evutils-0.3.11}/tests/io/test_npz.py +0 -0
  146. {evutils-0.3.10 → evutils-0.3.11}/tests/test_random.py +0 -0
  147. {evutils-0.3.10 → evutils-0.3.11}/tests/test_types.py +0 -0
  148. {evutils-0.3.10 → evutils-0.3.11}/tests/test_vis.py +0 -0
@@ -51,7 +51,7 @@ jobs:
51
51
  CIBW_ARCHS_MACOS: "x86_64 arm64"
52
52
 
53
53
  - name: Upload wheels
54
- uses: actions/upload-artifact@v4
54
+ uses: actions/upload-artifact@v7
55
55
  with:
56
56
  name: dist-${{ matrix.os }}
57
57
  path: wheelhouse/*.whl
@@ -75,7 +75,7 @@ jobs:
75
75
  run: pipx run build --sdist
76
76
 
77
77
  - name: Upload sdist
78
- uses: actions/upload-artifact@v4
78
+ uses: actions/upload-artifact@v7
79
79
  with:
80
80
  name: dist-sdist
81
81
  path: dist/*.tar.gz
@@ -140,4 +140,4 @@ jobs:
140
140
 
141
141
  - name: Deploy to GitHub Pages
142
142
  id: deployment
143
- uses: actions/deploy-pages@v4
143
+ uses: actions/deploy-pages@v5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: evutils
3
- Version: 0.3.10
3
+ Version: 0.3.11
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>
@@ -30,17 +30,17 @@ Project-URL: Homepage, https://mandulaj.github.io/evutils/
30
30
  Requires-Python: >=3.10
31
31
  Requires-Dist: numpy>=2.0.0
32
32
  Requires-Dist: opencv-python>=4.2
33
+ Requires-Dist: numba>=0.57
33
34
  Provides-Extra: pandas
34
35
  Requires-Dist: pandas>=2.3.3; extra == "pandas"
35
- Provides-Extra: numba
36
- Requires-Dist: numba>=0.57; extra == "numba"
37
36
  Provides-Extra: torch
38
37
  Requires-Dist: torch>=2.0; extra == "torch"
39
38
  Provides-Extra: hdf5
40
39
  Requires-Dist: hdf5plugin==6.0.0; extra == "hdf5"
41
- Requires-Dist: h5py==3.16.0; extra == "hdf5"
40
+ Requires-Dist: h5py>=3.16.0; extra == "hdf5"
42
41
  Provides-Extra: vis
43
42
  Requires-Dist: matplotlib>=3.1; extra == "vis"
43
+ Requires-Dist: open3d>=0.19.0; python_version < "3.13" and extra == "vis"
44
44
  Requires-Dist: scipy>=1.15.3; extra == "vis"
45
45
  Requires-Dist: tqdm>=4.41; extra == "vis"
46
46
  Provides-Extra: test
@@ -48,7 +48,8 @@ Requires-Dist: pytest>=9.0.2; extra == "test"
48
48
  Requires-Dist: pytest-benchmark>=5.2.3; extra == "test"
49
49
  Provides-Extra: compare
50
50
  Requires-Dist: expelliarmus>=1.1.0; extra == "compare"
51
- Requires-Dist: evlib; extra == "compare"
51
+ Requires-Dist: evlib>=0.12.0; python_version >= "3.11" and extra == "compare"
52
+ Requires-Dist: evlib>=0.8.0; python_version < "3.11" and extra == "compare"
52
53
  Provides-Extra: docs
53
54
  Requires-Dist: sphinx>=8.0.0; extra == "docs"
54
55
  Requires-Dist: sphinx-autoapi>=3.6.1; extra == "docs"
@@ -58,7 +59,7 @@ Requires-Dist: sphinx_book_theme>=1.0.0; extra == "docs"
58
59
  Requires-Dist: pydata_sphinx_theme>=0.19.0; extra == "docs"
59
60
  Requires-Dist: myst-parser>=4.0.0; extra == "docs"
60
61
  Provides-Extra: all
61
- Requires-Dist: evutils[hdf5,numba,pandas,torch,vis]; extra == "all"
62
+ Requires-Dist: evutils[hdf5,pandas,torch,vis]; extra == "all"
62
63
  Provides-Extra: dev
63
64
  Requires-Dist: evutils[all,docs,test]; extra == "dev"
64
65
  Description-Content-Type: text/markdown
@@ -83,10 +84,12 @@ This project draws inspiration from several excellent libraries in the event-bas
83
84
 
84
85
 
85
86
  ## Installation
86
- We recommend installing `evutils` using `uv`.
87
+ We recommend installing `evutils` using `uv`.
87
88
  ### From PyPi
88
89
  ```bash
89
- uv add evutils[all]
90
+ uv add evutils # Basic library
91
+ uv add evutils[all] # All groups (pandas, numba, torch, hdf5, etc..)
92
+ uv add evutils[dev] # Dev group
90
93
  ```
91
94
 
92
95
  ### From Git
@@ -187,26 +190,6 @@ uv run pytest benchmarks/ --benchmark-group-by=param:fmt # compare libraries
187
190
 
188
191
  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.
189
192
 
190
- Decode/encode throughput on a 12th Gen Intel Core i7-1280P (millions of events per second, higher is better):
191
-
192
- **Read** (full-file decode)
193
-
194
- | Format | evutils | expelliarmus | evlib |
195
- | ------ | ------: | -----------: | ----: |
196
- | EVT2 | 91 | 105 | 5 |
197
- | EVT2.1 | 72 | – | – |
198
- | EVT3 | 53 | 41 | 5 |
199
-
200
- **Write** (evutils; expelliarmus/evlib are read-only for these formats)
201
-
202
- | Format | evutils |
203
- | ------ | ------: |
204
- | EVT2 | 146 |
205
- | EVT2.1 | 110 |
206
- | EVT3 | 74 |
207
-
208
- (`–` = format not supported by that library.)
209
-
210
193
 
211
194
  ## Acknowledgements
212
195
 
@@ -18,10 +18,12 @@ This project draws inspiration from several excellent libraries in the event-bas
18
18
 
19
19
 
20
20
  ## Installation
21
- We recommend installing `evutils` using `uv`.
21
+ We recommend installing `evutils` using `uv`.
22
22
  ### From PyPi
23
23
  ```bash
24
- uv add evutils[all]
24
+ uv add evutils # Basic library
25
+ uv add evutils[all] # All groups (pandas, numba, torch, hdf5, etc..)
26
+ uv add evutils[dev] # Dev group
25
27
  ```
26
28
 
27
29
  ### From Git
@@ -122,26 +124,6 @@ uv run pytest benchmarks/ --benchmark-group-by=param:fmt # compare libraries
122
124
 
123
125
  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.
124
126
 
125
- Decode/encode throughput on a 12th Gen Intel Core i7-1280P (millions of events per second, higher is better):
126
-
127
- **Read** (full-file decode)
128
-
129
- | Format | evutils | expelliarmus | evlib |
130
- | ------ | ------: | -----------: | ----: |
131
- | EVT2 | 91 | 105 | 5 |
132
- | EVT2.1 | 72 | – | – |
133
- | EVT3 | 53 | 41 | 5 |
134
-
135
- **Write** (evutils; expelliarmus/evlib are read-only for these formats)
136
-
137
- | Format | evutils |
138
- | ------ | ------: |
139
- | EVT2 | 146 |
140
- | EVT2.1 | 110 |
141
- | EVT3 | 74 |
142
-
143
- (`–` = format not supported by that library.)
144
-
145
127
 
146
128
  ## Acknowledgements
147
129
 
@@ -0,0 +1,131 @@
1
+ # Benchmarks
2
+
3
+ Read/write throughput benchmarks for the native EVT2 / EVT2.1 / EVT3 codecs, plus optional comparisons against other event libraries.
4
+
5
+ These benchmarks are **not** part of the normal test run (which uses `testpaths = ["tests"]`). They must be executed explicitly from the repository root.
6
+
7
+ ## Quick Start
8
+
9
+ Run the default suite (evutils only, plus any installed comparison libraries):
10
+ ```bash
11
+ pytest benchmarks/
12
+ ```
13
+
14
+ Run specific benchmarks:
15
+ ```bash
16
+ pytest benchmarks/test_read.py # just reads
17
+ pytest benchmarks/test_write.py # just writes
18
+ ```
19
+
20
+ ## Benchmark Options
21
+
22
+ ### Datasets
23
+ You can benchmark against two datasets using the `--dataset` flag. The necessary files are automatically downloaded and cached on first use.
24
+ - `--dataset small` (default): A ~1GB memory footprint dataset (hand recordings).
25
+ - `--dataset large`: A massive multi-GB dataset designed to test the streaming and chunking capabilities of the decoders.
26
+
27
+ ```bash
28
+ pytest benchmarks/ --dataset large
29
+ ```
30
+
31
+ ### Filtering Readers
32
+ The benchmark tests are fully parametrized by the reader name. You can effortlessly exclude specific third-party libraries using pytest's standard `-k` flag (for example, if they are slow or misbehaving on large datasets):
33
+
34
+ ```bash
35
+ pytest benchmarks/ -k "not evlib" --dataset large
36
+ pytest benchmarks/ -k "not evlib and not openeb"
37
+ ```
38
+
39
+ ## File Structure
40
+
41
+ | File | What it benchmarks |
42
+ |------|--------------------|
43
+ | `test_read.py` | `evutils` decode throughput (evt2/evt21/evt3), asserts count vs reference |
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) |
47
+ | `readers.py` | adapter registry — one entry per external library |
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.*
50
+
51
+ ## Comparing Against Other Libraries
52
+
53
+ Install the optional readers and run with grouping so every library lines up per format:
54
+
55
+ ```bash
56
+ pip install evutils[compare] # expelliarmus, evlib
57
+ pytest benchmarks/ --benchmark-group-by=param:fmt --benchmark-columns=mean,ops
58
+ ```
59
+
60
+ Each library reads inside a lazy import. If a library is uninstalled or broken, its benchmarks simply **skip**. To add another library, append a `Reader(...)` entry to `readers.py`.
61
+
62
+ ## Benchmark Comparison (Mean Time in Seconds)
63
+
64
+ ### Reading
65
+
66
+ | Library | EVT2 | EVT21 | EVT3 |
67
+ |---|---|---|---|
68
+ | **evutils** | 0.138 s | 0.070 s | 0.292 s |
69
+ | **evlib** | 4.410 s | N/A | 4.327 s |
70
+ | **expelliarmus** | 0.128 s | N/A | 0.341 s |
71
+
72
+ ### Writing
73
+
74
+ | Library | EVT2 | EVT21 | EVT3 |
75
+ |---|---|---|---|
76
+ | **evutils** | 0.013 s | 0.028 s | 0.041 s |
77
+ | **expelliarmus** | 0.077 s | N/A | 0.097 s |
78
+
79
+ **Hardware:** 12th Gen Intel(R) Core(TM) i7-1280P | **OS:** Linux 7.1.1-3-MANJARO | **Python:** 3.12.13
80
+
81
+ *Lower is better. Generated dynamically by `scripts/generate_benchmark_table.py`.*
82
+
83
+ > **Note**: `tonic` is intentionally not included. It has no standalone EVT reader and reads Prophesee data through `expelliarmus` internally, so benchmarking it would just re-measure `expelliarmus`.
84
+
85
+ ## OpenEB / Metavision (via Docker)
86
+
87
+ OpenEB isn't on PyPI and is painful to build locally, so there's an image that builds it once. Both commands must be run **from the repo root** (the build context must be the whole project so `evutils` is copied in):
88
+
89
+ ```bash
90
+ # Build the image (compiles OpenEB from source + installs evutils; slow, one-time)
91
+ docker build -t evutils-openeb -f benchmarks/docker/Dockerfile.openeb .
92
+
93
+ # Run the full suite (evutils + OpenEB), grouped per format
94
+ docker run --rm evutils-openeb
95
+ ```
96
+
97
+ The container's default command is `pytest benchmarks/ --benchmark-group-by=param:fmt`, so you get `evutils` and OpenEB side by side per format.
98
+
99
+ ### Useful Variations
100
+
101
+ Persist the downloaded recording across runs (otherwise `--rm` discards the pytest cache and it re-downloads every time):
102
+
103
+ ```bash
104
+ docker run --rm -v evutils-cache:/work/.pytest_cache evutils-openeb
105
+ ```
106
+
107
+ Run only the OpenEB comparison (skip the `evutils` rows):
108
+
109
+ ```bash
110
+ docker run --rm evutils-openeb \
111
+ pytest benchmarks/test_compare.py --benchmark-group-by=param:fmt -q
112
+ ```
113
+
114
+ Drop into a shell to debug the build/run:
115
+
116
+ ```bash
117
+ docker run --rm -it evutils-openeb bash
118
+ ```
119
+
120
+ Build a different OpenEB release if the default fails to build:
121
+
122
+ ```bash
123
+ docker build -t evutils-openeb --build-arg OPENEB_VERSION=5.0.0 \
124
+ -f benchmarks/docker/Dockerfile.openeb .
125
+ ```
126
+
127
+ ### Caveats
128
+
129
+ - The image targets **OpenEB 5.x on Ubuntu 22.04**; OpenEB's apt dependencies and install layout drift between releases, so the `apt-get`/`PYTHONPATH` lines may need tweaking. The Dockerfile imports `metavision_core` at build time, so a broken OpenEB install fails during `docker build` rather than silently skipping at run time.
130
+ - The first pass is slow: it compiles OpenEB (`-j$(nproc)`) and downloads the reference recording on first run.
131
+ - A repo-root `.dockerignore` keeps the build context small (excludes `.venv`, `build/`, `.git`, `data/`, `*.raw`, etc.).
@@ -0,0 +1,94 @@
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
+
14
+ from evutils.io import EventReader
15
+
16
+ sys.path.append(str(Path(__file__).parent.parent / "tests"))
17
+ from conftest_utils import EventFile, download_and_extract_gdrive, load_event_files
18
+
19
+ #: Cap on how many events are held in memory for the write benchmarks. Large
20
+ #: enough for a stable throughput measurement, small enough to stay light.
21
+ N_REFERENCE = 5_000_000
22
+
23
+
24
+ @pytest.fixture(scope='session')
25
+ def real_event_files(request, benchmark_dataset):
26
+ """Real Prophesee recordings (downloaded + cached on first use).
27
+
28
+ Returns ``{format: [EventFile(path, count)]}``.
29
+ """
30
+ if benchmark_dataset == "large":
31
+ temp_dir = request.config.cache.mkdir("event_files_huge")
32
+ file_id = "1QPuilR1VD0rKyhVOlu1Y-HtkO4ZBc2Cz"
33
+
34
+ json_files = list(temp_dir.glob("*.json"))
35
+ if not json_files:
36
+ download_and_extract_gdrive(file_id, temp_dir, "huge.tar.zst")
37
+
38
+ return load_event_files(temp_dir)
39
+ else:
40
+ temp_dir = request.config.cache.mkdir("event_files")
41
+ file_id = "1uhOsWbp2o3CktsHrFkzGCNFbx0bQLsct"
42
+ filenames = {
43
+ 'evt3': "hand_evt3.raw",
44
+ 'evt21': "hand_evt21.raw",
45
+ 'evt2': "hand_evt2.raw",
46
+ }
47
+ paths = {fmt: temp_dir / name for fmt, name in filenames.items()}
48
+
49
+ for key, path in paths.items():
50
+ if not path.exists():
51
+ download_and_extract_gdrive(file_id, temp_dir, "hand.tar.zst")
52
+ break
53
+
54
+ return load_event_files(temp_dir, paths)
55
+
56
+
57
+ @pytest.fixture(scope='session')
58
+ def reference_events(real_event_files):
59
+ """First ``N_REFERENCE`` events of the real EVT3 recording as an AoS array.
60
+
61
+ Decoded once and reused as the payload for every write benchmark, so the
62
+ write benchmarks measure encoding cost only (not decoding).
63
+ """
64
+ parts = []
65
+ total = 0
66
+ if 'evt3' not in real_event_files or not real_event_files['evt3']:
67
+ pytest.skip("No evt3 files available for reference events")
68
+ # Use the hand_evt3.raw file for reference events
69
+ ef = next((f for f in real_event_files['evt3'] if 'hand' in f.path.name), real_event_files['evt3'][0])
70
+ with EventReader(ef.path, n_events=1_000_000) as reader:
71
+ for chunk in reader:
72
+ parts.append(chunk.to_aos())
73
+ total += len(chunk)
74
+ if total >= N_REFERENCE:
75
+ break
76
+ return np.concatenate(parts)[:N_REFERENCE]
77
+
78
+
79
+ def pytest_addoption(parser):
80
+ parser.addoption(
81
+ "--rounds", action="store", default=4, type=int, help="Number of benchmark rounds"
82
+ )
83
+ parser.addoption(
84
+ "--dataset", action="store", default="small", choices=["small", "large"],
85
+ help="Dataset size to use for benchmarks ('small' or 'large')"
86
+ )
87
+
88
+ @pytest.fixture(scope="session")
89
+ def benchmark_rounds(request):
90
+ return request.config.getoption("--rounds")
91
+
92
+ @pytest.fixture(scope="session")
93
+ def benchmark_dataset(request):
94
+ return request.config.getoption("--dataset")
@@ -0,0 +1,56 @@
1
+ # OpenEB (Metavision SDK, open-source edition) + evutils, for running the
2
+ # comparison benchmarks against OpenEB's RawReader.
3
+ #
4
+ # OpenEB is not on PyPI and is fiddly to build; this image does it once.
5
+ #
6
+ # Build (from the repo root, so the whole project is the build context):
7
+ # docker build -t evutils-openeb -f benchmarks/docker/Dockerfile.openeb .
8
+ #
9
+ # Run the benchmarks (downloads the reference recordings on first run):
10
+ # docker run --rm evutils-openeb
11
+ #
12
+ # Tip: add a repo-root .dockerignore (.venv, build/, .git, *.raw, data/) to keep
13
+ # the build context small.
14
+ #
15
+ # NOTE: OpenEB's exact apt dependencies and layout shift between releases. This
16
+ # targets OpenEB 5.x on Ubuntu 22.04 -- bump OPENEB_VERSION / tweak deps if the
17
+ # build fails.
18
+ FROM registry.git.ee.ethz.ch/pbl/research/event-camera/docker/open-eb-docker/openeb_base:latest
19
+
20
+ ARG USERNAME=user
21
+ ARG PASSWORD=password
22
+ ARG USER_ID=1000
23
+ ARG GROUP_ID=1000
24
+
25
+ # Validate that all required arguments are set
26
+ RUN : \
27
+ && if [ -z "$USERNAME" ]; then echo "ERROR: USERNAME is not set."; exit 1; fi \
28
+ && if [ -z "$PASSWORD" ]; then echo "ERROR: PASSWORD is not set."; exit 1; fi \
29
+ && if [ -z "$USER_ID" ]; then echo "ERROR: USER_ID is not set."; exit 1; fi \
30
+ && if [ -z "$GROUP_ID" ]; then echo "ERROR: GROUP_ID is not set."; exit 1; fi
31
+
32
+ RUN groupadd -g ${GROUP_ID} ${USERNAME} && useradd -u ${USER_ID} -g ${GROUP_ID} -ms /bin/bash ${USERNAME} && usermod -aG sudo ${USERNAME} && echo "${USERNAME}:${PASSWORD}" | chpasswd
33
+
34
+ USER ${USERNAME}
35
+ RUN echo "source /openeb/build/utils/scripts/setup_env.sh" >> /home/${USERNAME}/.bashrc
36
+
37
+ WORKDIR /home/${USERNAME}/work
38
+ COPY . /home/${USERNAME}/work
39
+
40
+
41
+
42
+ # 2. Omit --python 3.12 so it defaults to the system Python (3.10) to match OpenEB
43
+ RUN uv venv --system-site-packages
44
+ RUN uv pip install --no-cache-dir -e ".[test,compare]"
45
+
46
+ ENV PATH="/home/${USERNAME}/work/.venv/bin:$PATH"
47
+
48
+ # 3. Source the OpenEB setup script directly during the RUN step
49
+ RUN . /openeb/build/utils/scripts/setup_env.sh && \
50
+ python3 -c "from metavision_core.event_io import RawReader; print('OpenEB OK')"
51
+
52
+ # 4. Use an ENTRYPOINT script to ensure OpenEB vars are loaded for ANY command you run
53
+ ENTRYPOINT ["/bin/bash", "-c", "source /openeb/build/utils/scripts/setup_env.sh && exec \"$@\"", "--"]
54
+
55
+ CMD ["pytest", "benchmarks/", "--benchmark-group-by=param:fmt", \
56
+ "--benchmark-columns=mean,ops", "-q"]
@@ -42,7 +42,12 @@ def _count(obj) -> int:
42
42
  def _read_expelliarmus(path: str, fmt: str) -> int:
43
43
  # https://github.com/open-neuromorphic/expelliarmus (evt2, evt3, dat)
44
44
  from expelliarmus import Wizard
45
- return _count(Wizard(encoding=fmt).read(str(path)))
45
+ wiz = Wizard(encoding=fmt)
46
+ wiz.set_file(str(path))
47
+ total = 0
48
+ for chunk in wiz.read_chunk():
49
+ total += len(chunk)
50
+ return total
46
51
 
47
52
 
48
53
  # NOTE: tonic (https://github.com/neuromorphs/tonic) has no standalone EVT/.raw
@@ -53,11 +58,17 @@ def _read_expelliarmus(path: str, fmt: str) -> int:
53
58
 
54
59
  def _read_evlib(path: str, fmt: str) -> int:
55
60
  # https://github.com/tallamjr/evlib (Rust-backed). Auto-detects the format;
56
- # load_events() returns a polars LazyFrame, so collect() to force decoding.
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.
57
63
  import evlib
64
+ import polars as pl
58
65
  df = evlib.load_events(str(path))
59
66
  if hasattr(df, "collect"):
60
- df = 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()
61
72
  return _count(df)
62
73
 
63
74
 
@@ -21,16 +21,19 @@ from readers import ALL_FORMATS, READERS
21
21
 
22
22
  @pytest.mark.parametrize("fmt", ALL_FORMATS)
23
23
  @pytest.mark.parametrize("reader", READERS, ids=lambda r: r.name)
24
- def test_read_compare(benchmark, real_event_files, reader, fmt):
24
+ def test_read_compare(benchmark, benchmark_rounds, real_event_files, reader, fmt):
25
25
  if fmt not in reader.formats:
26
26
  pytest.skip(f"{reader.name} does not support {fmt}")
27
27
 
28
- ef = real_event_files[fmt]
28
+ benchmark.group = f"read-{fmt}"
29
+ if fmt not in real_event_files or not real_event_files[fmt]:
30
+ pytest.skip(f"No files for format {fmt}")
31
+ ef = next((f for f in real_event_files[fmt] if 'hand' in f.path.name), real_event_files[fmt][0])
29
32
 
30
33
  try:
31
34
  n = benchmark.pedantic(
32
35
  lambda: reader.read(ef.path, fmt),
33
- rounds=3, iterations=1, warmup_rounds=1,
36
+ rounds=benchmark_rounds, iterations=1, warmup_rounds=1,
34
37
  )
35
38
  except ImportError as exc:
36
39
  pytest.skip(f"{reader.name} not available: {exc}")
@@ -0,0 +1,119 @@
1
+ """Read/write throughput benchmarks for the fixed-record formats: DAT and AER.
2
+
3
+ DAT reuses the shared ``reference_events`` (1280x720 fits its 14-bit coords).
4
+ AER only has 9-bit coordinates and no timestamps, so it uses a separate small
5
+ (GenX320-sized) synthetic fixture.
6
+
7
+ pytest benchmarks/test_fixed_formats.py
8
+
9
+ The read benchmarks decode a file generated once per session; the write
10
+ benchmarks measure encoding of the in-memory events. expelliarmus (if installed)
11
+ is compared on the DAT read.
12
+ """
13
+ import numpy as np
14
+ import pytest
15
+
16
+ from evutils.io import EventReader, EventWriter
17
+ from evutils.types import Event_dtype
18
+
19
+ AER_N = 5_000_000
20
+
21
+
22
+ @pytest.fixture(scope="session")
23
+ def aer_events():
24
+ """Small-resolution (GenX320) events for AER, which is 9-bit / timestamp-less."""
25
+ rng = np.random.default_rng(0)
26
+ ev = np.zeros(AER_N, dtype=Event_dtype)
27
+ ev["x"] = rng.integers(0, 320, AER_N)
28
+ ev["y"] = rng.integers(0, 320, AER_N)
29
+ ev["p"] = rng.integers(0, 2, AER_N)
30
+ return ev
31
+
32
+
33
+ @pytest.fixture(scope="session")
34
+ def dat_file(reference_events, tmp_path_factory):
35
+ path = tmp_path_factory.mktemp("dat") / "ref.dat"
36
+ with EventWriter(path) as writer:
37
+ writer.write(reference_events)
38
+ return path
39
+
40
+
41
+ @pytest.fixture(scope="session")
42
+ def aer_file(aer_events, tmp_path_factory):
43
+ path = tmp_path_factory.mktemp("aer") / "ref.aer"
44
+ with EventWriter(path) as writer:
45
+ writer.write(aer_events)
46
+ return path
47
+
48
+
49
+ def _read_all(path):
50
+ return len(EventReader(path).read_all())
51
+
52
+
53
+ def _write(path, events):
54
+ with EventWriter(path) as writer:
55
+ writer.write(events)
56
+
57
+
58
+ # --------------------------------------------------------------------------- #
59
+ # DAT
60
+ # --------------------------------------------------------------------------- #
61
+ @pytest.mark.benchmark(group="read-dat")
62
+ def test_read_dat_evutils(benchmark, benchmark_rounds, dat_file):
63
+ n = benchmark.pedantic(lambda: _read_all(dat_file), rounds=benchmark_rounds, iterations=1, warmup_rounds=1)
64
+ assert n > 0
65
+ benchmark.extra_info.update(library="evutils", n_events=n)
66
+
67
+
68
+ @pytest.mark.benchmark(group="read-dat")
69
+ def test_read_dat_expelliarmus(benchmark, benchmark_rounds, dat_file):
70
+ try:
71
+ from expelliarmus import Wizard
72
+ except ImportError as exc:
73
+ pytest.skip(f"expelliarmus not available: {exc}")
74
+ wizard = Wizard(encoding="dat")
75
+ n = benchmark.pedantic(lambda: len(wizard.read(str(dat_file))),
76
+ rounds=benchmark_rounds, iterations=1, warmup_rounds=1)
77
+ assert n > 0
78
+ benchmark.extra_info.update(library="expelliarmus", n_events=n)
79
+
80
+
81
+ @pytest.mark.benchmark(group="write-dat")
82
+ def test_write_dat_evutils(benchmark, benchmark_rounds, reference_events, tmp_path):
83
+ out = tmp_path / "out.dat"
84
+ benchmark.pedantic(lambda: _write(out, reference_events),
85
+ rounds=benchmark_rounds, iterations=1, warmup_rounds=1)
86
+ assert len(EventReader(out).read_all()) == len(reference_events)
87
+ benchmark.extra_info.update(library="evutils", n_events=len(reference_events))
88
+
89
+
90
+ @pytest.mark.benchmark(group="write-dat")
91
+ def test_write_dat_expelliarmus(benchmark, benchmark_rounds, reference_events, tmp_path):
92
+ try:
93
+ from expelliarmus import Wizard
94
+ except ImportError as exc:
95
+ pytest.skip(f"expelliarmus not available: {exc}")
96
+ wizard = Wizard(encoding="dat")
97
+ out = tmp_path / "out.dat"
98
+ benchmark.pedantic(lambda: wizard.save(str(out), reference_events),
99
+ rounds=benchmark_rounds, iterations=1, warmup_rounds=1)
100
+ assert len(wizard.read(str(out))) == len(reference_events)
101
+ benchmark.extra_info.update(library="expelliarmus", n_events=len(reference_events))
102
+
103
+
104
+ # --------------------------------------------------------------------------- #
105
+ # AER
106
+ # --------------------------------------------------------------------------- #
107
+ @pytest.mark.benchmark(group="read-aer")
108
+ def test_read_aer_evutils(benchmark, benchmark_rounds, aer_file):
109
+ n = benchmark.pedantic(lambda: _read_all(aer_file), rounds=benchmark_rounds, iterations=1, warmup_rounds=1)
110
+ assert n > 0
111
+ benchmark.extra_info.update(library="evutils", n_events=n)
112
+
113
+
114
+ @pytest.mark.benchmark(group="write-aer")
115
+ def test_write_aer_evutils(benchmark, benchmark_rounds, aer_events, tmp_path):
116
+ out = tmp_path / "out.aer"
117
+ benchmark.pedantic(lambda: _write(out, aer_events),
118
+ rounds=benchmark_rounds, iterations=1, warmup_rounds=1)
119
+ benchmark.extra_info.update(library="evutils", n_events=len(aer_events))
@@ -17,16 +17,20 @@ FORMATS = ["evt3", "evt2", "evt21"]
17
17
 
18
18
 
19
19
  @pytest.mark.parametrize("fmt", FORMATS)
20
- def test_read_evutils(benchmark, real_event_files, fmt):
21
- ef = real_event_files[fmt]
22
-
23
- def read_all():
24
- n = 0
25
- for chunk in EventReader(ef.path, n_events=5_000_000):
26
- n += len(chunk)
27
- return n
28
-
29
- n = benchmark.pedantic(read_all, rounds=3, iterations=1, warmup_rounds=1)
20
+ def test_read_evutils(benchmark, benchmark_rounds, real_event_files, fmt):
21
+ benchmark.group = f"read-{fmt}"
22
+ if fmt not in real_event_files or not real_event_files[fmt]:
23
+ pytest.skip(f"No files for format {fmt}")
24
+ ef = next((f for f in real_event_files[fmt] if 'hand' in f.path.name), real_event_files[fmt][0])
25
+
26
+ def decode():
27
+ total = 0
28
+ with EventReader(ef.path) as reader:
29
+ for chunk in reader:
30
+ total += len(chunk)
31
+ return total
32
+
33
+ n = benchmark.pedantic(decode, rounds=benchmark_rounds, iterations=1, warmup_rounds=1)
30
34
 
31
35
  # Sanity: the benchmarked path decodes the expected number of events.
32
36
  assert n == ef.count