evaluators 2.1.4__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.4 → evaluators-3.0.0}/PKG-INFO +5 -4
- {evaluators-2.1.4 → evaluators-3.0.0}/README.md +3 -3
- {evaluators-2.1.4 → evaluators-3.0.0}/pyproject.toml +10 -1
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/_engine.py +1 -1
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/_streams.py +1 -3
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/benchmarks/sys_info.py +11 -7
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/cli/memory.py +5 -3
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/integrations/lightning.py +111 -6
- 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.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/_registry.py +3 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/gds/writer.py +1 -1
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/_buffers.py +2 -2
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/_writer.py +2 -3
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_blosc.py +1 -1
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/domain/depth.py +2 -2
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/domain/dvps.py +97 -15
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/__init__.py +4 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/_utils.py +1 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/panoptic.py +402 -62
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/domain/segmentation/semantic.py +15 -16
- 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-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/domain/step.py +5 -5
- 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.4 → evaluators-3.0.0}/sources/evaluators/metrics/stats.py +1 -1
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/presets/__init__.py +3 -2
- evaluators-3.0.0/sources/evaluators/presets/mot.py +228 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/coco.py +3 -4
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/utils/tensor_ops.py +2 -4
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/visuals/tools/_visualizer.py +12 -9
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/visuals/tools/transform.py +1 -1
- evaluators-2.1.4/sources/evaluators/metrics/domain/tracking.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/benchmarks/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/benchmarks/coordinator.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/benchmarks/scenarios.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/benchmarks/worker.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/cli/__main__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/cli/compute.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/cli/third_party.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/constants.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/filesystem/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/filesystem/_reader.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/filesystem/_writer.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/gds/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/gds/reader.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/gds/utils.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/lmdb/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/lmdb/_reader.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/lmdb/_writer.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/backends/memmap/_reader.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/codecs/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_png.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_raw.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/codecs/_tiff.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/config.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/interfaces.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/memory/schema.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/adapters/coco.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/metrics/adapters/trackeval.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/presets/cityscapes.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/presets/kitti.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/presets/semkitti.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/scheduling/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/scheduling/_catalog.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/scheduling/_partitioner.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/third_party/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/third_party/__main__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/dvpq.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/third_party/adapters/pq.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/types.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/utils/comms.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/utils/configs.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/visuals/__init__.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/visuals/metric.py +0 -0
- {evaluators-2.1.4 → evaluators-3.0.0}/sources/evaluators/visuals/tools/__init__.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
|
|
|
@@ -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
|
|
|
@@ -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]
|
|
@@ -117,6 +118,10 @@ ignore = [
|
|
|
117
118
|
"SIM118", # usage of 'x in dict.keys()' -- conflicts with `tensordict`.
|
|
118
119
|
"PLR2004", # magic numbers
|
|
119
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",
|
|
120
125
|
"D212",
|
|
121
126
|
"D203",
|
|
122
127
|
"D107", # docstring on __init__
|
|
@@ -209,6 +214,10 @@ include = ["sources", "tests", "notebooks", "scripts"]
|
|
|
209
214
|
|
|
210
215
|
[tool.pytest.ini_options]
|
|
211
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"
|
|
212
221
|
markers = [
|
|
213
222
|
"integration: integration tests that exercise real pretrained model inference end-to-end (may require network for first-time weight download).",
|
|
214
223
|
"slow: tests with high wall-clock cost.",
|
|
@@ -272,7 +272,7 @@ def _save_plots(
|
|
|
272
272
|
force: bool,
|
|
273
273
|
) -> None:
|
|
274
274
|
"""Call ``plot()`` on each metric and save the figure to *out_path*."""
|
|
275
|
-
import matplotlib.pyplot as plt
|
|
275
|
+
import matplotlib.pyplot as plt # lazy: matplotlib is optional
|
|
276
276
|
|
|
277
277
|
for metric in metrics:
|
|
278
278
|
name = metric.__class__.__name__
|
|
@@ -216,9 +216,7 @@ class MetricStream(nn.Module):
|
|
|
216
216
|
|
|
217
217
|
# 2. Plumbing / Renaming
|
|
218
218
|
if self.input_map:
|
|
219
|
-
# We
|
|
220
|
-
# rename_map = {v: k for k, v in self.input_map.items()}
|
|
221
|
-
# filtered_batch = filtered_batch.rename_keys_(rename_map, strict=False)
|
|
219
|
+
# We map model keys to metric keys as defined in the input map.
|
|
222
220
|
for metric_key, model_key in self.input_map.items():
|
|
223
221
|
if model_key in filtered_batch.keys():
|
|
224
222
|
filtered_batch.rename_key_(model_key, metric_key)
|
|
@@ -139,7 +139,7 @@ def get_machine_info() -> MachineInfo:
|
|
|
139
139
|
# CPU Info
|
|
140
140
|
try:
|
|
141
141
|
cpu_info = subprocess.check_output(
|
|
142
|
-
"lscpu",
|
|
142
|
+
["lscpu"], stderr=subprocess.DEVNULL
|
|
143
143
|
).decode()
|
|
144
144
|
for line in cpu_info.split("\n"):
|
|
145
145
|
if "Model name:" in line:
|
|
@@ -166,8 +166,14 @@ def get_machine_info() -> MachineInfo:
|
|
|
166
166
|
try:
|
|
167
167
|
mnt_out = (
|
|
168
168
|
subprocess.check_output(
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
[
|
|
170
|
+
"findmnt",
|
|
171
|
+
"-n",
|
|
172
|
+
"-o",
|
|
173
|
+
"SOURCE,FSTYPE",
|
|
174
|
+
"--target",
|
|
175
|
+
current_dir,
|
|
176
|
+
],
|
|
171
177
|
stderr=subprocess.DEVNULL,
|
|
172
178
|
)
|
|
173
179
|
.decode()
|
|
@@ -184,8 +190,7 @@ def get_machine_info() -> MachineInfo:
|
|
|
184
190
|
try:
|
|
185
191
|
parent = (
|
|
186
192
|
subprocess.check_output(
|
|
187
|
-
|
|
188
|
-
shell=True,
|
|
193
|
+
["lsblk", "-no", "PKNAME", target_dev],
|
|
189
194
|
stderr=subprocess.DEVNULL,
|
|
190
195
|
)
|
|
191
196
|
.decode()
|
|
@@ -204,8 +209,7 @@ def get_machine_info() -> MachineInfo:
|
|
|
204
209
|
try:
|
|
205
210
|
model_lines = (
|
|
206
211
|
subprocess.check_output(
|
|
207
|
-
|
|
208
|
-
shell=True,
|
|
212
|
+
["lsblk", "-no", "MODEL", target_dev],
|
|
209
213
|
stderr=subprocess.DEVNULL,
|
|
210
214
|
)
|
|
211
215
|
.decode()
|
|
@@ -289,9 +289,11 @@ def handle_validate(args):
|
|
|
289
289
|
status.update(f"[bold green]Validating {vid_id}...")
|
|
290
290
|
if args.quick:
|
|
291
291
|
# Just check if fragment files exist
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
292
|
+
errors.extend(
|
|
293
|
+
(vid_id, f"Chunk {frag['chunk_id']} missing")
|
|
294
|
+
for frag in entry["fragments"]
|
|
295
|
+
if not (path / frag["chunk_id"]).exists()
|
|
296
|
+
)
|
|
295
297
|
continue
|
|
296
298
|
|
|
297
299
|
try:
|
|
@@ -10,6 +10,7 @@ Exports
|
|
|
10
10
|
- ``DistributedInferenceWriter``: For Predict mode (Save only).
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
import contextlib
|
|
13
14
|
import gc
|
|
14
15
|
import logging
|
|
15
16
|
import shutil
|
|
@@ -24,6 +25,7 @@ from evaluators.memory import (
|
|
|
24
25
|
TemporalWriterProtocol,
|
|
25
26
|
save_evaluation_config,
|
|
26
27
|
)
|
|
28
|
+
from evaluators.types import MetricResult
|
|
27
29
|
from evaluators.utils import comms
|
|
28
30
|
from lightning import Callback, LightningModule, Trainer
|
|
29
31
|
|
|
@@ -55,15 +57,23 @@ def _default_evaluate_streams(
|
|
|
55
57
|
pl_module: LightningModule | None = None,
|
|
56
58
|
) -> dict[str, dict[str, dict[str, float]]]:
|
|
57
59
|
"""Default post-predict evaluator using offline evaluation."""
|
|
58
|
-
del trainer
|
|
60
|
+
del trainer
|
|
59
61
|
artifact_root = Path(artifact_root)
|
|
60
62
|
all_results: dict[str, dict[str, dict[str, float]]] = {}
|
|
61
63
|
|
|
64
|
+
# Pick the trainer's compute device for metric ``.update`` calls.
|
|
65
|
+
# ``run_offline_evaluation`` defaults to CPU if ``device`` is
|
|
66
|
+
# ``None``; the GPU dispatch shaves ~12 min off SemKITTI-DVPS
|
|
67
|
+
# finalize. See the ``DistributedEvaluationCallback._finalize_epoch``
|
|
68
|
+
# callsite for the longer rationale.
|
|
69
|
+
device = pl_module.device if pl_module is not None else None
|
|
70
|
+
|
|
62
71
|
for stream in streams:
|
|
63
72
|
stream_results = run_offline_evaluation(
|
|
64
73
|
metrics=stream.metrics,
|
|
65
74
|
artifact_dir=artifact_root / stream.name,
|
|
66
75
|
show_progress=False,
|
|
76
|
+
device=device,
|
|
67
77
|
)
|
|
68
78
|
all_results[stream.name] = stream_results
|
|
69
79
|
|
|
@@ -212,16 +222,18 @@ class DistributedEvaluationCallback(_DistributedWriterBase):
|
|
|
212
222
|
def on_validation_epoch_end(
|
|
213
223
|
self, trainer: Trainer, pl_module: LightningModule
|
|
214
224
|
) -> None:
|
|
215
|
-
self._finalize_epoch(pl_module, "val")
|
|
225
|
+
self._finalize_epoch(trainer, pl_module, "val")
|
|
216
226
|
|
|
217
227
|
def on_test_epoch_start(self, trainer: Trainer, pl_module: LightningModule) -> None:
|
|
218
228
|
self._setup_streams(pl_module)
|
|
219
229
|
|
|
220
230
|
def on_test_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None:
|
|
221
|
-
self._finalize_epoch(pl_module, "test")
|
|
231
|
+
self._finalize_epoch(trainer, pl_module, "test")
|
|
222
232
|
|
|
223
|
-
def _finalize_epoch(
|
|
224
|
-
|
|
233
|
+
def _finalize_epoch(
|
|
234
|
+
self, trainer: Trainer, pl_module: LightningModule, split: str
|
|
235
|
+
) -> None:
|
|
236
|
+
"""Close writers, run metrics, log scalars and plots to Lightning.
|
|
225
237
|
|
|
226
238
|
Writer-side memory maps are released before ``run_offline_evaluation``
|
|
227
239
|
opens reader-side maps over the same files; otherwise the two sets
|
|
@@ -238,6 +250,17 @@ class DistributedEvaluationCallback(_DistributedWriterBase):
|
|
|
238
250
|
metrics=stream.metrics,
|
|
239
251
|
artifact_dir=stream_dir,
|
|
240
252
|
show_progress=False,
|
|
253
|
+
# Route per-frame metric ``.update`` to the same
|
|
254
|
+
# device the trainer is using. Without this,
|
|
255
|
+
# ``run_offline_evaluation`` defaults to ``None`` →
|
|
256
|
+
# CPU, which made full-validation metric finalize
|
|
257
|
+
# take ~12 min on the SemKITTI-DVPS val split
|
|
258
|
+
# (255 frames, PQ + DVPQ {k=1,5,10,20} +
|
|
259
|
+
# SemanticMetric + DepthMetric). Panopticapi PQ
|
|
260
|
+
# matching is still CPU-only (pure Python), but
|
|
261
|
+
# the tensor reductions for the other three
|
|
262
|
+
# metrics + the DVPQ window stacks move to GPU.
|
|
263
|
+
device=pl_module.device,
|
|
241
264
|
)
|
|
242
265
|
|
|
243
266
|
if comms.get_rank() != 0:
|
|
@@ -249,7 +272,13 @@ class DistributedEvaluationCallback(_DistributedWriterBase):
|
|
|
249
272
|
log_key = f"{split}/{stream_name}/{metric_cls}/{key}"
|
|
250
273
|
pl_module.log(log_key, val, sync_dist=True)
|
|
251
274
|
|
|
252
|
-
# 2.
|
|
275
|
+
# 2. Per-metric plots (when the metric implements ``plot()``
|
|
276
|
+
# and at least one Lightning logger supports image upload).
|
|
277
|
+
self._log_metric_plots(
|
|
278
|
+
trainer, pl_module, split, stream_name, stream, stream_results
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# 3. Print Summary Tables (Interactive/Human-Friendly)
|
|
253
282
|
if sys.stdout.isatty():
|
|
254
283
|
self._print_stream_summary(stream_name, stream)
|
|
255
284
|
|
|
@@ -263,6 +292,82 @@ class DistributedEvaluationCallback(_DistributedWriterBase):
|
|
|
263
292
|
self._streams = {}
|
|
264
293
|
gc.collect()
|
|
265
294
|
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _log_metric_plots(
|
|
297
|
+
trainer: Trainer,
|
|
298
|
+
pl_module: LightningModule,
|
|
299
|
+
split: str,
|
|
300
|
+
stream_name: str,
|
|
301
|
+
stream: MetricStream,
|
|
302
|
+
stream_results: dict[str, dict[str, float]],
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Render each metric's ``plot()`` and push the figure to every
|
|
305
|
+
Lightning logger that exposes ``log_image`` (e.g. ``WandbLogger``).
|
|
306
|
+
|
|
307
|
+
Iterates ``trainer.loggers`` so multi-logger setups (such as
|
|
308
|
+
``[CSVLogger, WandbLogger]``) are handled correctly. Quietly skips
|
|
309
|
+
metrics that have no ``plot`` method, return None, or raise during
|
|
310
|
+
rendering; observability failures are not load-bearing for training
|
|
311
|
+
and must not crash the validation epoch.
|
|
312
|
+
|
|
313
|
+
Calls ``plot(val=...)`` with a precomputed :class:`MetricResult` so
|
|
314
|
+
the metric does not re-invoke ``compute()`` on state that
|
|
315
|
+
``run_offline_evaluation`` has already consumed.
|
|
316
|
+
"""
|
|
317
|
+
image_loggers = [
|
|
318
|
+
logger_obj
|
|
319
|
+
for logger_obj in getattr(trainer, "loggers", []) or []
|
|
320
|
+
if callable(getattr(logger_obj, "log_image", None))
|
|
321
|
+
]
|
|
322
|
+
if not image_loggers:
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
for metric in stream.metrics:
|
|
326
|
+
plot_fn = getattr(metric, "plot", None)
|
|
327
|
+
if plot_fn is None or not callable(plot_fn):
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
metric_name = metric.__class__.__name__
|
|
331
|
+
scores = stream_results.get(metric_name)
|
|
332
|
+
if scores is None:
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
val = MetricResult.from_dict(scores)
|
|
336
|
+
try:
|
|
337
|
+
result = plot_fn(val=val)
|
|
338
|
+
except Exception:
|
|
339
|
+
logger.exception(
|
|
340
|
+
"metric %s.plot() raised; skipping plot upload", metric_name
|
|
341
|
+
)
|
|
342
|
+
continue
|
|
343
|
+
if result is None:
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
fig = result[0] if isinstance(result, tuple) else result
|
|
347
|
+
try:
|
|
348
|
+
for image_logger in image_loggers:
|
|
349
|
+
try:
|
|
350
|
+
image_logger.log_image(
|
|
351
|
+
key=f"{split}/{stream_name}/{metric_name}/plot",
|
|
352
|
+
images=[fig],
|
|
353
|
+
step=getattr(pl_module, "global_step", None),
|
|
354
|
+
)
|
|
355
|
+
except Exception:
|
|
356
|
+
logger.exception(
|
|
357
|
+
"log_image failed for %s; skipping plot upload",
|
|
358
|
+
metric_name,
|
|
359
|
+
)
|
|
360
|
+
finally:
|
|
361
|
+
close_fn = getattr(fig, "close", None)
|
|
362
|
+
if close_fn is not None and callable(close_fn):
|
|
363
|
+
with contextlib.suppress(Exception):
|
|
364
|
+
close_fn()
|
|
365
|
+
else:
|
|
366
|
+
with contextlib.suppress(Exception):
|
|
367
|
+
import matplotlib.pyplot as plt
|
|
368
|
+
|
|
369
|
+
plt.close(fig)
|
|
370
|
+
|
|
266
371
|
@staticmethod
|
|
267
372
|
def _print_stream_summary(stream_name: str, stream: MetricStream) -> None:
|
|
268
373
|
from rich.console import Console
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""MOTChallenge ground-truth / tracker text-file reader.
|
|
2
|
+
|
|
3
|
+
Parses MOTChallenge CSV rows
|
|
4
|
+
``frame, id, bb_left, bb_top, bb_width, bb_height, conf, class, visibility``
|
|
5
|
+
for both the gt file and a tracker results file, and returns a list of
|
|
6
|
+
:class:`~evaluators.metrics.domain.tracking._preprocess.RawFrame` (one per
|
|
7
|
+
timestep) ready for the MOTChallenge preprocessor.
|
|
8
|
+
|
|
9
|
+
This module is I/O only; it does no scoring. In MOT gt files the 7th column
|
|
10
|
+
(``conf``) is the 0/1 "consider" flag, exposed here as ``gt_zero_marked``; in
|
|
11
|
+
tracker files the same column is the detection confidence.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import configparser
|
|
17
|
+
import csv
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
from evaluators.metrics.domain.tracking._preprocess import RawFrame
|
|
23
|
+
|
|
24
|
+
__all__ = ["read_mot_sequence", "read_seq_length"]
|
|
25
|
+
|
|
26
|
+
# Column layout (0-based) of a MOTChallenge CSV row.
|
|
27
|
+
_COL_FRAME = 0
|
|
28
|
+
_COL_ID = 1
|
|
29
|
+
_COL_BOX = slice(2, 6) # bb_left, bb_top, bb_width, bb_height (xywh)
|
|
30
|
+
_COL_CONF = 6 # tracker confidence OR gt zero-marked flag
|
|
31
|
+
_COL_CLASS = 7
|
|
32
|
+
_MIN_COLS = 7 # frame..conf are always required; class defaults to 1 if absent.
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _read_rows(path: Path) -> dict[int, list[list[float]]]:
|
|
36
|
+
"""Parse a MOTChallenge file into ``{frame -> list of float rows}``."""
|
|
37
|
+
by_frame: dict[int, list[list[float]]] = {}
|
|
38
|
+
text = Path(path).read_text()
|
|
39
|
+
reader = csv.reader(text.splitlines())
|
|
40
|
+
for raw in reader:
|
|
41
|
+
row = [c for c in raw if c.strip() != ""]
|
|
42
|
+
if len(row) < _MIN_COLS:
|
|
43
|
+
continue
|
|
44
|
+
values = [float(c) for c in row]
|
|
45
|
+
frame = int(values[_COL_FRAME])
|
|
46
|
+
by_frame.setdefault(frame, []).append(values)
|
|
47
|
+
return by_frame
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _frame_arrays(
|
|
51
|
+
rows: list[list[float]],
|
|
52
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
53
|
+
"""Convert raw float rows for one frame into ``(boxes, ids, classes, conf)``."""
|
|
54
|
+
if not rows:
|
|
55
|
+
return (
|
|
56
|
+
np.zeros((0, 4), dtype=np.float64),
|
|
57
|
+
np.zeros((0,), dtype=np.int64),
|
|
58
|
+
np.zeros((0,), dtype=np.int64),
|
|
59
|
+
np.zeros((0,), dtype=np.float64),
|
|
60
|
+
)
|
|
61
|
+
data = np.asarray(rows, dtype=np.float64)
|
|
62
|
+
boxes = data[:, _COL_BOX].astype(np.float64)
|
|
63
|
+
ids = data[:, _COL_ID].astype(np.int64)
|
|
64
|
+
conf = data[:, _COL_CONF].astype(np.float64)
|
|
65
|
+
if data.shape[1] > _COL_CLASS:
|
|
66
|
+
classes = data[:, _COL_CLASS].astype(np.int64)
|
|
67
|
+
else:
|
|
68
|
+
classes = np.ones_like(ids)
|
|
69
|
+
return boxes, ids, classes, conf
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def read_seq_length(seqinfo: str | Path) -> int:
|
|
73
|
+
"""Read ``seqLength`` from a MOTChallenge ``seqinfo.ini``."""
|
|
74
|
+
parser = configparser.ConfigParser()
|
|
75
|
+
parser.read(seqinfo)
|
|
76
|
+
return int(parser["Sequence"]["seqLength"])
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def read_mot_sequence(
|
|
80
|
+
gt_path: str | Path,
|
|
81
|
+
ts_path: str | Path,
|
|
82
|
+
*,
|
|
83
|
+
seqinfo: str | Path | None = None,
|
|
84
|
+
) -> list[RawFrame]:
|
|
85
|
+
"""Read a MOTChallenge gt + tracker sequence into per-frame raw data.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
gt_path
|
|
90
|
+
Path to the ground-truth file (``gt.txt``).
|
|
91
|
+
ts_path
|
|
92
|
+
Path to the tracker results file (``det.txt`` / results).
|
|
93
|
+
seqinfo
|
|
94
|
+
Optional path to ``seqinfo.ini``. If given, frames are padded to
|
|
95
|
+
``seqLength`` (trailing empty frames become empty arrays). If absent, the
|
|
96
|
+
number of frames is the maximum frame index present across either file.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
list of RawFrame
|
|
101
|
+
One :class:`RawFrame` per timestep, in frame order (index ``t`` is file
|
|
102
|
+
frame ``t + 1``). ``gt_zero_marked`` is taken from the gt ``conf`` column;
|
|
103
|
+
tracker ``conf`` becomes ``tracker_confidences``.
|
|
104
|
+
"""
|
|
105
|
+
gt_by_frame = _read_rows(Path(gt_path))
|
|
106
|
+
ts_by_frame = _read_rows(Path(ts_path))
|
|
107
|
+
|
|
108
|
+
if seqinfo is not None:
|
|
109
|
+
num_frames = read_seq_length(seqinfo)
|
|
110
|
+
else:
|
|
111
|
+
max_gt = max(gt_by_frame, default=0)
|
|
112
|
+
max_ts = max(ts_by_frame, default=0)
|
|
113
|
+
num_frames = max(max_gt, max_ts)
|
|
114
|
+
|
|
115
|
+
frames: list[RawFrame] = []
|
|
116
|
+
for t in range(num_frames):
|
|
117
|
+
file_frame = t + 1
|
|
118
|
+
gt_boxes, gt_ids, gt_classes, gt_conf = _frame_arrays(
|
|
119
|
+
gt_by_frame.get(file_frame, [])
|
|
120
|
+
)
|
|
121
|
+
ts_boxes, ts_ids, ts_classes, ts_conf = _frame_arrays(
|
|
122
|
+
ts_by_frame.get(file_frame, [])
|
|
123
|
+
)
|
|
124
|
+
frames.append(
|
|
125
|
+
RawFrame(
|
|
126
|
+
gt_boxes=gt_boxes,
|
|
127
|
+
gt_ids=gt_ids,
|
|
128
|
+
gt_classes=gt_classes,
|
|
129
|
+
gt_zero_marked=gt_conf.astype(np.int64),
|
|
130
|
+
tracker_boxes=ts_boxes,
|
|
131
|
+
tracker_ids=ts_ids,
|
|
132
|
+
tracker_classes=ts_classes,
|
|
133
|
+
tracker_confidences=ts_conf,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
return frames
|
|
@@ -113,6 +113,9 @@ def _validate_non_lmdb_chunks(artifact_dir: Path, chunk_ids: set[str]) -> None:
|
|
|
113
113
|
raise FileNotFoundError(msg)
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
# Backend reader imports are deferred to per-backend dispatchers so importing
|
|
117
|
+
# the registry does not pull every backend's optional dependencies (lmdb,
|
|
118
|
+
# torch.cuda.gds, etc.) at module load.
|
|
116
119
|
def _open_lmdb_sequence(fragments: Sequence[interfaces.Fragment], artifact_dir: Path):
|
|
117
120
|
from .lmdb._reader import LMDBVirtualSequence
|
|
118
121
|
|
|
@@ -40,7 +40,7 @@ class GDSTemporalWriter:
|
|
|
40
40
|
) -> None:
|
|
41
41
|
# Check for torch.cuda.gds support
|
|
42
42
|
try:
|
|
43
|
-
import torch.cuda.gds
|
|
43
|
+
import torch.cuda.gds # optional CUDA submodule probe
|
|
44
44
|
|
|
45
45
|
if not torch.cuda.gds.is_available():
|
|
46
46
|
msg = "NVIDIA GPUDirect Storage (GDS) is not available."
|
|
@@ -72,8 +72,8 @@ class RaggedBuffer:
|
|
|
72
72
|
filename=directory / f"{filename_prefix}_vals.memmap",
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
# 2. Offsets Buffer (The index)
|
|
76
|
-
# Stores
|
|
75
|
+
# 2. Offsets Buffer (The index).
|
|
76
|
+
# Stores the start and end pointers for each row.
|
|
77
77
|
# We need capacity_rows + 1 to store the final end pointer.
|
|
78
78
|
self.offsets = MemoryMappedTensor.empty(
|
|
79
79
|
(capacity_rows + 1,),
|
|
@@ -6,6 +6,7 @@ both fixed-size dense tensors and codec-encoded variable-length payloads under
|
|
|
6
6
|
one implementation.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import contextlib
|
|
9
10
|
import gc
|
|
10
11
|
import json
|
|
11
12
|
from pathlib import Path
|
|
@@ -335,10 +336,8 @@ class MemmapTemporalWriter:
|
|
|
335
336
|
|
|
336
337
|
def __del__(self) -> None:
|
|
337
338
|
# Best-effort cleanup if close() was skipped due to an exception path.
|
|
338
|
-
|
|
339
|
+
with contextlib.suppress(Exception):
|
|
339
340
|
self._release_buffers()
|
|
340
|
-
except Exception:
|
|
341
|
-
pass
|
|
342
341
|
|
|
343
342
|
def _validate_special_key(self, key: str, tensor: object) -> None:
|
|
344
343
|
if not torch.is_tensor(tensor):
|
|
@@ -485,7 +485,7 @@ class DepthMetric(BaseMetric):
|
|
|
485
485
|
The matplotlib figure and axes.
|
|
486
486
|
|
|
487
487
|
"""
|
|
488
|
-
import matplotlib.pyplot as plt
|
|
488
|
+
import matplotlib.pyplot as plt # lazy: matplotlib optional
|
|
489
489
|
|
|
490
490
|
result = val if val is not None else self.compute()
|
|
491
491
|
d = result.to_dict() if hasattr(result, "to_dict") else dict(result)
|
|
@@ -579,7 +579,7 @@ class DepthMetric(BaseMetric):
|
|
|
579
579
|
Depth metrics table.
|
|
580
580
|
|
|
581
581
|
"""
|
|
582
|
-
import pandas as pd
|
|
582
|
+
import pandas as pd # lazy: pandas optional
|
|
583
583
|
|
|
584
584
|
result = val if val is not None else self.compute()
|
|
585
585
|
d = result.to_dict() if hasattr(result, "to_dict") else dict(result)
|