euler-eval 2.10.0__tar.gz → 2.12.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.
- {euler_eval-2.10.0 → euler_eval-2.12.0}/PKG-INFO +9 -5
- {euler_eval-2.10.0 → euler_eval-2.12.0}/README.md +8 -4
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/cli.py +74 -40
- euler_eval-2.12.0/euler_eval/config_paths.py +127 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/data.py +15 -3
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/evaluate.py +33 -2
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval.egg-info/PKG-INFO +9 -5
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval.egg-info/SOURCES.txt +1 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/pyproject.toml +1 -1
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_config.py +48 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_data_builders.py +34 -2
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_save_results.py +27 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_sparse_depth.py +60 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/__init__.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/__init__.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/absrel.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/daniel_error.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/depth_binned_error.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/depth_edge_f1.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/depth_standard.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/fid_kid.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/gpu_depth_batch.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/gpu_image_batch.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/high_freq_energy.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/lpips_metric.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/normal_consistency.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/psnr.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/rgb_edge_f1.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/rgb_lpips.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/rgb_psnr_ssim.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/rho_a.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/rmse.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/scale_invariant_log.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/ssim.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/tail_errors.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/metrics/utils.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/sanity_checker.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval/utils/hierarchy_parser.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval.egg-info/dependency_links.txt +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval.egg-info/entry_points.txt +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval.egg-info/requires.txt +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/euler_eval.egg-info/top_level.txt +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/init_cache.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/setup.cfg +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_alignment.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_cli_device.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_data.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_depth_alignment_output.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_depth_standard.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_evaluate_helpers.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_init_cache.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_integration.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_meta_output.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_metrics_utils.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_rgb_fid_output.py +0 -0
- {euler_eval-2.10.0 → euler_eval-2.12.0}/tests/test_rho_a.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-eval
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.0
|
|
4
4
|
Summary: Depth map evaluation toolkit with comprehensive metrics
|
|
5
5
|
Author: Depth Eval Contributors
|
|
6
6
|
License: MIT
|
|
@@ -241,7 +241,7 @@ Defines GT modalities, prediction datasets to evaluate, and optional euler_train
|
|
|
241
241
|
|
|
242
242
|
Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`). euler-loading inline selectors are also accepted in `path`, such as `/data/muses.zip:test` or `/data/muses.zip:test#scope=rgb`.
|
|
243
243
|
|
|
244
|
-
For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction
|
|
244
|
+
For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction uses a dense depth-like map under `datasets[].depth`, `datasets[].relative_depth`, or `datasets[].affine_depth`. The evaluator projects the sparse GT point cloud into the prediction image plane using `gt.intrinsics` and `gt.camera_extrinsics`, then computes pointwise depth metrics only at projected valid pixels.
|
|
245
245
|
|
|
246
246
|
#### GT section
|
|
247
247
|
|
|
@@ -261,17 +261,21 @@ For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.dep
|
|
|
261
261
|
|
|
262
262
|
#### Prediction datasets
|
|
263
263
|
|
|
264
|
-
Each entry in `datasets` can include `rgb`, `depth`, `rays`, or any combination:
|
|
264
|
+
Each entry in `datasets` can include `rgb`, one dense depth-like prediction (`depth`, `relative_depth`, or `affine_depth`), `rays`, or any combination:
|
|
265
265
|
|
|
266
266
|
| Field | Required | Description |
|
|
267
267
|
|---|---|---|
|
|
268
268
|
| `name` | yes | Display name for this prediction dataset |
|
|
269
269
|
| `rgb.path` | no\* | Path to predicted RGB dataset |
|
|
270
|
-
| `depth.path` | no\* | Path to predicted dense depth dataset; also used when evaluating against sparse pointcloud GT |
|
|
270
|
+
| `depth.path` | no\* | Path to predicted dense metric depth dataset; also used when evaluating against sparse pointcloud GT |
|
|
271
|
+
| `relative_depth.path` | no\* | Path to predicted dense relative depth dataset; evaluated through the same depth pipeline with scale/shift alignment support |
|
|
272
|
+
| `affine_depth.path` | no\* | Path to predicted dense affine-depth dataset; evaluated through the same depth pipeline with scale/shift alignment support |
|
|
271
273
|
| `rays.path` | no\* | Path to predicted ray direction map dataset |
|
|
272
274
|
| `output_file` | no | Custom output path for results JSON (default: `eval.json` inside the first available modality path) |
|
|
273
275
|
|
|
274
|
-
\* At least one of `rgb.path`, `depth.path`, or `rays.path` is required.
|
|
276
|
+
\* At least one of `rgb.path`, `depth.path`, `relative_depth.path`, `affine_depth.path`, or `rays.path` is required. Use only one dense depth-like entry per prediction dataset.
|
|
277
|
+
|
|
278
|
+
When `relative_depth` or `affine_depth` is used, `--depth-alignment auto_affine` treats the entry as declared non-metric depth and runs scale/shift alignment even if the raw value range is not normalized. `--depth-alignment none` still disables calibration.
|
|
275
279
|
|
|
276
280
|
#### `euler_train` section (optional)
|
|
277
281
|
|
|
@@ -203,7 +203,7 @@ Defines GT modalities, prediction datasets to evaluate, and optional euler_train
|
|
|
203
203
|
|
|
204
204
|
Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`). euler-loading inline selectors are also accepted in `path`, such as `/data/muses.zip:test` or `/data/muses.zip:test#scope=rgb`.
|
|
205
205
|
|
|
206
|
-
For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction
|
|
206
|
+
For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction uses a dense depth-like map under `datasets[].depth`, `datasets[].relative_depth`, or `datasets[].affine_depth`. The evaluator projects the sparse GT point cloud into the prediction image plane using `gt.intrinsics` and `gt.camera_extrinsics`, then computes pointwise depth metrics only at projected valid pixels.
|
|
207
207
|
|
|
208
208
|
#### GT section
|
|
209
209
|
|
|
@@ -223,17 +223,21 @@ For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.dep
|
|
|
223
223
|
|
|
224
224
|
#### Prediction datasets
|
|
225
225
|
|
|
226
|
-
Each entry in `datasets` can include `rgb`, `depth`, `rays`, or any combination:
|
|
226
|
+
Each entry in `datasets` can include `rgb`, one dense depth-like prediction (`depth`, `relative_depth`, or `affine_depth`), `rays`, or any combination:
|
|
227
227
|
|
|
228
228
|
| Field | Required | Description |
|
|
229
229
|
|---|---|---|
|
|
230
230
|
| `name` | yes | Display name for this prediction dataset |
|
|
231
231
|
| `rgb.path` | no\* | Path to predicted RGB dataset |
|
|
232
|
-
| `depth.path` | no\* | Path to predicted dense depth dataset; also used when evaluating against sparse pointcloud GT |
|
|
232
|
+
| `depth.path` | no\* | Path to predicted dense metric depth dataset; also used when evaluating against sparse pointcloud GT |
|
|
233
|
+
| `relative_depth.path` | no\* | Path to predicted dense relative depth dataset; evaluated through the same depth pipeline with scale/shift alignment support |
|
|
234
|
+
| `affine_depth.path` | no\* | Path to predicted dense affine-depth dataset; evaluated through the same depth pipeline with scale/shift alignment support |
|
|
233
235
|
| `rays.path` | no\* | Path to predicted ray direction map dataset |
|
|
234
236
|
| `output_file` | no | Custom output path for results JSON (default: `eval.json` inside the first available modality path) |
|
|
235
237
|
|
|
236
|
-
\* At least one of `rgb.path`, `depth.path`, or `rays.path` is required.
|
|
238
|
+
\* At least one of `rgb.path`, `depth.path`, `relative_depth.path`, `affine_depth.path`, or `rays.path` is required. Use only one dense depth-like entry per prediction dataset.
|
|
239
|
+
|
|
240
|
+
When `relative_depth` or `affine_depth` is used, `--depth-alignment auto_affine` treats the entry as declared non-metric depth and runs scale/shift alignment even if the raw value range is not normalized. `--depth-alignment none` still disables calibration.
|
|
237
241
|
|
|
238
242
|
#### `euler_train` section (optional)
|
|
239
243
|
|
|
@@ -13,8 +13,8 @@ import zipfile
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
import torch
|
|
16
|
-
from euler_loading import Modality
|
|
17
16
|
|
|
17
|
+
from .config_paths import normalize_modality_path
|
|
18
18
|
from .data import (
|
|
19
19
|
build_depth_eval_dataset,
|
|
20
20
|
build_rays_eval_dataset,
|
|
@@ -41,22 +41,6 @@ except ImportError:
|
|
|
41
41
|
from euler_metric_naming import AxisDeclaration, MetricDescription, MetricNamespace
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def _normalize_modality_path(
|
|
45
|
-
path: str | Path,
|
|
46
|
-
*,
|
|
47
|
-
modality_key: str,
|
|
48
|
-
split: str | None = None,
|
|
49
|
-
) -> Path:
|
|
50
|
-
"""Return the filesystem/archive root for an euler-loading modality path."""
|
|
51
|
-
modality = Modality(
|
|
52
|
-
str(path),
|
|
53
|
-
modality_type=modality_key,
|
|
54
|
-
metadata_scope=modality_key,
|
|
55
|
-
split=split,
|
|
56
|
-
)
|
|
57
|
-
return Path(modality.path)
|
|
58
|
-
|
|
59
|
-
|
|
60
44
|
# ── Eval namespace ──────────────────────────────────────────────────────────
|
|
61
45
|
# MetricNamespace subclass for eval context with custom axis declarations,
|
|
62
46
|
# following the Normed Metric Namespacing convention (§3.6, §4, §5.2).
|
|
@@ -167,6 +151,8 @@ def _rgb_eval_axes(*, benchmark: bool = False) -> dict[str, AxisDeclaration]:
|
|
|
167
151
|
|
|
168
152
|
_RAYS_EVAL_AXES: dict[str, AxisDeclaration] = {}
|
|
169
153
|
|
|
154
|
+
_PRED_DEPTH_KEYS = ("depth", "relative_depth", "affine_depth")
|
|
155
|
+
|
|
170
156
|
# Downstream eval consumers validate metricSet.metricNamespace with a stricter
|
|
171
157
|
# first-segment rule than euler_metric_naming's modality validator. Keep the
|
|
172
158
|
# public Python sparse_depth result keys, but use a wire-compatible metric root
|
|
@@ -578,6 +564,34 @@ def print_device_info(requested_device: str, resolved_device: str) -> None:
|
|
|
578
564
|
pass
|
|
579
565
|
|
|
580
566
|
|
|
567
|
+
def _prediction_depth_entry(
|
|
568
|
+
entry: dict, *, label: str = "Prediction dataset"
|
|
569
|
+
) -> tuple[str | None, dict | None]:
|
|
570
|
+
"""Return the configured dense-depth prediction entry, if any."""
|
|
571
|
+
matches = [
|
|
572
|
+
key
|
|
573
|
+
for key in _PRED_DEPTH_KEYS
|
|
574
|
+
if key in entry and "path" in entry.get(key, {})
|
|
575
|
+
]
|
|
576
|
+
if not matches:
|
|
577
|
+
return None, None
|
|
578
|
+
if len(matches) > 1:
|
|
579
|
+
raise ValueError(
|
|
580
|
+
f"{label} contains multiple dense depth entries "
|
|
581
|
+
f"{matches}. Use only one of {list(_PRED_DEPTH_KEYS)}."
|
|
582
|
+
)
|
|
583
|
+
key = matches[0]
|
|
584
|
+
return key, entry[key]
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _prediction_depth_space_hint(key: str | None) -> str | None:
|
|
588
|
+
if key == "relative_depth":
|
|
589
|
+
return "relative"
|
|
590
|
+
if key == "affine_depth":
|
|
591
|
+
return "affine"
|
|
592
|
+
return None
|
|
593
|
+
|
|
594
|
+
|
|
581
595
|
def validate_gt_config(gt: dict) -> None:
|
|
582
596
|
"""Validate the ``gt`` section of the configuration.
|
|
583
597
|
|
|
@@ -616,9 +630,8 @@ def validate_gt_config(gt: dict) -> None:
|
|
|
616
630
|
"camera_extrinsics",
|
|
617
631
|
):
|
|
618
632
|
if modality in gt and "path" in gt[modality]:
|
|
619
|
-
p =
|
|
633
|
+
p = normalize_modality_path(
|
|
620
634
|
gt[modality]["path"],
|
|
621
|
-
modality_key=modality,
|
|
622
635
|
split=gt[modality].get("split"),
|
|
623
636
|
)
|
|
624
637
|
if not p.exists():
|
|
@@ -636,18 +649,19 @@ def validate_dataset_entry(entry: dict, index: int) -> None:
|
|
|
636
649
|
raise ValueError(f"{label} must have a 'name' field")
|
|
637
650
|
|
|
638
651
|
has_rgb = "rgb" in entry and "path" in entry.get("rgb", {})
|
|
639
|
-
|
|
652
|
+
depth_key, _ = _prediction_depth_entry(entry, label=label)
|
|
653
|
+
has_depth = depth_key is not None
|
|
640
654
|
has_rays = "rays" in entry and "path" in entry.get("rays", {})
|
|
641
655
|
if not has_rgb and not has_depth and not has_rays:
|
|
642
656
|
raise ValueError(
|
|
643
|
-
f"{label} must have at least 'rgb.path', 'depth.path',
|
|
657
|
+
f"{label} must have at least 'rgb.path', 'depth.path', "
|
|
658
|
+
"'relative_depth.path', 'affine_depth.path', or 'rays.path'"
|
|
644
659
|
)
|
|
645
660
|
|
|
646
|
-
for modality in ("rgb",
|
|
661
|
+
for modality in ("rgb", *_PRED_DEPTH_KEYS, "rays"):
|
|
647
662
|
if modality in entry and "path" in entry[modality]:
|
|
648
|
-
p =
|
|
663
|
+
p = normalize_modality_path(
|
|
649
664
|
entry[modality]["path"],
|
|
650
|
-
modality_key=modality,
|
|
651
665
|
split=entry[modality].get("split"),
|
|
652
666
|
)
|
|
653
667
|
if not p.exists():
|
|
@@ -769,27 +783,28 @@ def save_results(
|
|
|
769
783
|
"""
|
|
770
784
|
output_file = dataset_config.get("output_file")
|
|
771
785
|
if output_file is None:
|
|
786
|
+
selected_modality = modality
|
|
787
|
+
if selected_modality == "depth" and "depth" not in dataset_config:
|
|
788
|
+
selected_modality, _ = _prediction_depth_entry(dataset_config)
|
|
772
789
|
if (
|
|
773
|
-
|
|
774
|
-
and
|
|
775
|
-
and "path" in dataset_config[
|
|
790
|
+
selected_modality is not None
|
|
791
|
+
and selected_modality in dataset_config
|
|
792
|
+
and "path" in dataset_config[selected_modality]
|
|
776
793
|
):
|
|
777
794
|
output_file = (
|
|
778
|
-
|
|
779
|
-
dataset_config[
|
|
780
|
-
|
|
781
|
-
split=dataset_config[modality].get("split"),
|
|
795
|
+
normalize_modality_path(
|
|
796
|
+
dataset_config[selected_modality]["path"],
|
|
797
|
+
split=dataset_config[selected_modality].get("split"),
|
|
782
798
|
)
|
|
783
799
|
/ "eval.json"
|
|
784
800
|
)
|
|
785
801
|
else:
|
|
786
802
|
# Default: save alongside first available modality path
|
|
787
|
-
for mod in (
|
|
803
|
+
for mod in (*_PRED_DEPTH_KEYS, "rgb", "rays"):
|
|
788
804
|
if mod in dataset_config and "path" in dataset_config[mod]:
|
|
789
805
|
output_file = (
|
|
790
|
-
|
|
806
|
+
normalize_modality_path(
|
|
791
807
|
dataset_config[mod]["path"],
|
|
792
|
-
modality_key=mod,
|
|
793
808
|
split=dataset_config[mod].get("split"),
|
|
794
809
|
)
|
|
795
810
|
/ "eval.json"
|
|
@@ -1008,7 +1023,8 @@ def main():
|
|
|
1008
1023
|
# Evaluate each prediction dataset
|
|
1009
1024
|
for dataset_config in config["datasets"]:
|
|
1010
1025
|
ds_name = dataset_config["name"]
|
|
1011
|
-
|
|
1026
|
+
pred_depth_key, pred_depth_config = _prediction_depth_entry(dataset_config)
|
|
1027
|
+
has_depth = pred_depth_config is not None
|
|
1012
1028
|
has_rgb = "rgb" in dataset_config and "path" in dataset_config["rgb"]
|
|
1013
1029
|
has_rays = "rays" in dataset_config and "path" in dataset_config["rays"]
|
|
1014
1030
|
|
|
@@ -1031,17 +1047,22 @@ def main():
|
|
|
1031
1047
|
and not gt_sparse_depth_path
|
|
1032
1048
|
and not args.skip_depth
|
|
1033
1049
|
):
|
|
1034
|
-
pred_depth_path =
|
|
1035
|
-
pred_depth_split =
|
|
1050
|
+
pred_depth_path = pred_depth_config["path"]
|
|
1051
|
+
pred_depth_split = pred_depth_config.get("split")
|
|
1036
1052
|
print(f"\n[DEPTH] Evaluating: '{ds_name}'")
|
|
1037
1053
|
print(f" GT: {gt_depth_path}")
|
|
1038
1054
|
print(f" Pred: {pred_depth_path}")
|
|
1055
|
+
if pred_depth_key != "depth":
|
|
1056
|
+
print(f" Pred depth entry: {pred_depth_key}")
|
|
1039
1057
|
|
|
1040
1058
|
depth_dataset = build_depth_eval_dataset(
|
|
1041
1059
|
gt_depth_path=gt_depth_path,
|
|
1042
1060
|
pred_depth_path=pred_depth_path,
|
|
1043
1061
|
calibration_path=calibration_path,
|
|
1044
1062
|
segmentation_path=segmentation_path,
|
|
1063
|
+
pred_depth_metadata_scope=(
|
|
1064
|
+
pred_depth_key if pred_depth_key != "depth" else None
|
|
1065
|
+
),
|
|
1045
1066
|
gt_depth_split=gt_depth_split,
|
|
1046
1067
|
pred_depth_split=pred_depth_split,
|
|
1047
1068
|
calibration_split=calibration_split,
|
|
@@ -1070,6 +1091,7 @@ def main():
|
|
|
1070
1091
|
if args.benchmark_depth_range
|
|
1071
1092
|
else None
|
|
1072
1093
|
),
|
|
1094
|
+
input_space_hint=_prediction_depth_space_hint(pred_depth_key),
|
|
1073
1095
|
)
|
|
1074
1096
|
|
|
1075
1097
|
if sanity_checker is not None:
|
|
@@ -1121,6 +1143,7 @@ def main():
|
|
|
1121
1143
|
"pred": {
|
|
1122
1144
|
"path": pred_depth_path,
|
|
1123
1145
|
"split": pred_depth_split,
|
|
1146
|
+
"entry": pred_depth_key,
|
|
1124
1147
|
"dimensions": depth_spatial.get("pred_dimensions"),
|
|
1125
1148
|
},
|
|
1126
1149
|
"spatial_alignment": {
|
|
@@ -1210,11 +1233,13 @@ def main():
|
|
|
1210
1233
|
# -- Sparse pointcloud depth evaluation --
|
|
1211
1234
|
sparse_depth_dataset = None
|
|
1212
1235
|
if has_depth and gt_sparse_depth_path and not args.skip_depth:
|
|
1213
|
-
pred_depth_path =
|
|
1214
|
-
pred_depth_split =
|
|
1236
|
+
pred_depth_path = pred_depth_config["path"]
|
|
1237
|
+
pred_depth_split = pred_depth_config.get("split")
|
|
1215
1238
|
print(f"\n[SPARSE_DEPTH] Evaluating: '{ds_name}'")
|
|
1216
1239
|
print(f" GT sparse pointcloud: {gt_sparse_depth_path}")
|
|
1217
1240
|
print(f" Pred dense depth: {pred_depth_path}")
|
|
1241
|
+
if pred_depth_key != "depth":
|
|
1242
|
+
print(f" Pred depth entry: {pred_depth_key}")
|
|
1218
1243
|
print(f" Intrinsics: {intrinsics_path}")
|
|
1219
1244
|
print(f" Camera extrinsics: {camera_extrinsics_path}")
|
|
1220
1245
|
|
|
@@ -1224,6 +1249,9 @@ def main():
|
|
|
1224
1249
|
intrinsics_path=intrinsics_path,
|
|
1225
1250
|
camera_extrinsics_path=camera_extrinsics_path,
|
|
1226
1251
|
segmentation_path=segmentation_path,
|
|
1252
|
+
pred_depth_metadata_scope=(
|
|
1253
|
+
pred_depth_key if pred_depth_key != "depth" else None
|
|
1254
|
+
),
|
|
1227
1255
|
gt_sparse_depth_split=gt_sparse_depth_split,
|
|
1228
1256
|
pred_depth_split=pred_depth_split,
|
|
1229
1257
|
intrinsics_split=intrinsics_split,
|
|
@@ -1251,6 +1279,7 @@ def main():
|
|
|
1251
1279
|
if args.benchmark_depth_range
|
|
1252
1280
|
else None
|
|
1253
1281
|
),
|
|
1282
|
+
input_space_hint=_prediction_depth_space_hint(pred_depth_key),
|
|
1254
1283
|
)
|
|
1255
1284
|
|
|
1256
1285
|
if sanity_checker is not None:
|
|
@@ -1297,6 +1326,7 @@ def main():
|
|
|
1297
1326
|
"pred": {
|
|
1298
1327
|
"path": pred_depth_path,
|
|
1299
1328
|
"split": pred_depth_split,
|
|
1329
|
+
"entry": pred_depth_key,
|
|
1300
1330
|
"dimensions": sparse_depth_spatial.get("pred_dimensions"),
|
|
1301
1331
|
},
|
|
1302
1332
|
"calibration": {
|
|
@@ -1652,7 +1682,11 @@ def main():
|
|
|
1652
1682
|
|
|
1653
1683
|
# Save per-modality results to respective dataset paths
|
|
1654
1684
|
if depth_save:
|
|
1655
|
-
depth_out = save_results(
|
|
1685
|
+
depth_out = save_results(
|
|
1686
|
+
depth_save,
|
|
1687
|
+
dataset_config,
|
|
1688
|
+
modality=pred_depth_key or "depth",
|
|
1689
|
+
)
|
|
1656
1690
|
depth_label = (
|
|
1657
1691
|
"Sparse depth"
|
|
1658
1692
|
if _SPARSE_DEPTH_METRIC_ROOT in depth_save
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Helpers for euler-loading modality path selectors in config files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from urllib.parse import parse_qsl
|
|
8
|
+
|
|
9
|
+
from ds_crawler import validate_metadata_scope
|
|
10
|
+
from ds_crawler.zip_utils import validate_split_name
|
|
11
|
+
from euler_loading import Modality
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ModalityPath:
|
|
16
|
+
path: str
|
|
17
|
+
split: str | None = None
|
|
18
|
+
metadata_scope: str | None = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _parse_inline_scope(path: str) -> tuple[str, str | None]:
|
|
22
|
+
candidate_path, sep, fragment = path.rpartition("#")
|
|
23
|
+
if not sep:
|
|
24
|
+
return path, None
|
|
25
|
+
|
|
26
|
+
params = parse_qsl(fragment, keep_blank_values=True)
|
|
27
|
+
scope_values = [value for key, value in params if key == "scope"]
|
|
28
|
+
if not scope_values:
|
|
29
|
+
return path, None
|
|
30
|
+
|
|
31
|
+
unsupported = [key for key, _ in params if key != "scope"]
|
|
32
|
+
if unsupported:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
"Unsupported modality path fragment parameter(s): "
|
|
35
|
+
f"{', '.join(sorted(set(unsupported)))}. "
|
|
36
|
+
"Only '#scope=<metadata_scope>' is supported."
|
|
37
|
+
)
|
|
38
|
+
if len(scope_values) > 1:
|
|
39
|
+
raise ValueError("Modality path may contain at most one '#scope=' selector.")
|
|
40
|
+
|
|
41
|
+
return candidate_path, validate_metadata_scope(scope_values[0])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _parse_inline_split(path: str) -> tuple[str, str | None]:
|
|
45
|
+
colon_pos = path.rfind(":")
|
|
46
|
+
if colon_pos <= 1:
|
|
47
|
+
return path, None
|
|
48
|
+
|
|
49
|
+
candidate_split = path[colon_pos + 1 :]
|
|
50
|
+
candidate_path = path[:colon_pos]
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
split = validate_split_name(candidate_split)
|
|
54
|
+
except ValueError:
|
|
55
|
+
return path, None
|
|
56
|
+
|
|
57
|
+
return candidate_path, split
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parse_modality_path(
|
|
61
|
+
path: str | Path,
|
|
62
|
+
*,
|
|
63
|
+
split: str | None = None,
|
|
64
|
+
metadata_scope: str | None = None,
|
|
65
|
+
) -> ModalityPath:
|
|
66
|
+
"""Parse euler-loading inline split/scope selectors from a config path."""
|
|
67
|
+
raw_path = str(path)
|
|
68
|
+
parsed_path, inline_scope = _parse_inline_scope(raw_path)
|
|
69
|
+
parsed_path, inline_split = _parse_inline_split(parsed_path)
|
|
70
|
+
|
|
71
|
+
if inline_split is not None and split is not None:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"Modality path {raw_path!r} contains an inline split "
|
|
74
|
+
f"({inline_split!r}) but an explicit split={split!r} was also "
|
|
75
|
+
"provided. Use one or the other, not both."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
normalized_scope = (
|
|
79
|
+
validate_metadata_scope(metadata_scope)
|
|
80
|
+
if metadata_scope is not None
|
|
81
|
+
else None
|
|
82
|
+
)
|
|
83
|
+
if inline_scope is not None:
|
|
84
|
+
if normalized_scope is not None and normalized_scope != inline_scope:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"Modality path {raw_path!r} contains an inline metadata scope "
|
|
87
|
+
f"({inline_scope!r}) but an explicit "
|
|
88
|
+
f"metadata_scope={metadata_scope!r} was also provided. Use one "
|
|
89
|
+
"metadata scope selector."
|
|
90
|
+
)
|
|
91
|
+
normalized_scope = inline_scope
|
|
92
|
+
|
|
93
|
+
return ModalityPath(
|
|
94
|
+
path=parsed_path,
|
|
95
|
+
split=split if split is not None else inline_split,
|
|
96
|
+
metadata_scope=normalized_scope,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def normalize_modality_path(path: str | Path, *, split: str | None = None) -> Path:
|
|
101
|
+
"""Return the filesystem/archive root for a config modality path."""
|
|
102
|
+
return Path(parse_modality_path(path, split=split).path)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def build_modality(
|
|
106
|
+
*,
|
|
107
|
+
path: str | Path,
|
|
108
|
+
modality_key: str,
|
|
109
|
+
split: str | None = None,
|
|
110
|
+
metadata_scope: str | None = None,
|
|
111
|
+
loader=None,
|
|
112
|
+
used_as: str | None = None,
|
|
113
|
+
) -> Modality:
|
|
114
|
+
"""Create a Modality after parsing inline config selectors locally."""
|
|
115
|
+
parsed = parse_modality_path(
|
|
116
|
+
path,
|
|
117
|
+
split=split,
|
|
118
|
+
metadata_scope=metadata_scope,
|
|
119
|
+
)
|
|
120
|
+
return Modality(
|
|
121
|
+
path=parsed.path,
|
|
122
|
+
loader=loader,
|
|
123
|
+
used_as=used_as,
|
|
124
|
+
modality_type=modality_key,
|
|
125
|
+
metadata_scope=parsed.metadata_scope or modality_key,
|
|
126
|
+
split=parsed.split,
|
|
127
|
+
)
|
|
@@ -13,6 +13,7 @@ import torch
|
|
|
13
13
|
from ds_crawler import get_dataset_contract, index_dataset_from_path
|
|
14
14
|
from euler_loading import Modality, MultiModalDataset, resolve_loader_module
|
|
15
15
|
|
|
16
|
+
from .config_paths import build_modality
|
|
16
17
|
from .metrics.utils import convert_planar_to_radial
|
|
17
18
|
|
|
18
19
|
# ---------------------------------------------------------------------------
|
|
@@ -570,16 +571,17 @@ def _modality(
|
|
|
570
571
|
path: str,
|
|
571
572
|
modality_key: str,
|
|
572
573
|
split: Optional[str] = None,
|
|
574
|
+
metadata_scope: Optional[str] = None,
|
|
573
575
|
loader: Optional[Callable[..., Any]] = None,
|
|
574
576
|
used_as: Optional[str] = None,
|
|
575
577
|
) -> Modality:
|
|
576
578
|
"""Create an euler-loading modality with an explicit metadata selector."""
|
|
577
|
-
return
|
|
579
|
+
return build_modality(
|
|
578
580
|
path=path,
|
|
579
581
|
loader=loader,
|
|
580
582
|
used_as=used_as,
|
|
581
|
-
|
|
582
|
-
metadata_scope=
|
|
583
|
+
modality_key=modality_key,
|
|
584
|
+
metadata_scope=metadata_scope,
|
|
583
585
|
split=split,
|
|
584
586
|
)
|
|
585
587
|
|
|
@@ -593,6 +595,7 @@ def build_depth_eval_dataset(
|
|
|
593
595
|
pred_depth_split: Optional[str] = None,
|
|
594
596
|
calibration_split: Optional[str] = None,
|
|
595
597
|
segmentation_split: Optional[str] = None,
|
|
598
|
+
pred_depth_metadata_scope: Optional[str] = None,
|
|
596
599
|
) -> MultiModalDataset:
|
|
597
600
|
"""Build a MultiModalDataset for depth evaluation.
|
|
598
601
|
|
|
@@ -611,6 +614,9 @@ def build_depth_eval_dataset(
|
|
|
611
614
|
pred_depth_split: Optional split name for the prediction depth modality.
|
|
612
615
|
calibration_split: Optional split name for the calibration modality.
|
|
613
616
|
segmentation_split: Optional split name for the segmentation modality.
|
|
617
|
+
pred_depth_metadata_scope: Optional metadata scope for the prediction
|
|
618
|
+
depth modality, useful when loading depth-like outputs such as
|
|
619
|
+
``relative_depth`` or ``affine_depth`` through the depth loader.
|
|
614
620
|
|
|
615
621
|
Returns:
|
|
616
622
|
A MultiModalDataset instance.
|
|
@@ -624,6 +630,7 @@ def build_depth_eval_dataset(
|
|
|
624
630
|
"pred": _modality(
|
|
625
631
|
path=pred_depth_path,
|
|
626
632
|
modality_key="depth",
|
|
633
|
+
metadata_scope=pred_depth_metadata_scope,
|
|
627
634
|
used_as="output",
|
|
628
635
|
split=pred_depth_split,
|
|
629
636
|
),
|
|
@@ -662,12 +669,16 @@ def build_sparse_depth_eval_dataset(
|
|
|
662
669
|
intrinsics_split: Optional[str] = None,
|
|
663
670
|
camera_extrinsics_split: Optional[str] = None,
|
|
664
671
|
segmentation_split: Optional[str] = None,
|
|
672
|
+
pred_depth_metadata_scope: Optional[str] = None,
|
|
665
673
|
) -> MultiModalDataset:
|
|
666
674
|
"""Build a MultiModalDataset for sparse pointcloud depth evaluation.
|
|
667
675
|
|
|
668
676
|
The returned dataset yields dense predicted depth under ``"pred"``,
|
|
669
677
|
sparse GT point clouds under ``"gt"``, and hierarchical camera
|
|
670
678
|
calibration under ``"intrinsics"`` and ``"camera_extrinsics"``.
|
|
679
|
+
``pred_depth_metadata_scope`` can select a depth-like prediction scope
|
|
680
|
+
such as ``relative_depth`` or ``affine_depth`` while still loading it
|
|
681
|
+
as a dense depth map.
|
|
671
682
|
"""
|
|
672
683
|
modalities = {
|
|
673
684
|
"gt": _modality(
|
|
@@ -678,6 +689,7 @@ def build_sparse_depth_eval_dataset(
|
|
|
678
689
|
"pred": _modality(
|
|
679
690
|
path=pred_depth_path,
|
|
680
691
|
modality_key="depth",
|
|
692
|
+
metadata_scope=pred_depth_metadata_scope,
|
|
681
693
|
used_as="output",
|
|
682
694
|
split=pred_depth_split,
|
|
683
695
|
),
|
|
@@ -552,6 +552,7 @@ def evaluate_depth_samples(
|
|
|
552
552
|
sky_mask_enabled: bool = False,
|
|
553
553
|
alignment_mode: str = "auto_affine",
|
|
554
554
|
benchmark_depth_range: Optional[tuple[float, float]] = None,
|
|
555
|
+
input_space_hint: Optional[str] = None,
|
|
555
556
|
) -> dict:
|
|
556
557
|
"""Evaluate all depth metrics from a MultiModalDataset.
|
|
557
558
|
|
|
@@ -574,6 +575,9 @@ def evaluate_depth_samples(
|
|
|
574
575
|
benchmark_depth_range: Optional ``(min_meters, max_meters)`` tuple.
|
|
575
576
|
When set, also computes depth metrics for pixels within this
|
|
576
577
|
range, subdivided into square-root-scaled near/mid/far bins.
|
|
578
|
+
input_space_hint: Optional declaration from the prediction config.
|
|
579
|
+
``relative`` or ``affine`` makes ``auto_affine`` run alignment
|
|
580
|
+
even when values are outside the normalized range.
|
|
577
581
|
|
|
578
582
|
Returns:
|
|
579
583
|
Dictionary containing depth aggregate/per-file metrics with:
|
|
@@ -587,6 +591,12 @@ def evaluate_depth_samples(
|
|
|
587
591
|
f"Invalid alignment_mode '{alignment_mode}'. "
|
|
588
592
|
f"Expected one of {sorted(valid_alignment_modes)}."
|
|
589
593
|
)
|
|
594
|
+
valid_input_space_hints = {None, "relative", "affine"}
|
|
595
|
+
if input_space_hint not in valid_input_space_hints:
|
|
596
|
+
raise ValueError(
|
|
597
|
+
f"Invalid input_space_hint '{input_space_hint}'. "
|
|
598
|
+
"Expected None, 'relative', or 'affine'."
|
|
599
|
+
)
|
|
590
600
|
|
|
591
601
|
num_samples = len(dataset)
|
|
592
602
|
if num_samples == 0:
|
|
@@ -1007,7 +1017,14 @@ def evaluate_depth_samples(
|
|
|
1007
1017
|
if i == 0:
|
|
1008
1018
|
pred_min = float(np.nanmin(depth_pred))
|
|
1009
1019
|
pred_max = float(np.nanmax(depth_pred))
|
|
1010
|
-
if
|
|
1020
|
+
if input_space_hint in {"relative", "affine"}:
|
|
1021
|
+
normalized_predictions = True
|
|
1022
|
+
input_space_detected = input_space_hint
|
|
1023
|
+
print(
|
|
1024
|
+
f" Detected native depth space: {input_space_hint} "
|
|
1025
|
+
f"(declared; range [{pred_min:.3f}, {pred_max:.3f}])"
|
|
1026
|
+
)
|
|
1027
|
+
elif pred_max <= 1.0 + 1e-3 and pred_min >= -1.0 - 1e-3:
|
|
1011
1028
|
normalized_predictions = True
|
|
1012
1029
|
input_space_detected = "normalized"
|
|
1013
1030
|
print(
|
|
@@ -1496,6 +1513,7 @@ def evaluate_sparse_depth_samples(
|
|
|
1496
1513
|
sky_mask_enabled: bool = False,
|
|
1497
1514
|
alignment_mode: str = "auto_affine",
|
|
1498
1515
|
benchmark_depth_range: Optional[tuple[float, float]] = None,
|
|
1516
|
+
input_space_hint: Optional[str] = None,
|
|
1499
1517
|
) -> dict:
|
|
1500
1518
|
"""Evaluate dense depth predictions against sparse pointcloud GT.
|
|
1501
1519
|
|
|
@@ -1510,6 +1528,12 @@ def evaluate_sparse_depth_samples(
|
|
|
1510
1528
|
f"Invalid alignment_mode '{alignment_mode}'. "
|
|
1511
1529
|
f"Expected one of {sorted(valid_alignment_modes)}."
|
|
1512
1530
|
)
|
|
1531
|
+
valid_input_space_hints = {None, "relative", "affine"}
|
|
1532
|
+
if input_space_hint not in valid_input_space_hints:
|
|
1533
|
+
raise ValueError(
|
|
1534
|
+
f"Invalid input_space_hint '{input_space_hint}'. "
|
|
1535
|
+
"Expected None, 'relative', or 'affine'."
|
|
1536
|
+
)
|
|
1513
1537
|
|
|
1514
1538
|
num_samples = len(dataset)
|
|
1515
1539
|
if num_samples == 0:
|
|
@@ -1700,7 +1724,14 @@ def evaluate_sparse_depth_samples(
|
|
|
1700
1724
|
if i == 0:
|
|
1701
1725
|
pred_native_dims = depth_pred.shape[:2]
|
|
1702
1726
|
finite_pred = depth_pred[np.isfinite(depth_pred)]
|
|
1703
|
-
if
|
|
1727
|
+
if input_space_hint in {"relative", "affine"} and finite_pred.size:
|
|
1728
|
+
normalized_predictions = True
|
|
1729
|
+
input_space_detected = input_space_hint
|
|
1730
|
+
print(
|
|
1731
|
+
f" Detected native depth space: {input_space_hint} "
|
|
1732
|
+
f"(declared; range [{float(finite_pred.min()):.3f}, {float(finite_pred.max()):.3f}])"
|
|
1733
|
+
)
|
|
1734
|
+
elif finite_pred.size and finite_pred.max() <= 1.0 + 1e-3 and finite_pred.min() >= -1.0 - 1e-3:
|
|
1704
1735
|
normalized_predictions = True
|
|
1705
1736
|
input_space_detected = "normalized"
|
|
1706
1737
|
print(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-eval
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.0
|
|
4
4
|
Summary: Depth map evaluation toolkit with comprehensive metrics
|
|
5
5
|
Author: Depth Eval Contributors
|
|
6
6
|
License: MIT
|
|
@@ -241,7 +241,7 @@ Defines GT modalities, prediction datasets to evaluate, and optional euler_train
|
|
|
241
241
|
|
|
242
242
|
Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`). euler-loading inline selectors are also accepted in `path`, such as `/data/muses.zip:test` or `/data/muses.zip:test#scope=rgb`.
|
|
243
243
|
|
|
244
|
-
For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction
|
|
244
|
+
For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction uses a dense depth-like map under `datasets[].depth`, `datasets[].relative_depth`, or `datasets[].affine_depth`. The evaluator projects the sparse GT point cloud into the prediction image plane using `gt.intrinsics` and `gt.camera_extrinsics`, then computes pointwise depth metrics only at projected valid pixels.
|
|
245
245
|
|
|
246
246
|
#### GT section
|
|
247
247
|
|
|
@@ -261,17 +261,21 @@ For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.dep
|
|
|
261
261
|
|
|
262
262
|
#### Prediction datasets
|
|
263
263
|
|
|
264
|
-
Each entry in `datasets` can include `rgb`, `depth`, `rays`, or any combination:
|
|
264
|
+
Each entry in `datasets` can include `rgb`, one dense depth-like prediction (`depth`, `relative_depth`, or `affine_depth`), `rays`, or any combination:
|
|
265
265
|
|
|
266
266
|
| Field | Required | Description |
|
|
267
267
|
|---|---|---|
|
|
268
268
|
| `name` | yes | Display name for this prediction dataset |
|
|
269
269
|
| `rgb.path` | no\* | Path to predicted RGB dataset |
|
|
270
|
-
| `depth.path` | no\* | Path to predicted dense depth dataset; also used when evaluating against sparse pointcloud GT |
|
|
270
|
+
| `depth.path` | no\* | Path to predicted dense metric depth dataset; also used when evaluating against sparse pointcloud GT |
|
|
271
|
+
| `relative_depth.path` | no\* | Path to predicted dense relative depth dataset; evaluated through the same depth pipeline with scale/shift alignment support |
|
|
272
|
+
| `affine_depth.path` | no\* | Path to predicted dense affine-depth dataset; evaluated through the same depth pipeline with scale/shift alignment support |
|
|
271
273
|
| `rays.path` | no\* | Path to predicted ray direction map dataset |
|
|
272
274
|
| `output_file` | no | Custom output path for results JSON (default: `eval.json` inside the first available modality path) |
|
|
273
275
|
|
|
274
|
-
\* At least one of `rgb.path`, `depth.path`, or `rays.path` is required.
|
|
276
|
+
\* At least one of `rgb.path`, `depth.path`, `relative_depth.path`, `affine_depth.path`, or `rays.path` is required. Use only one dense depth-like entry per prediction dataset.
|
|
277
|
+
|
|
278
|
+
When `relative_depth` or `affine_depth` is used, `--depth-alignment auto_affine` treats the entry as declared non-metric depth and runs scale/shift alignment even if the raw value range is not normalized. `--depth-alignment none` still disables calibration.
|
|
275
279
|
|
|
276
280
|
#### `euler_train` section (optional)
|
|
277
281
|
|
|
@@ -65,6 +65,25 @@ class TestValidateGtConfig:
|
|
|
65
65
|
gt = {"rgb": {"path": f"{rgb_path}#scope=rgb"}}
|
|
66
66
|
validate_gt_config(gt)
|
|
67
67
|
|
|
68
|
+
def test_inline_scope_intrinsics_gt_path_is_valid(self, tmp_path):
|
|
69
|
+
depth_path = tmp_path / "depth"
|
|
70
|
+
depth_path.mkdir()
|
|
71
|
+
intrinsics_path = tmp_path / "frame_camera_trainvaltest.zip"
|
|
72
|
+
with zipfile.ZipFile(intrinsics_path, "w") as zf:
|
|
73
|
+
zf.writestr("dummy.txt", "x")
|
|
74
|
+
gt = {
|
|
75
|
+
"depth": {"path": str(depth_path)},
|
|
76
|
+
"intrinsics": {"path": f"{intrinsics_path}#scope=intrinsics"},
|
|
77
|
+
}
|
|
78
|
+
validate_gt_config(gt)
|
|
79
|
+
|
|
80
|
+
def test_inline_split_and_scope_gt_path_is_valid(self, tmp_path):
|
|
81
|
+
rgb_path = tmp_path / "reference.zip"
|
|
82
|
+
with zipfile.ZipFile(rgb_path, "w") as zf:
|
|
83
|
+
zf.writestr("dummy.txt", "x")
|
|
84
|
+
gt = {"rgb": {"path": f"{rgb_path}:fog_day#scope=rgb"}}
|
|
85
|
+
validate_gt_config(gt)
|
|
86
|
+
|
|
68
87
|
def test_sparse_depth_requires_projection_modalities(self, tmp_path):
|
|
69
88
|
sparse_path = tmp_path / "sparse_depth"
|
|
70
89
|
sparse_path.mkdir()
|
|
@@ -108,6 +127,35 @@ class TestValidateDatasetEntry:
|
|
|
108
127
|
}
|
|
109
128
|
validate_dataset_entry(entry, 0)
|
|
110
129
|
|
|
130
|
+
def test_valid_relative_depth_only(self, tmp_path):
|
|
131
|
+
depth_path = tmp_path / "relative_depth"
|
|
132
|
+
depth_path.mkdir()
|
|
133
|
+
entry = {
|
|
134
|
+
"name": "model_a",
|
|
135
|
+
"relative_depth": {"path": str(depth_path)},
|
|
136
|
+
}
|
|
137
|
+
validate_dataset_entry(entry, 0)
|
|
138
|
+
|
|
139
|
+
def test_valid_affine_depth_only(self, tmp_path):
|
|
140
|
+
depth_path = tmp_path / "affine_depth"
|
|
141
|
+
depth_path.mkdir()
|
|
142
|
+
entry = {
|
|
143
|
+
"name": "model_a",
|
|
144
|
+
"affine_depth": {"path": str(depth_path)},
|
|
145
|
+
}
|
|
146
|
+
validate_dataset_entry(entry, 0)
|
|
147
|
+
|
|
148
|
+
def test_multiple_depth_prediction_entries_are_rejected(self, tmp_path):
|
|
149
|
+
for name in ("depth", "relative_depth"):
|
|
150
|
+
(tmp_path / name).mkdir()
|
|
151
|
+
entry = {
|
|
152
|
+
"name": "model_a",
|
|
153
|
+
"depth": {"path": str(tmp_path / "depth")},
|
|
154
|
+
"relative_depth": {"path": str(tmp_path / "relative_depth")},
|
|
155
|
+
}
|
|
156
|
+
with pytest.raises(ValueError, match="multiple dense depth entries"):
|
|
157
|
+
validate_dataset_entry(entry, 0)
|
|
158
|
+
|
|
111
159
|
def test_valid_rgb_only(self, tmp_path):
|
|
112
160
|
rgb_path = tmp_path / "rgb"
|
|
113
161
|
rgb_path.mkdir()
|
|
@@ -15,8 +15,10 @@ def _install_captured_dataset(monkeypatch):
|
|
|
15
15
|
monkeypatch.setattr(data, "MultiModalDataset", _CapturedDataset)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def _assert_modality(
|
|
19
|
-
|
|
18
|
+
def _assert_modality(
|
|
19
|
+
modality, *, key, split=None, used_as=None, loader=None, scope=None
|
|
20
|
+
):
|
|
21
|
+
assert modality.metadata_scope == (scope or key)
|
|
20
22
|
assert modality.modality_type == key
|
|
21
23
|
assert modality.split == split
|
|
22
24
|
assert modality.used_as == used_as
|
|
@@ -100,6 +102,25 @@ def test_sparse_depth_builder_sets_projection_modality_scopes(monkeypatch):
|
|
|
100
102
|
)
|
|
101
103
|
|
|
102
104
|
|
|
105
|
+
def test_sparse_depth_builder_can_load_relative_depth_prediction_scope(monkeypatch):
|
|
106
|
+
_install_captured_dataset(monkeypatch)
|
|
107
|
+
|
|
108
|
+
dataset = data.build_sparse_depth_eval_dataset(
|
|
109
|
+
gt_sparse_depth_path="/datasets/shared",
|
|
110
|
+
pred_depth_path="/predictions/shared",
|
|
111
|
+
intrinsics_path="/datasets/shared",
|
|
112
|
+
camera_extrinsics_path="/datasets/shared",
|
|
113
|
+
pred_depth_metadata_scope="relative_depth",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
_assert_modality(
|
|
117
|
+
dataset.modalities["pred"],
|
|
118
|
+
key="depth",
|
|
119
|
+
scope="relative_depth",
|
|
120
|
+
used_as="output",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
103
124
|
def test_rgb_builder_sets_rgb_and_auxiliary_modality_scopes(monkeypatch):
|
|
104
125
|
_install_captured_dataset(monkeypatch)
|
|
105
126
|
|
|
@@ -192,3 +213,14 @@ def test_sky_mask_loader_resolution_strips_inline_split(monkeypatch):
|
|
|
192
213
|
assert calls == [
|
|
193
214
|
("/datasets/shared.zip", {"metadata_scope": "segmentation"}),
|
|
194
215
|
]
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_modality_parses_inline_split_and_scope():
|
|
219
|
+
modality = data._modality(
|
|
220
|
+
path="/datasets/frame_camera_trainvaltest.zip:fog_day#scope=intrinsics",
|
|
221
|
+
modality_key="intrinsics",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
assert modality.path == "/datasets/frame_camera_trainvaltest.zip"
|
|
225
|
+
assert modality.split == "fog_day"
|
|
226
|
+
assert modality.metadata_scope == "intrinsics"
|
|
@@ -126,6 +126,18 @@ class TestSaveResultsDirectory:
|
|
|
126
126
|
assert out.exists()
|
|
127
127
|
assert json.loads(out.read_text()) == results
|
|
128
128
|
|
|
129
|
+
def test_saves_to_relative_depth_path(self, tmp_path):
|
|
130
|
+
depth_dir = tmp_path / "pred_relative_depth"
|
|
131
|
+
depth_dir.mkdir()
|
|
132
|
+
|
|
133
|
+
results = {"depth": {"rmse": 0.5}}
|
|
134
|
+
config = {"name": "model", "relative_depth": {"path": str(depth_dir)}}
|
|
135
|
+
out = save_results(results, config, modality="depth")
|
|
136
|
+
|
|
137
|
+
assert out == depth_dir / "eval.json"
|
|
138
|
+
assert out.exists()
|
|
139
|
+
assert json.loads(out.read_text()) == results
|
|
140
|
+
|
|
129
141
|
def test_saves_to_rgb_path_when_no_depth(self, tmp_path):
|
|
130
142
|
rgb_dir = tmp_path / "pred_rgb"
|
|
131
143
|
rgb_dir.mkdir()
|
|
@@ -229,6 +241,21 @@ class TestSaveResultsZip:
|
|
|
229
241
|
stored = json.loads(zf.read("eval.json"))
|
|
230
242
|
assert stored == results
|
|
231
243
|
|
|
244
|
+
def test_inline_zip_scope_writes_eval_json_into_zip(self, tmp_path):
|
|
245
|
+
zp = tmp_path / "dataset.zip"
|
|
246
|
+
with zipfile.ZipFile(zp, "w") as zf:
|
|
247
|
+
zf.writestr("image_001.png", b"\x89PNG")
|
|
248
|
+
|
|
249
|
+
results = {"rgb": {"psnr": 30.0}}
|
|
250
|
+
config = {"name": "model", "rgb": {"path": f"{zp}#scope=rgb"}}
|
|
251
|
+
out = save_results(results, config, modality="rgb")
|
|
252
|
+
|
|
253
|
+
assert out == zp / "eval.json"
|
|
254
|
+
with zipfile.ZipFile(zp, "r") as zf:
|
|
255
|
+
assert "eval.json" in zf.namelist()
|
|
256
|
+
stored = json.loads(zf.read("eval.json"))
|
|
257
|
+
assert stored == results
|
|
258
|
+
|
|
232
259
|
def test_preserves_existing_zip_contents(self, tmp_path):
|
|
233
260
|
zp = tmp_path / "dataset.zip"
|
|
234
261
|
with zipfile.ZipFile(zp, "w") as zf:
|
|
@@ -99,6 +99,22 @@ class _ZeroValidSparseDataset(_OneSampleSparseDataset):
|
|
|
99
99
|
self.pred = np.zeros((3, 3), dtype=np.float32)
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
class _RelativeSparseDataset(_OneSampleSparseDataset):
|
|
103
|
+
def __init__(self):
|
|
104
|
+
super().__init__()
|
|
105
|
+
self.pred = np.zeros((3, 3), dtype=np.float32)
|
|
106
|
+
self.pred[1, 1] = (np.sqrt(12.0) - 3.0) / 2.0
|
|
107
|
+
self.pred[1, 2] = (np.sqrt(24.0) - 3.0) / 2.0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class _AffineSparseDataset(_OneSampleSparseDataset):
|
|
111
|
+
def __init__(self):
|
|
112
|
+
super().__init__()
|
|
113
|
+
self.pred = np.zeros((3, 3), dtype=np.float32)
|
|
114
|
+
self.pred[1, 1] = np.sqrt(12.0) * 10.0 + 7.0
|
|
115
|
+
self.pred[1, 2] = np.sqrt(24.0) * 10.0 + 7.0
|
|
116
|
+
|
|
117
|
+
|
|
102
118
|
def test_sparse_depth_eval_reports_only_pointwise_depth_metrics():
|
|
103
119
|
dataset = _OneSampleSparseDataset()
|
|
104
120
|
|
|
@@ -119,6 +135,50 @@ def test_sparse_depth_eval_reports_only_pointwise_depth_metrics():
|
|
|
119
135
|
np.testing.assert_allclose(metrics["depth_metrics"]["rmse"]["median"], 0.0)
|
|
120
136
|
|
|
121
137
|
|
|
138
|
+
def test_sparse_depth_eval_auto_affine_aligns_relative_depth():
|
|
139
|
+
result = evaluate_sparse_depth_samples(
|
|
140
|
+
_RelativeSparseDataset(),
|
|
141
|
+
pred_is_radial=True,
|
|
142
|
+
num_workers=0,
|
|
143
|
+
alignment_mode="auto_affine",
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
assert result["space_info"]["input_space_detected"] == "normalized"
|
|
147
|
+
assert result["space_info"]["calibration_applied"] is True
|
|
148
|
+
assert result["space_info"]["emitted_spaces"] == ["native", "metric"]
|
|
149
|
+
|
|
150
|
+
native_rmse = result["sparse_depth_native"]["depth_metrics"]["rmse"]["median"]
|
|
151
|
+
metric_rmse = result["sparse_depth_metric"]["depth_metrics"]["rmse"]["median"]
|
|
152
|
+
assert metric_rmse < native_rmse
|
|
153
|
+
np.testing.assert_allclose(metric_rmse, 0.0, atol=1e-6)
|
|
154
|
+
|
|
155
|
+
per_file = result["per_file_metrics"]["files"][0]["metrics"]
|
|
156
|
+
assert "sparse_depth_native" in per_file
|
|
157
|
+
assert "sparse_depth_metric" in per_file
|
|
158
|
+
assert (
|
|
159
|
+
per_file["sparse_depth_metric"]["depth_metrics"]["rmse"]
|
|
160
|
+
< per_file["sparse_depth_native"]["depth_metrics"]["rmse"]
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_sparse_depth_eval_auto_affine_uses_declared_affine_depth_hint():
|
|
165
|
+
result = evaluate_sparse_depth_samples(
|
|
166
|
+
_AffineSparseDataset(),
|
|
167
|
+
pred_is_radial=True,
|
|
168
|
+
num_workers=0,
|
|
169
|
+
alignment_mode="auto_affine",
|
|
170
|
+
input_space_hint="affine",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
assert result["space_info"]["input_space_detected"] == "affine"
|
|
174
|
+
assert result["space_info"]["calibration_applied"] is True
|
|
175
|
+
|
|
176
|
+
native_rmse = result["sparse_depth_native"]["depth_metrics"]["rmse"]["median"]
|
|
177
|
+
metric_rmse = result["sparse_depth_metric"]["depth_metrics"]["rmse"]["median"]
|
|
178
|
+
assert metric_rmse < native_rmse
|
|
179
|
+
np.testing.assert_allclose(metric_rmse, 0.0, atol=1e-5)
|
|
180
|
+
|
|
181
|
+
|
|
122
182
|
def test_sparse_depth_per_file_reports_zero_valid_support():
|
|
123
183
|
result = evaluate_sparse_depth_samples(
|
|
124
184
|
_ZeroValidSparseDataset(),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|