evaluators 2.1.0__tar.gz → 2.1.2__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.0 → evaluators-2.1.2}/PKG-INFO +12 -5
- {evaluators-2.1.0 → evaluators-2.1.2}/README.md +2 -2
- {evaluators-2.1.0 → evaluators-2.1.2}/pyproject.toml +15 -4
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/__init__.py +11 -5
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/_engine.py +3 -3
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/_streams.py +1 -0
- evaluators-2.1.2/sources/evaluators/cli/compute.py +167 -0
- evaluators-2.1.2/sources/evaluators/cli/third_party.py +177 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/integrations/lightning.py +72 -3
- evaluators-2.1.2/sources/evaluators/memory/__init__.py +146 -0
- evaluators-2.1.2/sources/evaluators/memory/backends/__init__.py +11 -0
- evaluators-2.1.2/sources/evaluators/memory/backends/_registry.py +192 -0
- evaluators-2.1.2/sources/evaluators/memory/backends/filesystem/__init__.py +8 -0
- evaluators-2.1.2/sources/evaluators/memory/backends/filesystem/_reader.py +157 -0
- evaluators-2.1.2/sources/evaluators/memory/backends/filesystem/_writer.py +184 -0
- {evaluators-2.1.0/sources/evaluators/memory → evaluators-2.1.2/sources/evaluators/memory/backends}/lmdb/_reader.py +42 -57
- evaluators-2.1.2/sources/evaluators/memory/backends/lmdb/_writer.py +202 -0
- evaluators-2.1.2/sources/evaluators/memory/backends/memmap/_reader.py +242 -0
- evaluators-2.1.2/sources/evaluators/memory/backends/memmap/_writer.py +302 -0
- evaluators-2.1.2/sources/evaluators/memory/codecs/__init__.py +42 -0
- evaluators-2.1.2/sources/evaluators/memory/codecs/_blosc.py +138 -0
- evaluators-2.1.2/sources/evaluators/memory/codecs/_png.py +60 -0
- evaluators-2.1.2/sources/evaluators/memory/codecs/_raw.py +35 -0
- evaluators-2.1.2/sources/evaluators/memory/codecs/_tiff.py +193 -0
- evaluators-2.1.2/sources/evaluators/memory/config.py +73 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/memory/interfaces.py +33 -3
- evaluators-2.1.2/sources/evaluators/memory/schema.py +96 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/depth.py +1 -1
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/dvps.py +1 -1
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/segmentation/panoptic.py +4 -4
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/segmentation/semantic.py +2 -2
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/step.py +2 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/scheduling/_catalog.py +2 -0
- evaluators-2.1.2/sources/evaluators/third_party/__init__.py +7 -0
- evaluators-2.1.2/sources/evaluators/third_party/__main__.py +6 -0
- evaluators-2.1.2/sources/evaluators/third_party/adapters/__init__.py +4 -0
- evaluators-2.1.2/sources/evaluators/third_party/adapters/coco.py +312 -0
- evaluators-2.1.2/sources/evaluators/third_party/adapters/dvpq.py +262 -0
- evaluators-2.1.2/sources/evaluators/third_party/adapters/pq.py +26 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/types.py +56 -1
- evaluators-2.1.2/sources/evaluators/utils/configs.py +62 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/visuals/metric.py +6 -2
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/visuals/tools/__init__.py +7 -1
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/visuals/tools/_visualizer.py +14 -42
- evaluators-2.1.0/sources/evaluators/memory/__init__.py +0 -43
- evaluators-2.1.0/sources/evaluators/memory/encoded_memmap/__init__.py +0 -13
- evaluators-2.1.0/sources/evaluators/memory/encoded_memmap/_reader.py +0 -176
- evaluators-2.1.0/sources/evaluators/memory/encoded_memmap/_writer.py +0 -196
- evaluators-2.1.0/sources/evaluators/memory/lmdb/_writer.py +0 -180
- evaluators-2.1.0/sources/evaluators/memory/memmap/_reader.py +0 -218
- evaluators-2.1.0/sources/evaluators/memory/memmap/_writer.py +0 -272
- evaluators-2.1.0/sources/evaluators/memory/schema.py +0 -60
- evaluators-2.1.0/sources/evaluators/memory/utils/encoding.py +0 -102
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/cli/__main__.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/cli/memory.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/constants.py +0 -0
- {evaluators-2.1.0/sources/evaluators/memory → evaluators-2.1.2/sources/evaluators/memory/backends}/lmdb/__init__.py +0 -0
- {evaluators-2.1.0/sources/evaluators/memory → evaluators-2.1.2/sources/evaluators/memory/backends}/memmap/__init__.py +0 -0
- {evaluators-2.1.0/sources/evaluators/memory → evaluators-2.1.2/sources/evaluators/memory/backends}/memmap/_buffers.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/__init__.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/adapters/coco.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/adapters/trackeval.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/segmentation/__init__.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/segmentation/_utils.py +2 -2
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/domain/tracking.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/metrics/stats.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/presets/__init__.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/presets/cityscapes.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/presets/kitti.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/presets/semkitti.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/scheduling/__init__.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/scheduling/_partitioner.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/utils/comms.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/utils/tensor_ops.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/visuals/__init__.py +0 -0
- {evaluators-2.1.0 → evaluators-2.1.2}/sources/evaluators/visuals/tools/transform.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: evaluators
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Various scene understanding and perception evaluation metrics.
|
|
5
5
|
Author: Kurt Stolle
|
|
6
6
|
Author-email: Kurt Stolle <kurt@computer.org>
|
|
@@ -23,13 +23,20 @@ Requires-Dist: tensordict>=0.11.0
|
|
|
23
23
|
Requires-Dist: numpy>=2.0.0
|
|
24
24
|
Requires-Dist: matplotlib>=3.9.0
|
|
25
25
|
Requires-Dist: rich>=13.9.4
|
|
26
|
-
Requires-Dist: lmdb
|
|
27
|
-
Requires-Dist: tifffile
|
|
28
26
|
Requires-Dist: pydantic
|
|
29
27
|
Requires-Dist: opencv-python>=4.13.0.92
|
|
28
|
+
Requires-Dist: omegaconf>=2.3.0
|
|
29
|
+
Requires-Dist: hydra-zen>=0.16.0
|
|
30
|
+
Requires-Dist: blosc2>=4.1.2 ; extra == 'blosc2'
|
|
30
31
|
Requires-Dist: lightning>=2.6.1 ; extra == 'lightning'
|
|
32
|
+
Requires-Dist: lmdb>=2.2.0 ; extra == 'lmdb'
|
|
33
|
+
Requires-Dist: imagecodecs>=2026.3.6 ; extra == 'tiff'
|
|
34
|
+
Requires-Dist: tifffile>=2026.3.3 ; extra == 'tiff'
|
|
31
35
|
Requires-Python: >=3.13, <3.14
|
|
36
|
+
Provides-Extra: blosc2
|
|
32
37
|
Provides-Extra: lightning
|
|
38
|
+
Provides-Extra: lmdb
|
|
39
|
+
Provides-Extra: tiff
|
|
33
40
|
Description-Content-Type: text/markdown
|
|
34
41
|
|
|
35
42
|
# Scalable Distributed Evaluation for Computer Vision
|
|
@@ -218,10 +225,10 @@ The evaluation pipeline consists of three stages.
|
|
|
218
225
|
Each GPU writes predictions to locally sharded files (e.g. `.memmap` or `.lmdb`). No communication occurs.
|
|
219
226
|
2. **Schedule.**
|
|
220
227
|
A synchronization barrier is reached. The main process aggregates metadata manifests from all shards to build a Global Catalog. It partitions the workload (videos) among workers using a greedy strategy to balance duration.
|
|
221
|
-
3. **Compute.**
|
|
228
|
+
3. **Compute.**
|
|
222
229
|
Workers iterate through their assigned Virtual Sequences. Data is streamed from disk, processed by torchmetrics, and reduced globally.
|
|
223
230
|
|
|
224
|
-
See [
|
|
231
|
+
See [OFFLINE_EVALUATION.md](OFFLINE_EVALUATION.md) for detailed usage and design principles.
|
|
225
232
|
|
|
226
233
|
---
|
|
227
234
|
|
|
@@ -184,10 +184,10 @@ The evaluation pipeline consists of three stages.
|
|
|
184
184
|
Each GPU writes predictions to locally sharded files (e.g. `.memmap` or `.lmdb`). No communication occurs.
|
|
185
185
|
2. **Schedule.**
|
|
186
186
|
A synchronization barrier is reached. The main process aggregates metadata manifests from all shards to build a Global Catalog. It partitions the workload (videos) among workers using a greedy strategy to balance duration.
|
|
187
|
-
3. **Compute.**
|
|
187
|
+
3. **Compute.**
|
|
188
188
|
Workers iterate through their assigned Virtual Sequences. Data is streamed from disk, processed by torchmetrics, and reduced globally.
|
|
189
189
|
|
|
190
|
-
See [
|
|
190
|
+
See [OFFLINE_EVALUATION.md](OFFLINE_EVALUATION.md) for detailed usage and design principles.
|
|
191
191
|
|
|
192
192
|
---
|
|
193
193
|
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "evaluators"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.2"
|
|
8
8
|
description = "Various scene understanding and perception evaluation metrics."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13, <3.14"
|
|
@@ -31,14 +31,17 @@ dependencies = [
|
|
|
31
31
|
"numpy>=2.0.0",
|
|
32
32
|
"matplotlib>=3.9.0",
|
|
33
33
|
"rich>=13.9.4",
|
|
34
|
-
"lmdb",
|
|
35
|
-
"tifffile",
|
|
36
34
|
"pydantic",
|
|
37
35
|
"opencv-python>=4.13.0.92",
|
|
36
|
+
"omegaconf>=2.3.0",
|
|
37
|
+
"hydra-zen>=0.16.0",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
40
|
[project.optional-dependencies]
|
|
41
41
|
lightning = ["lightning>=2.6.1"]
|
|
42
|
+
lmdb = ["lmdb>=2.2.0"]
|
|
43
|
+
blosc2 = ["blosc2>=4.1.2"]
|
|
44
|
+
tiff = ["imagecodecs>=2026.3.6", "tifffile>=2026.3.3"]
|
|
42
45
|
|
|
43
46
|
[dependency-groups]
|
|
44
47
|
dev = [
|
|
@@ -97,15 +100,23 @@ select = [
|
|
|
97
100
|
"UP", # pyupgrade
|
|
98
101
|
]
|
|
99
102
|
ignore = [
|
|
103
|
+
"SIM118", # usage of 'x in dict.keys()' -- conflicts with `tensordict`.
|
|
100
104
|
"PLR2004", # magic numbers
|
|
101
105
|
"PLW2901", # for loop variable overwritten
|
|
102
106
|
"D212",
|
|
103
107
|
"D203",
|
|
104
108
|
"D107", # docstring on __init__
|
|
109
|
+
"PT011", # pytest.raises too broad (restrictive)
|
|
110
|
+
|
|
111
|
+
|
|
105
112
|
]
|
|
106
113
|
|
|
107
114
|
[tool.ruff.lint.per-file-ignores]
|
|
108
|
-
"tests/*" = [
|
|
115
|
+
"tests/*" = [
|
|
116
|
+
"D",
|
|
117
|
+
"T",
|
|
118
|
+
"E402", # Module level import not at top of file
|
|
119
|
+
]
|
|
109
120
|
|
|
110
121
|
[tool.ruff.format]
|
|
111
122
|
docstring-code-format = true
|
|
@@ -22,33 +22,39 @@ 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
|
-
EncodedMemmapTemporalWriter,
|
|
26
25
|
LMDBTemporalWriter,
|
|
27
|
-
MemorySchema,
|
|
28
26
|
MemmapTemporalWriter,
|
|
27
|
+
MemorySchema,
|
|
29
28
|
TensorField,
|
|
29
|
+
deserialize_metrics,
|
|
30
|
+
load_evaluation_config,
|
|
31
|
+
save_evaluation_config,
|
|
32
|
+
serialize_metrics,
|
|
30
33
|
)
|
|
31
34
|
|
|
32
35
|
if TYPE_CHECKING:
|
|
33
36
|
__version__: str
|
|
34
37
|
__all__ = [
|
|
35
38
|
"BackendInterface",
|
|
36
|
-
"EncodedMemmapTemporalWriter",
|
|
37
39
|
"LMDBTemporalWriter",
|
|
38
|
-
"MemorySchema",
|
|
39
40
|
"MemmapTemporalWriter",
|
|
41
|
+
"MemorySchema",
|
|
40
42
|
"MetricStream",
|
|
41
43
|
"TensorField",
|
|
42
44
|
"UnboundStreamWarning",
|
|
43
45
|
"__version__",
|
|
46
|
+
"deserialize_metrics",
|
|
47
|
+
"load_evaluation_config",
|
|
44
48
|
"run_offline_evaluation",
|
|
49
|
+
"save_evaluation_config",
|
|
50
|
+
"serialize_metrics",
|
|
45
51
|
]
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
# 2. Runtime Lazy Loader
|
|
49
55
|
def __getattr__(name: str) -> str:
|
|
50
56
|
if name == "__version__":
|
|
51
|
-
from importlib.metadata import PackageNotFoundError, version
|
|
57
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
52
58
|
|
|
53
59
|
try:
|
|
54
60
|
return version("evaluators")
|
|
@@ -18,7 +18,7 @@ from pathlib import Path
|
|
|
18
18
|
import rich.progress
|
|
19
19
|
import torch
|
|
20
20
|
|
|
21
|
-
from evaluators.memory import
|
|
21
|
+
from evaluators.memory.backends._registry import open_virtual_sequence
|
|
22
22
|
from evaluators.scheduling import CatalogBuilder, WorkPartitioner
|
|
23
23
|
from evaluators.types import BaseMetric, MetricResult
|
|
24
24
|
from evaluators.utils import comms
|
|
@@ -192,7 +192,7 @@ def _evaluate_metrics(
|
|
|
192
192
|
|
|
193
193
|
for i, item in enumerate(work_items):
|
|
194
194
|
fragments = item.entry["fragments"]
|
|
195
|
-
sequence =
|
|
195
|
+
sequence = open_virtual_sequence(artifact_path, item.sequence_id, fragments)
|
|
196
196
|
|
|
197
197
|
if show_progress:
|
|
198
198
|
with rich.progress.Progress() as progress:
|
|
@@ -265,7 +265,7 @@ def _save_plots(
|
|
|
265
265
|
force: bool,
|
|
266
266
|
) -> None:
|
|
267
267
|
"""Call ``plot()`` on each metric and save the figure to *out_path*."""
|
|
268
|
-
import matplotlib.pyplot as plt
|
|
268
|
+
import matplotlib.pyplot as plt
|
|
269
269
|
|
|
270
270
|
for metric in metrics:
|
|
271
271
|
name = metric.__class__.__name__
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI Module: Evaluate.
|
|
3
|
+
|
|
4
|
+
Runs offline evaluation on stored inference results using the config saved in
|
|
5
|
+
the artifact directory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import logging
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, cast
|
|
15
|
+
|
|
16
|
+
import rich.console
|
|
17
|
+
import rich.pretty
|
|
18
|
+
import torch
|
|
19
|
+
from evaluators import run_offline_evaluation
|
|
20
|
+
from evaluators.memory import deserialize_metrics, load_evaluation_config
|
|
21
|
+
from omegaconf import DictConfig, ListConfig
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def setup_parser() -> argparse.ArgumentParser:
|
|
27
|
+
"""Define arguments for the compute subcommand."""
|
|
28
|
+
parser = argparse.ArgumentParser(
|
|
29
|
+
prog="evaluators compute",
|
|
30
|
+
description="Run offline evaluation on stored inference results.",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"artifact_dir",
|
|
35
|
+
type=str,
|
|
36
|
+
help="Path to directory containing stored inference results.",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
"--output-dir",
|
|
41
|
+
"-o",
|
|
42
|
+
type=str,
|
|
43
|
+
default=None,
|
|
44
|
+
help="Directory to save metric results and plots.",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--plot",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="Generate and save visualization plots.",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--force",
|
|
55
|
+
action="store_true",
|
|
56
|
+
help="Force recomputation of cached metrics.",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
parser.add_argument(
|
|
60
|
+
"--device",
|
|
61
|
+
type=str,
|
|
62
|
+
default="cuda" if torch.cuda.is_available() else "cpu",
|
|
63
|
+
help="Device for computation (default: cuda if available).",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--quiet",
|
|
68
|
+
"-q",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Suppress progress bars.",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return parser
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _load_from_config_file(config_path: str | Path) -> list[Any]:
|
|
77
|
+
"""Load metrics from serialized YAML metric definitions in config file."""
|
|
78
|
+
config = load_evaluation_config(config_path)
|
|
79
|
+
|
|
80
|
+
raw_specs = config.get("metrics", [])
|
|
81
|
+
if not raw_specs:
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"Saved config has no metric definitions under 'metrics'. "
|
|
84
|
+
"Offline evaluation requires serialized metrics written at inference time."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if not isinstance(raw_specs, (list, ListConfig)):
|
|
88
|
+
raise ValueError("Saved config field 'metrics' must be a list.")
|
|
89
|
+
|
|
90
|
+
raw_spec_items = cast(list[Any], raw_specs)
|
|
91
|
+
metric_specs: list[DictConfig] = []
|
|
92
|
+
for spec in raw_spec_items:
|
|
93
|
+
if not isinstance(spec, DictConfig):
|
|
94
|
+
raise ValueError("Saved metric definitions must be DictConfig mappings.")
|
|
95
|
+
metric_specs.append(spec)
|
|
96
|
+
return deserialize_metrics(metric_specs)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _find_saved_config(artifact_dir: Path) -> str | None:
|
|
100
|
+
"""Check if artifact directory has a saved evaluation config."""
|
|
101
|
+
for name in ["evaluation_config.json", "evaluation_config.yaml"]:
|
|
102
|
+
if (artifact_dir / name).exists():
|
|
103
|
+
return str(artifact_dir / name)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _load_evaluation_metrics(artifact_path: Path) -> list[Any]:
|
|
108
|
+
"""Load evaluation metrics from config saved in artifact directory."""
|
|
109
|
+
saved_config = _find_saved_config(artifact_path)
|
|
110
|
+
if not saved_config:
|
|
111
|
+
raise FileNotFoundError(
|
|
112
|
+
"No saved evaluation config found in artifact directory. "
|
|
113
|
+
"Expected evaluation_config.json or evaluation_config.yaml."
|
|
114
|
+
)
|
|
115
|
+
return _load_from_config_file(saved_config)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def handle_evaluate(args: Any) -> None:
|
|
119
|
+
"""Execute the evaluate subcommand."""
|
|
120
|
+
console = rich.console.Console()
|
|
121
|
+
artifact_path = Path(args.artifact_dir)
|
|
122
|
+
|
|
123
|
+
if not artifact_path.exists():
|
|
124
|
+
console.print(f"[red]Error: {artifact_path} not found[/red]")
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
|
|
127
|
+
# Load metrics
|
|
128
|
+
metrics = _load_evaluation_metrics(artifact_path)
|
|
129
|
+
if not metrics:
|
|
130
|
+
console.print("[yellow]No metrics found in saved evaluation config.[/yellow]")
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
|
|
133
|
+
console.print(f"[bold]Loaded {len(metrics)} metric(s):[/bold]")
|
|
134
|
+
for metric in metrics:
|
|
135
|
+
metric_str = "\t" + "\n\t".join(str(metric).splitlines())
|
|
136
|
+
console.print(metric_str)
|
|
137
|
+
|
|
138
|
+
# Run evaluation
|
|
139
|
+
device = torch.device(args.device)
|
|
140
|
+
console.print(f"[bold]Starting evaluation on {device!s}...[/bold]")
|
|
141
|
+
|
|
142
|
+
results = run_offline_evaluation(
|
|
143
|
+
metrics,
|
|
144
|
+
artifact_path,
|
|
145
|
+
output_dir=args.output_dir,
|
|
146
|
+
plot=args.plot,
|
|
147
|
+
force=args.force,
|
|
148
|
+
device=device,
|
|
149
|
+
show_progress=not args.quiet,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if results:
|
|
153
|
+
console.print("\n[bold green]Evaluation Complete[/bold green]")
|
|
154
|
+
for metric_name, values in results.items():
|
|
155
|
+
console.print(f"\n{metric_name}:")
|
|
156
|
+
for key, val in values.items():
|
|
157
|
+
console.print(f" {key:30s}: {val:.4f}")
|
|
158
|
+
|
|
159
|
+
if args.output_dir:
|
|
160
|
+
console.print(f"\n[bold]Results: {args.output_dir}[/bold]")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def main(args: list[str]) -> None:
|
|
164
|
+
"""Entry point for evaluate command."""
|
|
165
|
+
parser = setup_parser()
|
|
166
|
+
parsed_args = parser.parse_args(args)
|
|
167
|
+
handle_evaluate(parsed_args)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CLI module for third-party benchmark export.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from evaluators.third_party.adapters.dvpq import VipDeeplabKeyMap, export_vip_deeplab
|
|
11
|
+
from evaluators.third_party.adapters.pq import export_pq
|
|
12
|
+
|
|
13
|
+
BENCHMARK_CHOICES: tuple[str, ...] = (
|
|
14
|
+
"pq",
|
|
15
|
+
"cityscapes_dvpq",
|
|
16
|
+
"semkitti_dvpq",
|
|
17
|
+
"semkitti_dstq",
|
|
18
|
+
)
|
|
19
|
+
VIP_DEEPLAB_BENCHMARKS: tuple[str, ...] = (
|
|
20
|
+
"cityscapes_dvpq",
|
|
21
|
+
"semkitti_dvpq",
|
|
22
|
+
"semkitti_dstq",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def setup_parser(prog: str = "evaluators third_party") -> argparse.ArgumentParser:
|
|
27
|
+
"""Build argument parser for third-party benchmark export CLI."""
|
|
28
|
+
parser = argparse.ArgumentParser(
|
|
29
|
+
prog=prog,
|
|
30
|
+
description=(
|
|
31
|
+
"Export offline memory artifacts for third-party benchmark scripts."
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
subparsers = parser.add_subparsers(dest="subcommand", required=True)
|
|
35
|
+
|
|
36
|
+
export_parser = subparsers.add_parser(
|
|
37
|
+
"export",
|
|
38
|
+
help="Export artifacts for a third-party benchmark script.",
|
|
39
|
+
)
|
|
40
|
+
export_parser.add_argument(
|
|
41
|
+
"--memory",
|
|
42
|
+
type=str,
|
|
43
|
+
required=True,
|
|
44
|
+
help="Path to the offline memory artifact directory.",
|
|
45
|
+
)
|
|
46
|
+
export_parser.add_argument(
|
|
47
|
+
"--benchmark",
|
|
48
|
+
type=str,
|
|
49
|
+
required=True,
|
|
50
|
+
choices=BENCHMARK_CHOICES,
|
|
51
|
+
help="Benchmark adapter target.",
|
|
52
|
+
)
|
|
53
|
+
export_parser.add_argument(
|
|
54
|
+
"--output",
|
|
55
|
+
type=str,
|
|
56
|
+
required=True,
|
|
57
|
+
help="Output directory for exported files.",
|
|
58
|
+
)
|
|
59
|
+
export_parser.add_argument(
|
|
60
|
+
"--pred-panoptic-key",
|
|
61
|
+
type=str,
|
|
62
|
+
required=True,
|
|
63
|
+
help="Key name for predicted panoptic tensor in memory.",
|
|
64
|
+
)
|
|
65
|
+
export_parser.add_argument(
|
|
66
|
+
"--frame-index-key",
|
|
67
|
+
type=str,
|
|
68
|
+
required=True,
|
|
69
|
+
help="Key name for frame index tensor in memory.",
|
|
70
|
+
)
|
|
71
|
+
export_parser.add_argument(
|
|
72
|
+
"--target-panoptic-key",
|
|
73
|
+
type=str,
|
|
74
|
+
help="Key name for target panoptic tensor in memory (required for pq).",
|
|
75
|
+
)
|
|
76
|
+
export_parser.add_argument(
|
|
77
|
+
"--pred-depth-key",
|
|
78
|
+
type=str,
|
|
79
|
+
help="Key name for predicted depth tensor in memory (required for DVPQ/DSTQ).",
|
|
80
|
+
)
|
|
81
|
+
export_parser.add_argument(
|
|
82
|
+
"--panoptic-divisor",
|
|
83
|
+
type=int,
|
|
84
|
+
required=True,
|
|
85
|
+
help="Divisor used to encode panoptic IDs as category*divisor + instance.",
|
|
86
|
+
)
|
|
87
|
+
export_parser.add_argument(
|
|
88
|
+
"--thing-category-ids",
|
|
89
|
+
type=str,
|
|
90
|
+
required=True,
|
|
91
|
+
help="Comma-separated thing category IDs used to set COCO isthing flags.",
|
|
92
|
+
)
|
|
93
|
+
return parser
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _require_arg(value: str | None, flag: str, benchmark: str) -> str:
|
|
97
|
+
if value:
|
|
98
|
+
return value
|
|
99
|
+
raise ValueError(f"{flag} is required for benchmark '{benchmark}'.")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _parse_thing_category_ids(value: str) -> set[int]:
|
|
103
|
+
raw_parts = [part.strip() for part in value.split(",")]
|
|
104
|
+
if any(part == "" for part in raw_parts):
|
|
105
|
+
raise ValueError("--thing-category-ids must be a comma-separated list of ints.")
|
|
106
|
+
ids: set[int] = set()
|
|
107
|
+
for part in raw_parts:
|
|
108
|
+
try:
|
|
109
|
+
category_id = int(part)
|
|
110
|
+
except ValueError as exc:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"Invalid thing category id '{part}'. Expected integer values."
|
|
113
|
+
) from exc
|
|
114
|
+
if category_id < 0:
|
|
115
|
+
raise ValueError("Thing category ids must be non-negative integers.")
|
|
116
|
+
ids.add(category_id)
|
|
117
|
+
return ids
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def handle_export(args: argparse.Namespace) -> None:
|
|
121
|
+
memory = Path(args.memory)
|
|
122
|
+
output = Path(args.output)
|
|
123
|
+
thing_category_ids = _parse_thing_category_ids(args.thing_category_ids)
|
|
124
|
+
|
|
125
|
+
if args.benchmark == "pq":
|
|
126
|
+
key_map = {
|
|
127
|
+
"pred_panoptic": args.pred_panoptic_key,
|
|
128
|
+
"target_panoptic": _require_arg(
|
|
129
|
+
args.target_panoptic_key, "--target-panoptic-key", args.benchmark
|
|
130
|
+
),
|
|
131
|
+
"frame_index": args.frame_index_key,
|
|
132
|
+
}
|
|
133
|
+
summary = export_pq(
|
|
134
|
+
memory=memory,
|
|
135
|
+
output=output,
|
|
136
|
+
key_map=key_map,
|
|
137
|
+
panoptic_divisor=args.panoptic_divisor,
|
|
138
|
+
thing_category_ids=thing_category_ids,
|
|
139
|
+
)
|
|
140
|
+
print(
|
|
141
|
+
f"Export complete for '{args.benchmark}'. "
|
|
142
|
+
f"images={summary['images']}, "
|
|
143
|
+
f"predictions={summary['predictions']}, "
|
|
144
|
+
f"targets={summary['targets']}."
|
|
145
|
+
)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
vip_key_map: VipDeeplabKeyMap = {
|
|
149
|
+
"pred_panoptic": args.pred_panoptic_key,
|
|
150
|
+
"frame_index": args.frame_index_key,
|
|
151
|
+
"pred_depth": _require_arg(
|
|
152
|
+
args.pred_depth_key, "--pred-depth-key", args.benchmark
|
|
153
|
+
),
|
|
154
|
+
}
|
|
155
|
+
if args.benchmark not in VIP_DEEPLAB_BENCHMARKS:
|
|
156
|
+
raise ValueError(f"Unsupported ViP-DeepLab benchmark: {args.benchmark}")
|
|
157
|
+
summary = export_vip_deeplab(
|
|
158
|
+
memory=memory,
|
|
159
|
+
output=output,
|
|
160
|
+
key_map=vip_key_map,
|
|
161
|
+
format_name=args.benchmark,
|
|
162
|
+
panoptic_divisor=args.panoptic_divisor,
|
|
163
|
+
)
|
|
164
|
+
print(
|
|
165
|
+
f"Export complete for '{args.benchmark}'. "
|
|
166
|
+
f"images={summary['images']}, "
|
|
167
|
+
f"panoptic_files={summary['panoptic_files']}, "
|
|
168
|
+
f"depth_files={summary['depth_files']}."
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def main(args: list[str] | None = None) -> None:
|
|
173
|
+
"""Entry point for third_party command."""
|
|
174
|
+
parser = setup_parser()
|
|
175
|
+
parsed_args = parser.parse_args(args)
|
|
176
|
+
if parsed_args.subcommand == "export":
|
|
177
|
+
handle_export(parsed_args)
|
|
@@ -12,12 +12,15 @@ Exports
|
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
14
|
import shutil
|
|
15
|
+
from collections.abc import Sequence
|
|
15
16
|
from pathlib import Path
|
|
17
|
+
from typing import Protocol
|
|
16
18
|
|
|
17
19
|
from evaluators import MetricStream, run_offline_evaluation
|
|
18
20
|
from evaluators.memory import (
|
|
19
21
|
MemmapTemporalWriter,
|
|
20
22
|
TemporalWriterProtocol,
|
|
23
|
+
save_evaluation_config,
|
|
21
24
|
)
|
|
22
25
|
from evaluators.utils import comms
|
|
23
26
|
from lightning import Callback, LightningModule, Trainer
|
|
@@ -27,6 +30,44 @@ __all__ = ["DistributedEvaluationCallback", "DistributedInferenceWriter"]
|
|
|
27
30
|
logger = logging.getLogger(__name__)
|
|
28
31
|
|
|
29
32
|
|
|
33
|
+
class PredictEvaluationCallable(Protocol):
|
|
34
|
+
"""Signature for post-prediction evaluation callables."""
|
|
35
|
+
|
|
36
|
+
def __call__(
|
|
37
|
+
self,
|
|
38
|
+
streams: Sequence[MetricStream],
|
|
39
|
+
artifact_root: str | Path,
|
|
40
|
+
*,
|
|
41
|
+
trainer: Trainer | None = None,
|
|
42
|
+
pl_module: LightningModule | None = None,
|
|
43
|
+
) -> dict[str, dict[str, dict[str, float]]] | None:
|
|
44
|
+
"""Run evaluation for persisted artifacts."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _default_evaluate_streams(
|
|
49
|
+
streams: Sequence[MetricStream],
|
|
50
|
+
artifact_root: str | Path,
|
|
51
|
+
*,
|
|
52
|
+
trainer: Trainer | None = None,
|
|
53
|
+
pl_module: LightningModule | None = None,
|
|
54
|
+
) -> dict[str, dict[str, dict[str, float]]]:
|
|
55
|
+
"""Default post-predict evaluator using offline evaluation."""
|
|
56
|
+
del trainer, pl_module
|
|
57
|
+
artifact_root = Path(artifact_root)
|
|
58
|
+
all_results: dict[str, dict[str, dict[str, float]]] = {}
|
|
59
|
+
|
|
60
|
+
for stream in streams:
|
|
61
|
+
stream_results = run_offline_evaluation(
|
|
62
|
+
metrics=stream.metrics,
|
|
63
|
+
artifact_dir=artifact_root / stream.name,
|
|
64
|
+
show_progress=False,
|
|
65
|
+
)
|
|
66
|
+
all_results[stream.name] = stream_results
|
|
67
|
+
|
|
68
|
+
return all_results
|
|
69
|
+
|
|
70
|
+
|
|
30
71
|
class _DistributedWriterBase(Callback):
|
|
31
72
|
"""
|
|
32
73
|
Base class for callbacks that need to bind MetricStreams to Disk Writers.
|
|
@@ -83,6 +124,11 @@ class _DistributedWriterBase(Callback):
|
|
|
83
124
|
**self.backend_kwargs,
|
|
84
125
|
)
|
|
85
126
|
stream.bind(writer)
|
|
127
|
+
save_evaluation_config(
|
|
128
|
+
output_dir=stream_dir,
|
|
129
|
+
metrics=stream.metrics,
|
|
130
|
+
metadata={"stream_name": stream.name},
|
|
131
|
+
)
|
|
86
132
|
self._active_streams[stream.name] = (stream, writer)
|
|
87
133
|
|
|
88
134
|
def _close_writers(self) -> None:
|
|
@@ -186,11 +232,34 @@ class DistributedInferenceWriter(_DistributedWriterBase):
|
|
|
186
232
|
Does NOT run any metrics. Useful for generating offline artifacts.
|
|
187
233
|
"""
|
|
188
234
|
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
output_dir: str | Path,
|
|
238
|
+
*,
|
|
239
|
+
backend: type[TemporalWriterProtocol] = MemmapTemporalWriter,
|
|
240
|
+
evaluate: PredictEvaluationCallable | None = _default_evaluate_streams,
|
|
241
|
+
**backend_kwargs,
|
|
242
|
+
) -> None:
|
|
243
|
+
super().__init__(
|
|
244
|
+
output_dir=output_dir,
|
|
245
|
+
backend=backend,
|
|
246
|
+
**backend_kwargs,
|
|
247
|
+
)
|
|
248
|
+
self.evaluate = evaluate
|
|
249
|
+
|
|
189
250
|
def on_predict_start(self, trainer: Trainer, pl_module: LightningModule) -> None:
|
|
190
251
|
self._setup_streams(pl_module)
|
|
191
252
|
|
|
192
|
-
def
|
|
193
|
-
self, trainer: Trainer, pl_module: LightningModule
|
|
194
|
-
) -> None:
|
|
253
|
+
def on_predict_end(self, trainer: Trainer, pl_module: LightningModule) -> None:
|
|
195
254
|
self._close_writers()
|
|
255
|
+
|
|
256
|
+
if self.evaluate is not None:
|
|
257
|
+
streams = [stream for stream, _ in self._active_streams.values()]
|
|
258
|
+
self.evaluate(
|
|
259
|
+
streams,
|
|
260
|
+
artifact_root=self.output_dir,
|
|
261
|
+
trainer=trainer,
|
|
262
|
+
pl_module=pl_module,
|
|
263
|
+
)
|
|
264
|
+
|
|
196
265
|
self._active_streams = {}
|