evaluators 2.1.3__tar.gz → 3.0.0__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.
- {evaluators-2.1.3 → evaluators-3.0.0}/PKG-INFO +29 -4
- {evaluators-2.1.3 → evaluators-3.0.0}/README.md +27 -3
- {evaluators-2.1.3 → evaluators-3.0.0}/pyproject.toml +92 -3
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/__init__.py +2 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/_engine.py +11 -4
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/_streams.py +12 -4
- evaluators-3.0.0/sources/evaluators/benchmarks/coordinator.py +274 -0
- evaluators-3.0.0/sources/evaluators/benchmarks/scenarios.py +110 -0
- evaluators-3.0.0/sources/evaluators/benchmarks/sys_info.py +228 -0
- evaluators-3.0.0/sources/evaluators/benchmarks/worker.py +152 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/cli/__main__.py +1 -5
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/cli/compute.py +50 -10
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/cli/memory.py +93 -80
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/cli/third_party.py +19 -24
- evaluators-3.0.0/sources/evaluators/integrations/lightning.py +440 -0
- evaluators-3.0.0/sources/evaluators/io/__init__.py +7 -0
- evaluators-3.0.0/sources/evaluators/io/mot_challenge.py +136 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/__init__.py +36 -2
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/__init__.py +2 -1
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/_registry.py +56 -10
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/filesystem/_reader.py +4 -7
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/filesystem/_writer.py +4 -2
- evaluators-3.0.0/sources/evaluators/memory/backends/gds/__init__.py +3 -0
- evaluators-3.0.0/sources/evaluators/memory/backends/gds/reader.py +87 -0
- evaluators-3.0.0/sources/evaluators/memory/backends/gds/utils.py +17 -0
- evaluators-3.0.0/sources/evaluators/memory/backends/gds/writer.py +168 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/lmdb/_reader.py +44 -28
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/lmdb/_writer.py +18 -2
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/_buffers.py +30 -11
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/_reader.py +32 -11
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/_writer.py +56 -7
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_blosc.py +1 -1
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/config.py +6 -3
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/interfaces.py +11 -22
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/schema.py +1 -3
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/metrics/domain/depth.py +5 -11
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/metrics/domain/dvps.py +137 -79
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/__init__.py +4 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/_utils.py +11 -17
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/panoptic.py +410 -78
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/semantic.py +19 -28
- evaluators-3.0.0/sources/evaluators/metrics/domain/segmentation/vendor/__init__.py +7 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/segmentation/vendor/panopticapi.py +252 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/step.py +794 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/__init__.py +16 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_base.py +803 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_classcomb.py +73 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_clear.py +132 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_combine.py +38 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_hota.py +125 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_identity.py +66 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_preprocess.py +263 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_seqdata.py +119 -0
- evaluators-3.0.0/sources/evaluators/metrics/domain/tracking/_similarity.py +208 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/metrics/stats.py +3 -7
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/presets/__init__.py +3 -2
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/presets/cityscapes.py +60 -7
- evaluators-3.0.0/sources/evaluators/presets/mot.py +228 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/presets/semkitti.py +60 -1
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/scheduling/_catalog.py +3 -6
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/third_party/__init__.py +0 -1
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/__init__.py +0 -1
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/coco.py +30 -10
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/dvpq.py +5 -21
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/types.py +52 -2
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/utils/comms.py +1 -3
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/utils/configs.py +2 -4
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/utils/tensor_ops.py +5 -8
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/visuals/__init__.py +1 -1
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/visuals/metric.py +7 -14
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/visuals/tools/__init__.py +6 -0
- evaluators-3.0.0/sources/evaluators/visuals/tools/_visualizer.py +1414 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/visuals/tools/transform.py +10 -24
- evaluators-2.1.3/sources/evaluators/integrations/lightning.py +0 -265
- evaluators-2.1.3/sources/evaluators/metrics/domain/step.py +0 -393
- evaluators-2.1.3/sources/evaluators/visuals/tools/_visualizer.py +0 -779
- {evaluators-2.1.3/sources/evaluators/metrics → evaluators-3.0.0/sources/evaluators/benchmarks}/__init__.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/constants.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/filesystem/__init__.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/lmdb/__init__.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/__init__.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/codecs/__init__.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_png.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_raw.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_tiff.py +0 -0
- /evaluators-2.1.3/sources/evaluators/metrics/adapters/coco.py → /evaluators-3.0.0/sources/evaluators/metrics/__init__.py +0 -0
- /evaluators-2.1.3/sources/evaluators/metrics/adapters/trackeval.py → /evaluators-3.0.0/sources/evaluators/metrics/adapters/coco.py +0 -0
- /evaluators-2.1.3/sources/evaluators/metrics/domain/tracking.py → /evaluators-3.0.0/sources/evaluators/metrics/adapters/trackeval.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/presets/kitti.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/scheduling/__init__.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/scheduling/_partitioner.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/third_party/__main__.py +0 -0
- {evaluators-2.1.3 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/pq.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: evaluators
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: Various scene understanding and perception evaluation metrics.
|
|
5
5
|
Author: Kurt Stolle
|
|
6
6
|
Author-email: Kurt Stolle <kurt@computer.org>
|
|
@@ -27,6 +27,7 @@ Requires-Dist: pydantic
|
|
|
27
27
|
Requires-Dist: opencv-python>=4.13.0.92
|
|
28
28
|
Requires-Dist: omegaconf>=2.3.0
|
|
29
29
|
Requires-Dist: hydra-zen>=0.16.0
|
|
30
|
+
Requires-Dist: scipy>=1.14.0
|
|
30
31
|
Requires-Dist: blosc2>=4.1.2 ; extra == 'blosc2'
|
|
31
32
|
Requires-Dist: lightning>=2.6.1 ; extra == 'lightning'
|
|
32
33
|
Requires-Dist: lmdb>=2.2.0 ; extra == 'lmdb'
|
|
@@ -103,7 +104,7 @@ from evaluators.metrics.domain.segmentation import SemanticMetric
|
|
|
103
104
|
dataset = CityscapesDataset(...)
|
|
104
105
|
metric = SemanticMetric(
|
|
105
106
|
num_classes=19,
|
|
106
|
-
target_source=dataset,
|
|
107
|
+
target_source=dataset,
|
|
107
108
|
)
|
|
108
109
|
|
|
109
110
|
### Define the explicit memory schema
|
|
@@ -144,12 +145,12 @@ for batch in dataloader: # Model forward pass
|
|
|
144
145
|
"frame_index": batch["frame_index"]
|
|
145
146
|
}
|
|
146
147
|
)
|
|
147
|
-
|
|
148
|
+
|
|
148
149
|
# Finalize
|
|
149
150
|
writer.close()
|
|
150
151
|
```
|
|
151
152
|
|
|
152
|
-
> Note: `sequence_id` and `frame_index` must be `torch.int64`.
|
|
153
|
+
> Note: `sequence_id` and `frame_index` must be `torch.int64`.
|
|
153
154
|
|
|
154
155
|
##### 4. Finalize and compute
|
|
155
156
|
|
|
@@ -232,6 +233,30 @@ See [OFFLINE_EVALUATION.md](OFFLINE_EVALUATION.md) for detailed usage and design
|
|
|
232
233
|
|
|
233
234
|
---
|
|
234
235
|
|
|
236
|
+
## Performance
|
|
237
|
+
|
|
238
|
+
`evaluators` is built for high-throughput I/O. The following benchmarks were conducted using the **Comprehensive Memory Evaluation Suite (CMES)** on a mobile workstation (i7-12700H, NVMe SSD).
|
|
239
|
+
|
|
240
|
+
### Throughput (FPS)
|
|
241
|
+
|
|
242
|
+
| Backend | Codec | Resolution | Write FPS | Read FPS | Compression |
|
|
243
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
244
|
+
| **Memmap** | Raw | 512x1024 | 250.8 | 501.6 | 1.0x |
|
|
245
|
+
| **Memmap** | Blosc | 512x1024 | 59.7 | 119.3 | 0.85x |
|
|
246
|
+
| **LMDB** | Raw | 512x1024 | 13.3 | 26.7 | 1.0x |
|
|
247
|
+
| **LMDB** | PNG | 512x1024 | 27.2 | 13.6 | 0.62x |
|
|
248
|
+
| **Filesystem**| TIFF | 512x1024 | 124.9 | 249.7 | 0.89x |
|
|
249
|
+
|
|
250
|
+
### Insights
|
|
251
|
+
|
|
252
|
+
- **Memmap is king for raw throughput:** The `MemmapTemporalWriter` achieves >500 FPS for read operations on mid-resolution video frames, making it ideal for fast metric computation.
|
|
253
|
+
- **Blosc provides the best balance:** Using `BloscCodec` with `Memmap` offers significant storage savings with minimal CPU overhead compared to PNG/TIFF.
|
|
254
|
+
- **LMDB for stability:** While slower for sequential video access, LMDB provides robust ACID compliance and is preferred for random-access metadata or small feature vectors.
|
|
255
|
+
|
|
256
|
+
> Full benchmark reports, including memory usage (Peak RSS) and CPU overhead plots, are available in `docs/benchmarks/`.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
235
260
|
## Development
|
|
236
261
|
|
|
237
262
|
This project uses modern Python tooling for dependency management and quality assurance.
|
|
@@ -62,7 +62,7 @@ from evaluators.metrics.domain.segmentation import SemanticMetric
|
|
|
62
62
|
dataset = CityscapesDataset(...)
|
|
63
63
|
metric = SemanticMetric(
|
|
64
64
|
num_classes=19,
|
|
65
|
-
target_source=dataset,
|
|
65
|
+
target_source=dataset,
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
### Define the explicit memory schema
|
|
@@ -103,12 +103,12 @@ for batch in dataloader: # Model forward pass
|
|
|
103
103
|
"frame_index": batch["frame_index"]
|
|
104
104
|
}
|
|
105
105
|
)
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
# Finalize
|
|
108
108
|
writer.close()
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
> Note: `sequence_id` and `frame_index` must be `torch.int64`.
|
|
111
|
+
> Note: `sequence_id` and `frame_index` must be `torch.int64`.
|
|
112
112
|
|
|
113
113
|
##### 4. Finalize and compute
|
|
114
114
|
|
|
@@ -191,6 +191,30 @@ See [OFFLINE_EVALUATION.md](OFFLINE_EVALUATION.md) for detailed usage and design
|
|
|
191
191
|
|
|
192
192
|
---
|
|
193
193
|
|
|
194
|
+
## Performance
|
|
195
|
+
|
|
196
|
+
`evaluators` is built for high-throughput I/O. The following benchmarks were conducted using the **Comprehensive Memory Evaluation Suite (CMES)** on a mobile workstation (i7-12700H, NVMe SSD).
|
|
197
|
+
|
|
198
|
+
### Throughput (FPS)
|
|
199
|
+
|
|
200
|
+
| Backend | Codec | Resolution | Write FPS | Read FPS | Compression |
|
|
201
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
202
|
+
| **Memmap** | Raw | 512x1024 | 250.8 | 501.6 | 1.0x |
|
|
203
|
+
| **Memmap** | Blosc | 512x1024 | 59.7 | 119.3 | 0.85x |
|
|
204
|
+
| **LMDB** | Raw | 512x1024 | 13.3 | 26.7 | 1.0x |
|
|
205
|
+
| **LMDB** | PNG | 512x1024 | 27.2 | 13.6 | 0.62x |
|
|
206
|
+
| **Filesystem**| TIFF | 512x1024 | 124.9 | 249.7 | 0.89x |
|
|
207
|
+
|
|
208
|
+
### Insights
|
|
209
|
+
|
|
210
|
+
- **Memmap is king for raw throughput:** The `MemmapTemporalWriter` achieves >500 FPS for read operations on mid-resolution video frames, making it ideal for fast metric computation.
|
|
211
|
+
- **Blosc provides the best balance:** Using `BloscCodec` with `Memmap` offers significant storage savings with minimal CPU overhead compared to PNG/TIFF.
|
|
212
|
+
- **LMDB for stability:** While slower for sequential video access, LMDB provides robust ACID compliance and is preferred for random-access metadata or small feature vectors.
|
|
213
|
+
|
|
214
|
+
> Full benchmark reports, including memory usage (Peak RSS) and CPU overhead plots, are available in `docs/benchmarks/`.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
194
218
|
## Development
|
|
195
219
|
|
|
196
220
|
This project uses modern Python tooling for dependency management and quality assurance.
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "evaluators"
|
|
7
|
-
version = "
|
|
7
|
+
version = "3.0.0"
|
|
8
8
|
description = "Various scene understanding and perception evaluation metrics."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13, <3.14"
|
|
@@ -35,6 +35,7 @@ dependencies = [
|
|
|
35
35
|
"opencv-python>=4.13.0.92",
|
|
36
36
|
"omegaconf>=2.3.0",
|
|
37
37
|
"hydra-zen>=0.16.0",
|
|
38
|
+
"scipy>=1.14.0",
|
|
38
39
|
]
|
|
39
40
|
|
|
40
41
|
[project.optional-dependencies]
|
|
@@ -60,13 +61,27 @@ dev = [
|
|
|
60
61
|
"ty>=0.0.17",
|
|
61
62
|
"pytest-timeout>=2.4.0",
|
|
62
63
|
"uv-build>=0.10.9",
|
|
64
|
+
"pycocotools>=2.0.11",
|
|
65
|
+
"seaborn>=0.13.2",
|
|
66
|
+
"pandas>=3.0.2",
|
|
63
67
|
]
|
|
64
68
|
docs = ["numpydoc>=1.9.0", "sphinx>=8.2.3", "sphinx-rtd-theme>=3.0.2"]
|
|
65
69
|
notebooks = ["ipykernel>=6.30.1", "ipywidgets>=8.1.7", "jupyterlab>=4.4.9"]
|
|
70
|
+
fixtures = [
|
|
71
|
+
"transformers>=4.46.0",
|
|
72
|
+
"huggingface-hub>=0.25.0",
|
|
73
|
+
"accelerate>=1.0.0",
|
|
74
|
+
"safetensors>=0.4.5",
|
|
75
|
+
"scipy>=1.14.0",
|
|
76
|
+
"tifffile>=2026.3.3",
|
|
77
|
+
"imagecodecs>=2026.3.6",
|
|
78
|
+
"av>=13.0.0",
|
|
79
|
+
]
|
|
66
80
|
|
|
67
81
|
[tool.ruff]
|
|
68
82
|
target-version = 'py313'
|
|
69
83
|
line-length = 88
|
|
84
|
+
exclude = ["third_party"]
|
|
70
85
|
|
|
71
86
|
[tool.ruff.lint]
|
|
72
87
|
select = [
|
|
@@ -103,12 +118,54 @@ ignore = [
|
|
|
103
118
|
"SIM118", # usage of 'x in dict.keys()' -- conflicts with `tensordict`.
|
|
104
119
|
"PLR2004", # magic numbers
|
|
105
120
|
"PLW2901", # for loop variable overwritten
|
|
121
|
+
# Lazy/deferred imports are a deliberate, pervasive pattern (optional deps:
|
|
122
|
+
# matplotlib, codecs, heavy adapters) used to keep import and CLI startup
|
|
123
|
+
# cheap; PLC0415 (import-should-be-at-top) fights that design codebase-wide.
|
|
124
|
+
"PLC0415",
|
|
106
125
|
"D212",
|
|
107
126
|
"D203",
|
|
108
127
|
"D107", # docstring on __init__
|
|
109
128
|
"PT011", # pytest.raises too broad (restrictive)
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
# Pre-existing style noise that pervades the codebase.
|
|
130
|
+
"D100",
|
|
131
|
+
"D101",
|
|
132
|
+
"D102",
|
|
133
|
+
"D103",
|
|
134
|
+
"D104",
|
|
135
|
+
"D105",
|
|
136
|
+
"D200",
|
|
137
|
+
"D205",
|
|
138
|
+
"D400",
|
|
139
|
+
"D401",
|
|
140
|
+
"E501", # line length (many docstring tables, type hint lines).
|
|
141
|
+
"N806", # uppercase var names (tensor shape conventions).
|
|
142
|
+
"N812",
|
|
143
|
+
"N813",
|
|
144
|
+
"ARG001", # protocol/ABC stubs frequently take unused args.
|
|
145
|
+
"ARG002",
|
|
146
|
+
"C901", # complexity (compute loops are intrinsically complex).
|
|
147
|
+
"PLR0911",
|
|
148
|
+
"PLR0912",
|
|
149
|
+
"PLR0913",
|
|
150
|
+
"PLR0915",
|
|
151
|
+
"PT019", # fixture leading underscore (intentional in benchmarks).
|
|
152
|
+
"RUF001", # ambiguous unicode (math symbols in docstrings).
|
|
153
|
+
"RUF002",
|
|
154
|
+
"RUF003",
|
|
155
|
+
"PTH100",
|
|
156
|
+
"PTH103",
|
|
157
|
+
"PTH105",
|
|
158
|
+
"PTH109",
|
|
159
|
+
"PTH110",
|
|
160
|
+
"PTH118",
|
|
161
|
+
"PTH123",
|
|
162
|
+
"SIM117", # nested with statements.
|
|
163
|
+
"B017", # assertRaises(Exception) is acceptable in smoke tests.
|
|
164
|
+
"B911", # batched(strict=True).
|
|
165
|
+
"NPY002", # np.random.seed legacy use.
|
|
166
|
+
"PGH003", # blanket `# type: ignore`.
|
|
167
|
+
"E741", # ambiguous variable names (e.g. tensor `l`).
|
|
168
|
+
"PT012",
|
|
112
169
|
]
|
|
113
170
|
|
|
114
171
|
[tool.ruff.lint.per-file-ignores]
|
|
@@ -118,6 +175,28 @@ ignore = [
|
|
|
118
175
|
"E402", # Module level import not at top of file
|
|
119
176
|
]
|
|
120
177
|
|
|
178
|
+
[tool.ruff.lint.pydocstyle]
|
|
179
|
+
convention = "numpy"
|
|
180
|
+
ignore-decorators = ["typing.overload", "typing.override"]
|
|
181
|
+
|
|
182
|
+
[tool.ruff.lint.pylint]
|
|
183
|
+
max-args = 12
|
|
184
|
+
max-bool-expr = 8
|
|
185
|
+
max-branches = 16
|
|
186
|
+
|
|
187
|
+
[tool.ruff.lint.flake8-unused-arguments]
|
|
188
|
+
ignore-variadic-names = true
|
|
189
|
+
|
|
190
|
+
[tool.ruff.lint.flake8-import-conventions.aliases]
|
|
191
|
+
"matplotlib.pyplot" = "plt"
|
|
192
|
+
numpy = "np"
|
|
193
|
+
pandas = "pd"
|
|
194
|
+
seaborn = "sns"
|
|
195
|
+
"torchvision.transforms.v2" = "tvt"
|
|
196
|
+
"torchvision.transforms.v2.functional" = "tvt_fn"
|
|
197
|
+
"unipercept" = "up"
|
|
198
|
+
"hydra_zen" = "hz"
|
|
199
|
+
|
|
121
200
|
[tool.ruff.format]
|
|
122
201
|
docstring-code-format = true
|
|
123
202
|
docstring-code-line-length = "dynamic"
|
|
@@ -135,3 +214,13 @@ include = ["sources", "tests", "notebooks", "scripts"]
|
|
|
135
214
|
|
|
136
215
|
[tool.pytest.ini_options]
|
|
137
216
|
timeout = 10
|
|
217
|
+
# Vendored reference implementations under third_party/ ship their own
|
|
218
|
+
# environment-dependent test suites (e.g. TrackEval); never collect them as
|
|
219
|
+
# part of this project's tests. Mirrors the ruff `exclude = ["third_party"]`.
|
|
220
|
+
addopts = "--ignore=third_party"
|
|
221
|
+
markers = [
|
|
222
|
+
"integration: integration tests that exercise real pretrained model inference end-to-end (may require network for first-time weight download).",
|
|
223
|
+
"slow: tests with high wall-clock cost.",
|
|
224
|
+
"gpu: tests that require an accelerator (CUDA/ROCm).",
|
|
225
|
+
"serial: tests that must not co-run with siblings (large RAM allocation, GPU contention, or non-thread-safe global state); pytest-xdist groups them onto a single worker via ``xdist_group('serial')``.",
|
|
226
|
+
]
|
|
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING
|
|
|
22
22
|
from ._engine import run_offline_evaluation
|
|
23
23
|
from ._streams import BackendInterface, MetricStream, UnboundStreamWarning
|
|
24
24
|
from .memory import (
|
|
25
|
+
GDSTemporalWriter,
|
|
25
26
|
LMDBTemporalWriter,
|
|
26
27
|
MemmapTemporalWriter,
|
|
27
28
|
MemorySchema,
|
|
@@ -36,6 +37,7 @@ if TYPE_CHECKING:
|
|
|
36
37
|
__version__: str
|
|
37
38
|
__all__ = [
|
|
38
39
|
"BackendInterface",
|
|
40
|
+
"GDSTemporalWriter",
|
|
39
41
|
"LMDBTemporalWriter",
|
|
40
42
|
"MemmapTemporalWriter",
|
|
41
43
|
"MemorySchema",
|
|
@@ -193,12 +193,16 @@ def _evaluate_metrics(
|
|
|
193
193
|
for i, item in enumerate(work_items):
|
|
194
194
|
fragments = item.entry["fragments"]
|
|
195
195
|
sequence = open_virtual_sequence(artifact_path, item.sequence_id, fragments)
|
|
196
|
+
seq_len = len(sequence)
|
|
197
|
+
|
|
198
|
+
for metric in metrics:
|
|
199
|
+
metric.on_sequence_start(seq_len)
|
|
196
200
|
|
|
197
201
|
if show_progress:
|
|
198
202
|
with rich.progress.Progress() as progress:
|
|
199
203
|
task_total = progress.add_task(
|
|
200
204
|
f"Processing item {i + 1}/{len(work_items)}",
|
|
201
|
-
total=len(metrics) *
|
|
205
|
+
total=len(metrics) * seq_len,
|
|
202
206
|
)
|
|
203
207
|
|
|
204
208
|
# O11: Direct iteration avoids DataLoader IPC overhead.
|
|
@@ -222,6 +226,9 @@ def _evaluate_metrics(
|
|
|
222
226
|
for metric in metrics:
|
|
223
227
|
metric.update(**metric._map_batch_to_args(frame))
|
|
224
228
|
|
|
229
|
+
for metric in metrics:
|
|
230
|
+
metric.on_sequence_end()
|
|
231
|
+
|
|
225
232
|
sequence = None # Deallocate
|
|
226
233
|
|
|
227
234
|
# --- AGGREGATION ---
|
|
@@ -238,8 +245,8 @@ def _evaluate_metrics(
|
|
|
238
245
|
metric_name = metric.__class__.__name__
|
|
239
246
|
try:
|
|
240
247
|
score = metric.compute()
|
|
241
|
-
except Exception
|
|
242
|
-
logger.
|
|
248
|
+
except Exception:
|
|
249
|
+
logger.exception("Failed to compute %s", metric_name)
|
|
243
250
|
continue
|
|
244
251
|
|
|
245
252
|
if isinstance(score, MetricResult):
|
|
@@ -265,7 +272,7 @@ def _save_plots(
|
|
|
265
272
|
force: bool,
|
|
266
273
|
) -> None:
|
|
267
274
|
"""Call ``plot()`` on each metric and save the figure to *out_path*."""
|
|
268
|
-
import matplotlib.pyplot as plt
|
|
275
|
+
import matplotlib.pyplot as plt # lazy: matplotlib is optional
|
|
269
276
|
|
|
270
277
|
for metric in metrics:
|
|
271
278
|
name = metric.__class__.__name__
|
|
@@ -173,6 +173,15 @@ class MetricStream(nn.Module):
|
|
|
173
173
|
"""
|
|
174
174
|
self._backend = backend
|
|
175
175
|
|
|
176
|
+
def unbind(self) -> None:
|
|
177
|
+
"""Detach the backend, dropping the stream's reference to the writer.
|
|
178
|
+
|
|
179
|
+
Called by the infrastructure layer at the end of the validation epoch
|
|
180
|
+
so that the writer (and its memory-mapped buffers) can be reclaimed
|
|
181
|
+
without waiting for the next ``bind()`` to overwrite the slot.
|
|
182
|
+
"""
|
|
183
|
+
self._backend = None
|
|
184
|
+
|
|
176
185
|
@torch.compiler.disable
|
|
177
186
|
def update(self, batch: TensorDict) -> None:
|
|
178
187
|
"""
|
|
@@ -207,9 +216,7 @@ class MetricStream(nn.Module):
|
|
|
207
216
|
|
|
208
217
|
# 2. Plumbing / Renaming
|
|
209
218
|
if self.input_map:
|
|
210
|
-
# We
|
|
211
|
-
# rename_map = {v: k for k, v in self.input_map.items()}
|
|
212
|
-
# filtered_batch = filtered_batch.rename_keys_(rename_map, strict=False)
|
|
219
|
+
# We map model keys to metric keys as defined in the input map.
|
|
213
220
|
for metric_key, model_key in self.input_map.items():
|
|
214
221
|
if model_key in filtered_batch.keys():
|
|
215
222
|
filtered_batch.rename_key_(model_key, metric_key)
|
|
@@ -220,11 +227,12 @@ class MetricStream(nn.Module):
|
|
|
220
227
|
def _handle_unbound_state(self) -> None:
|
|
221
228
|
"""Handle the missing backend case with configurable tolerance."""
|
|
222
229
|
if self.strict:
|
|
223
|
-
|
|
230
|
+
msg = (
|
|
224
231
|
f"MetricStream '{self.name}' received data but is not bound to "
|
|
225
232
|
"a backend. Ensure DistributedEvaluationCallback is present "
|
|
226
233
|
"in the Trainer or set strict=False."
|
|
227
234
|
)
|
|
235
|
+
raise RuntimeError(msg)
|
|
228
236
|
|
|
229
237
|
if not self._has_warned:
|
|
230
238
|
warnings.warn(
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Coordinator for the Comprehensive Memory Evaluation Suite (CMES).
|
|
3
|
+
|
|
4
|
+
Manages the benchmark grid, spawns worker subprocesses, and aggregates results.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import itertools
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
17
|
+
import numpy as np
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import seaborn as sns
|
|
20
|
+
|
|
21
|
+
from .sys_info import get_machine_info, get_machine_slug
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _plot_results(df: pd.DataFrame, out_dir: Path) -> None:
|
|
27
|
+
"""Generate benchmark visualizations."""
|
|
28
|
+
sns.set_theme(style="whitegrid")
|
|
29
|
+
|
|
30
|
+
# 1. FPS Plot (Write & Read)
|
|
31
|
+
plot_df = df.melt(
|
|
32
|
+
id_vars=["backend", "codec", "complexity", "resolution"],
|
|
33
|
+
value_vars=["write_fps_mean", "read_fps_mean"],
|
|
34
|
+
var_name="op",
|
|
35
|
+
value_name="fps",
|
|
36
|
+
)
|
|
37
|
+
plot_df["op"] = plot_df["op"].str.replace("_fps_mean", "").str.capitalize()
|
|
38
|
+
|
|
39
|
+
g = sns.catplot(
|
|
40
|
+
data=plot_df,
|
|
41
|
+
kind="bar",
|
|
42
|
+
x="codec",
|
|
43
|
+
y="fps",
|
|
44
|
+
hue="backend",
|
|
45
|
+
col="op",
|
|
46
|
+
row="resolution",
|
|
47
|
+
height=4,
|
|
48
|
+
aspect=1.5,
|
|
49
|
+
sharey=False,
|
|
50
|
+
)
|
|
51
|
+
g.set(yscale="log")
|
|
52
|
+
g.fig.suptitle("Backend Throughput (FPS) - Log Scale", y=1.02)
|
|
53
|
+
g.savefig(out_dir / "throughput_fps.png")
|
|
54
|
+
plt.close()
|
|
55
|
+
|
|
56
|
+
# 2. MB/s Plot (Write & Read)
|
|
57
|
+
plot_mb = df.melt(
|
|
58
|
+
id_vars=["backend", "codec", "complexity", "resolution"],
|
|
59
|
+
value_vars=["write_mb_s_mean", "read_mb_s_mean"],
|
|
60
|
+
var_name="op",
|
|
61
|
+
value_name="mb_s",
|
|
62
|
+
)
|
|
63
|
+
plot_mb["op"] = plot_mb["op"].str.replace("_mb_s_mean", "").str.capitalize()
|
|
64
|
+
|
|
65
|
+
g = sns.catplot(
|
|
66
|
+
data=plot_mb,
|
|
67
|
+
kind="bar",
|
|
68
|
+
x="codec",
|
|
69
|
+
y="mb_s",
|
|
70
|
+
hue="backend",
|
|
71
|
+
col="op",
|
|
72
|
+
row="resolution",
|
|
73
|
+
height=4,
|
|
74
|
+
aspect=1.5,
|
|
75
|
+
sharey=False,
|
|
76
|
+
)
|
|
77
|
+
g.fig.suptitle("Backend Throughput (MB/s)", y=1.02)
|
|
78
|
+
g.savefig(out_dir / "throughput_mb_s.png")
|
|
79
|
+
plt.close()
|
|
80
|
+
|
|
81
|
+
# 3. Efficiency Plot
|
|
82
|
+
plt.figure(figsize=(12, 6))
|
|
83
|
+
sns.barplot(
|
|
84
|
+
data=df,
|
|
85
|
+
x="codec",
|
|
86
|
+
y="compression_ratio_mean",
|
|
87
|
+
hue="backend",
|
|
88
|
+
)
|
|
89
|
+
plt.title("Storage Efficiency (Compression Ratio)")
|
|
90
|
+
plt.ylabel("Ratio (Raw / Disk)")
|
|
91
|
+
plt.savefig(out_dir / "storage_efficiency.png")
|
|
92
|
+
plt.close()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _run_single_config(config: dict[str, Any], temp_dir: Path) -> dict[str, Any]:
|
|
96
|
+
"""Spawn a worker for a single run and return the stats."""
|
|
97
|
+
cmd = [
|
|
98
|
+
sys.executable,
|
|
99
|
+
"-m",
|
|
100
|
+
"evaluators.benchmarks.worker",
|
|
101
|
+
"--config",
|
|
102
|
+
json.dumps(config),
|
|
103
|
+
"--temp_dir",
|
|
104
|
+
str(temp_dir),
|
|
105
|
+
]
|
|
106
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
|
107
|
+
|
|
108
|
+
if result.returncode != 0:
|
|
109
|
+
msg = f"Worker failed with code {result.returncode}: {result.stderr}"
|
|
110
|
+
raise RuntimeError(msg)
|
|
111
|
+
|
|
112
|
+
output_lines = result.stdout.strip().splitlines()
|
|
113
|
+
if not output_lines:
|
|
114
|
+
msg = f"Worker produced no output: {result.stderr}"
|
|
115
|
+
raise RuntimeError(msg)
|
|
116
|
+
|
|
117
|
+
return json.loads(output_lines[-1])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def run_grid(
|
|
121
|
+
iters: int = 3,
|
|
122
|
+
limit_scenarios: bool = False,
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Execute the benchmark grid."""
|
|
125
|
+
slug = get_machine_slug()
|
|
126
|
+
out_dir = Path(f"docs/benchmarks/{slug}")
|
|
127
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
|
|
129
|
+
with (out_dir / "machine_info.json").open("w", encoding="utf-8") as f:
|
|
130
|
+
json.dump(get_machine_info(), f, indent=2)
|
|
131
|
+
|
|
132
|
+
backends = ["memmap", "lmdb", "filesystem", "gds"]
|
|
133
|
+
codecs = ["raw", "png", "tiff", "blosc"]
|
|
134
|
+
complexities = ["dense", "ragged", "sparse"]
|
|
135
|
+
resolutions = {
|
|
136
|
+
"feat1d": (1024,),
|
|
137
|
+
"low": (128, 256),
|
|
138
|
+
"mid": (512, 1024),
|
|
139
|
+
"high": (1024, 2048),
|
|
140
|
+
"feat3d": (64, 256, 512),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if limit_scenarios:
|
|
144
|
+
backends, codecs, complexities = (
|
|
145
|
+
["memmap", "lmdb", "gds"],
|
|
146
|
+
["raw", "blosc"],
|
|
147
|
+
["dense"],
|
|
148
|
+
)
|
|
149
|
+
resolutions = {"low": (128, 256)}
|
|
150
|
+
|
|
151
|
+
raw_results: list[dict[str, Any]] = []
|
|
152
|
+
permutations = list(
|
|
153
|
+
itertools.product(backends, codecs, complexities, resolutions.keys())
|
|
154
|
+
)
|
|
155
|
+
total = len(permutations) * iters
|
|
156
|
+
|
|
157
|
+
count = 0
|
|
158
|
+
for b_name, codec_name, comp, res_name in permutations:
|
|
159
|
+
# Skip invalid combinations
|
|
160
|
+
if codec_name in ["png", "tiff"] and res_name in ["feat1d", "feat3d"]:
|
|
161
|
+
continue
|
|
162
|
+
if comp != "dense" and res_name != "mid" and not limit_scenarios:
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
hw = resolutions[res_name]
|
|
166
|
+
shape = (1, *hw) if comp != "ragged" else (None, 4)
|
|
167
|
+
dtype = "uint8" if codec_name == "png" else "float32"
|
|
168
|
+
|
|
169
|
+
fields = {"data": {"dtype": dtype, "shape": list(shape), "codec": codec_name}}
|
|
170
|
+
if comp == "sparse":
|
|
171
|
+
fields["uncertainty"] = {
|
|
172
|
+
"dtype": dtype,
|
|
173
|
+
"shape": list(shape),
|
|
174
|
+
"codec": "raw",
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
config = {
|
|
178
|
+
"backend": b_name,
|
|
179
|
+
"codec": codec_name,
|
|
180
|
+
"complexity": comp,
|
|
181
|
+
"resolution": res_name,
|
|
182
|
+
"b": 1,
|
|
183
|
+
"t": 10,
|
|
184
|
+
"schema": {"fields": fields},
|
|
185
|
+
"backend_opts": {},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if b_name == "memmap":
|
|
189
|
+
config["backend_opts"]["chunk_size_frames"] = 1000
|
|
190
|
+
elif b_name == "lmdb":
|
|
191
|
+
config["backend_opts"]["map_size"] = 10 * 1024 * 1024 * 1024
|
|
192
|
+
|
|
193
|
+
for run_idx in range(iters):
|
|
194
|
+
count += 1
|
|
195
|
+
logger.info(
|
|
196
|
+
"[%d/%d] %s | %s | %s | %s | Run %d",
|
|
197
|
+
count,
|
|
198
|
+
total,
|
|
199
|
+
b_name,
|
|
200
|
+
codec_name,
|
|
201
|
+
comp,
|
|
202
|
+
res_name,
|
|
203
|
+
run_idx,
|
|
204
|
+
)
|
|
205
|
+
temp_path = Path(
|
|
206
|
+
f"tmp_bench_{b_name}_{codec_name}_{comp}_{res_name}_{run_idx}"
|
|
207
|
+
)
|
|
208
|
+
temp_path.mkdir(exist_ok=True)
|
|
209
|
+
try:
|
|
210
|
+
run_stats = _run_single_config(config, temp_path)
|
|
211
|
+
frames = run_stats["frames"]
|
|
212
|
+
# Calculate raw bytes for MB/s reporting
|
|
213
|
+
element_size = 4 if dtype == "float32" else 1
|
|
214
|
+
raw_bytes_per_frame = (
|
|
215
|
+
np.prod([s if s else 100 for s in shape]) * element_size
|
|
216
|
+
)
|
|
217
|
+
raw_bytes_total = frames * raw_bytes_per_frame
|
|
218
|
+
|
|
219
|
+
disk_bytes = run_stats["disk_bytes"]
|
|
220
|
+
run_stats.update(
|
|
221
|
+
{
|
|
222
|
+
"backend": b_name,
|
|
223
|
+
"codec": codec_name,
|
|
224
|
+
"complexity": comp,
|
|
225
|
+
"resolution": res_name,
|
|
226
|
+
"run": run_idx,
|
|
227
|
+
"write_fps": frames / run_stats["write_s"],
|
|
228
|
+
"read_fps": frames / run_stats["read_s"],
|
|
229
|
+
"write_mb_s": (raw_bytes_total / (1024**2))
|
|
230
|
+
/ run_stats["write_s"],
|
|
231
|
+
"read_mb_s": (raw_bytes_total / (1024**2))
|
|
232
|
+
/ run_stats["read_s"],
|
|
233
|
+
"compression_ratio": raw_bytes_total / disk_bytes
|
|
234
|
+
if disk_bytes > 0
|
|
235
|
+
else 1.0,
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
raw_results.append(run_stats)
|
|
239
|
+
except Exception:
|
|
240
|
+
logger.exception(" FAILED permutation: %s", b_name)
|
|
241
|
+
finally:
|
|
242
|
+
if temp_path.exists():
|
|
243
|
+
shutil.rmtree(temp_path)
|
|
244
|
+
|
|
245
|
+
if not raw_results:
|
|
246
|
+
logger.error("No results collected.")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
df = pd.DataFrame(raw_results)
|
|
250
|
+
stats = (
|
|
251
|
+
df.groupby(["backend", "codec", "complexity", "resolution"])
|
|
252
|
+
.agg(
|
|
253
|
+
{
|
|
254
|
+
"write_fps": ["mean", "std"],
|
|
255
|
+
"read_fps": ["mean", "std"],
|
|
256
|
+
"write_mb_s": ["mean", "std"],
|
|
257
|
+
"read_mb_s": ["mean", "std"],
|
|
258
|
+
"peak_rss_mb": ["mean", "max"],
|
|
259
|
+
"compression_ratio": ["mean"],
|
|
260
|
+
"avg_cpu": ["mean"],
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
.reset_index()
|
|
264
|
+
)
|
|
265
|
+
stats.columns = ["_".join(col).strip("_") for col in stats.columns.values]
|
|
266
|
+
stats.to_csv(out_dir / "backend_comparison.csv", index=False)
|
|
267
|
+
logger.info("Results saved to %s", out_dir / "backend_comparison.csv")
|
|
268
|
+
|
|
269
|
+
_plot_results(stats, out_dir)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
if __name__ == "__main__":
|
|
273
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
274
|
+
run_grid(limit_scenarios=True)
|