euler-eval 2.20.0__tar.gz → 2.22.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.20.0 → euler_eval-2.22.0}/PKG-INFO +3 -3
- {euler_eval-2.20.0 → euler_eval-2.22.0}/README.md +2 -2
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/cli.py +36 -1
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/evaluate.py +92 -5
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/__init__.py +2 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/points3d_cloud.py +86 -2
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval.egg-info/PKG-INFO +3 -3
- {euler_eval-2.20.0 → euler_eval-2.22.0}/pyproject.toml +1 -1
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_meta_output.py +20 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_points_3d.py +26 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_points_3d_sparse.py +17 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/__init__.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/calibration.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/config_paths.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/data.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/absrel.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/daniel_error.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/depth_binned_error.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/depth_edge_f1.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/depth_standard.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/fid_kid.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/gpu_depth_batch.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/gpu_image_batch.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/high_freq_energy.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/lpips_metric.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/normal_consistency.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/points3d_decomposition.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/points3d_distance.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/points3d_geometry.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/psnr.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/rgb_edge_f1.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/rgb_lpips.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/rgb_psnr_ssim.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/rho_a.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/rmse.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/scale_invariant_log.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/ssim.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/tail_errors.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/metrics/utils.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/sanity_checker.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/utils/hierarchy_parser.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval/validation.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval.egg-info/SOURCES.txt +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval.egg-info/dependency_links.txt +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval.egg-info/entry_points.txt +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval.egg-info/requires.txt +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/euler_eval.egg-info/top_level.txt +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/init_cache.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/setup.cfg +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_alignment.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_cli_device.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_config.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_data.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_data_builders.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_depth_alignment_output.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_depth_standard.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_evaluate_helpers.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_init_cache.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_integration.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_metrics_utils.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_rgb_fid_output.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_rho_a.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_save_results.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_sparse_depth.py +0 -0
- {euler_eval-2.20.0 → euler_eval-2.22.0}/tests/test_validation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-eval
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.0
|
|
4
4
|
Summary: Depth map evaluation toolkit with comprehensive metrics
|
|
5
5
|
Author: Depth Eval Contributors
|
|
6
6
|
License: MIT
|
|
@@ -493,7 +493,7 @@ a `points_3d` canonical alias, keyed as
|
|
|
493
493
|
| Euclidean 3D agreement | `point_error.{image_mean,image_median,pixel_pool}.*` | 3D EPE `mae3d`, `rmse3d`, `median3d`/`p90`/`p95`, relative error `rel_median`/`rel_p90`, and 3D δ-accuracy `acc_<τ>` (within τ m) / `acc_rel_<τ>` |
|
|
494
494
|
| Error decomposition | `error_decomposition.{radial_*,lateral_*,lateral_fraction}` + `angular_error.*` + `rho_a.*` | Splits each error vector into a **radial** (≈ depth) and **lateral** (≈ camera-model) component; `lateral_fraction ∈ [0,1]` attributes blame. Angular ray error and ρ_A (on native directions) measure the implied camera model |
|
|
495
495
|
| Geometric | `geometric.normal_consistency.*`, `geometric.point_edge_f1.*` | True-3D surface normals (cross-product of point-map tangents, no focal assumption) and a 3D discontinuity F1 |
|
|
496
|
-
| Cloud distance | `cloud_distance.chamfer.*`, `cloud_distance.fscore.tau_
|
|
496
|
+
| Cloud distance | `cloud_distance.chamfer.*`, `cloud_distance.fscore.tau_<τ>.*`, `cloud_distance.f_a` | Correspondence-free Chamfer (accuracy/completeness/distance/median), F-score (precision/recall/f1) at fixed distance thresholds τ, and F-score AUC (`f_a`) integrated up to 1/20 of the dataset's maximum GT depth |
|
|
497
497
|
|
|
498
498
|
#### Points-3D gauge alignment
|
|
499
499
|
|
|
@@ -607,7 +607,7 @@ For sparse depth outputs, the internal Python result dict still uses `sparse_dep
|
|
|
607
607
|
|
|
608
608
|
For points_3d outputs, the internal Python result dict uses `points_3d_native`, `points_3d_metric`, and `points_3d` (canonical alias), but serialized `eval.json` metric paths are rooted at `points3d.eval` (no underscore) to satisfy the namespace first-segment rule, mirroring `sparsedepth`. The `metric` space is emitted only when a gauge alignment is applied; otherwise only `native` is present and aliased.
|
|
609
609
|
|
|
610
|
-
When the points_3d metrics come from a sparse pointcloud GT (`gt.sparse_depth` + a predicted `points_3d` map or a dense depth prediction), they use the same `points3d.eval` namespace and are written as `eval.json` beside the selected prediction modality (or to the configured `output_file`). Only the `point_error`, `error_decomposition`, and
|
|
610
|
+
When the points_3d metrics come from a sparse pointcloud GT (`gt.sparse_depth` + a predicted `points_3d` map or a dense depth prediction), they use the same `points3d.eval` namespace and are written as `eval.json` beside the selected prediction modality (or to the configured `output_file`). Only the `point_error`, `error_decomposition`, and `cloud_distance` categories are present. Fixed-threshold cloud diagnostics are directed (completeness/recall), while `cloud_distance.f_a` performs the symmetric comparison required by the F-score AUC definition. The `metric` space is emitted only when the gauge (`--points-3d-alignment` for a point-map prediction, `--depth-alignment` for a depth prediction) calibrates the prediction.
|
|
611
611
|
|
|
612
612
|
Previous single-depth structure (kept under `depth`) is:
|
|
613
613
|
|
|
@@ -455,7 +455,7 @@ a `points_3d` canonical alias, keyed as
|
|
|
455
455
|
| Euclidean 3D agreement | `point_error.{image_mean,image_median,pixel_pool}.*` | 3D EPE `mae3d`, `rmse3d`, `median3d`/`p90`/`p95`, relative error `rel_median`/`rel_p90`, and 3D δ-accuracy `acc_<τ>` (within τ m) / `acc_rel_<τ>` |
|
|
456
456
|
| Error decomposition | `error_decomposition.{radial_*,lateral_*,lateral_fraction}` + `angular_error.*` + `rho_a.*` | Splits each error vector into a **radial** (≈ depth) and **lateral** (≈ camera-model) component; `lateral_fraction ∈ [0,1]` attributes blame. Angular ray error and ρ_A (on native directions) measure the implied camera model |
|
|
457
457
|
| Geometric | `geometric.normal_consistency.*`, `geometric.point_edge_f1.*` | True-3D surface normals (cross-product of point-map tangents, no focal assumption) and a 3D discontinuity F1 |
|
|
458
|
-
| Cloud distance | `cloud_distance.chamfer.*`, `cloud_distance.fscore.tau_
|
|
458
|
+
| Cloud distance | `cloud_distance.chamfer.*`, `cloud_distance.fscore.tau_<τ>.*`, `cloud_distance.f_a` | Correspondence-free Chamfer (accuracy/completeness/distance/median), F-score (precision/recall/f1) at fixed distance thresholds τ, and F-score AUC (`f_a`) integrated up to 1/20 of the dataset's maximum GT depth |
|
|
459
459
|
|
|
460
460
|
#### Points-3D gauge alignment
|
|
461
461
|
|
|
@@ -569,7 +569,7 @@ For sparse depth outputs, the internal Python result dict still uses `sparse_dep
|
|
|
569
569
|
|
|
570
570
|
For points_3d outputs, the internal Python result dict uses `points_3d_native`, `points_3d_metric`, and `points_3d` (canonical alias), but serialized `eval.json` metric paths are rooted at `points3d.eval` (no underscore) to satisfy the namespace first-segment rule, mirroring `sparsedepth`. The `metric` space is emitted only when a gauge alignment is applied; otherwise only `native` is present and aliased.
|
|
571
571
|
|
|
572
|
-
When the points_3d metrics come from a sparse pointcloud GT (`gt.sparse_depth` + a predicted `points_3d` map or a dense depth prediction), they use the same `points3d.eval` namespace and are written as `eval.json` beside the selected prediction modality (or to the configured `output_file`). Only the `point_error`, `error_decomposition`, and
|
|
572
|
+
When the points_3d metrics come from a sparse pointcloud GT (`gt.sparse_depth` + a predicted `points_3d` map or a dense depth prediction), they use the same `points3d.eval` namespace and are written as `eval.json` beside the selected prediction modality (or to the configured `output_file`). Only the `point_error`, `error_decomposition`, and `cloud_distance` categories are present. Fixed-threshold cloud diagnostics are directed (completeness/recall), while `cloud_distance.f_a` performs the symmetric comparison required by the F-score AUC definition. The `metric` space is emitted only when the gauge (`--points-3d-alignment` for a point-map prediction, `--depth-alignment` for a depth prediction) calibrates the prediction.
|
|
573
573
|
|
|
574
574
|
Previous single-depth structure (kept under `depth`) is:
|
|
575
575
|
|
|
@@ -8,6 +8,7 @@ import argparse
|
|
|
8
8
|
import importlib.metadata
|
|
9
9
|
import json
|
|
10
10
|
import math
|
|
11
|
+
import platform
|
|
11
12
|
import sys
|
|
12
13
|
import zipfile
|
|
13
14
|
from pathlib import Path
|
|
@@ -503,6 +504,13 @@ def _points_3d_descriptions() -> dict:
|
|
|
503
504
|
"f1": MetricDescription(
|
|
504
505
|
is_higher_better=True, min_value=0.0, max_value=1.0, display_name="F1"
|
|
505
506
|
),
|
|
507
|
+
"f_a": MetricDescription(
|
|
508
|
+
is_higher_better=True,
|
|
509
|
+
scale="percentage",
|
|
510
|
+
min_value=0.0,
|
|
511
|
+
max_value=100.0,
|
|
512
|
+
display_name="F-score AUC (F_A)",
|
|
513
|
+
),
|
|
506
514
|
# cloud_distance
|
|
507
515
|
"chamfer.accuracy": MetricDescription(
|
|
508
516
|
is_higher_better=False, unit="meters", display_name="Chamfer Accuracy"
|
|
@@ -554,6 +562,24 @@ def _get_version() -> str:
|
|
|
554
562
|
return "0.0.0"
|
|
555
563
|
|
|
556
564
|
|
|
565
|
+
def _package_metadata() -> dict:
|
|
566
|
+
"""Return stable package/runtime metadata for evaluation provenance."""
|
|
567
|
+
return {
|
|
568
|
+
"name": "euler-eval",
|
|
569
|
+
"version": _get_version(),
|
|
570
|
+
"python_version": platform.python_version(),
|
|
571
|
+
"torch_version": str(torch.__version__),
|
|
572
|
+
"cuda_version": torch.version.cuda,
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def _log_package_metadata() -> dict:
|
|
577
|
+
"""Emit and return the package metadata recorded at workflow startup."""
|
|
578
|
+
metadata = _package_metadata()
|
|
579
|
+
print(f"Evaluation package: {json.dumps(metadata, sort_keys=True)}")
|
|
580
|
+
return metadata
|
|
581
|
+
|
|
582
|
+
|
|
557
583
|
def _sparse_depth_metric_set_envelope(
|
|
558
584
|
namespace: _EvalNamespace,
|
|
559
585
|
*,
|
|
@@ -1173,6 +1199,9 @@ def main():
|
|
|
1173
1199
|
|
|
1174
1200
|
args = parser.parse_args()
|
|
1175
1201
|
|
|
1202
|
+
# Emit producer provenance before configuration loading or evaluation work.
|
|
1203
|
+
package_metadata = _log_package_metadata()
|
|
1204
|
+
|
|
1176
1205
|
# Load and validate config
|
|
1177
1206
|
try:
|
|
1178
1207
|
config = load_config(args.config)
|
|
@@ -1283,7 +1312,12 @@ def main():
|
|
|
1283
1312
|
|
|
1284
1313
|
# Register evaluation as running before work begins
|
|
1285
1314
|
if et_run is not None:
|
|
1286
|
-
et_run.add_evaluation(
|
|
1315
|
+
et_run.add_evaluation(
|
|
1316
|
+
ds_name,
|
|
1317
|
+
name=ds_name,
|
|
1318
|
+
status="running",
|
|
1319
|
+
metadata={"package": package_metadata},
|
|
1320
|
+
)
|
|
1287
1321
|
|
|
1288
1322
|
# -- Depth evaluation --
|
|
1289
1323
|
depth_dataset = None
|
|
@@ -2362,6 +2396,7 @@ def main():
|
|
|
2362
2396
|
# name=ds_name,
|
|
2363
2397
|
# status="completed",
|
|
2364
2398
|
metadata={
|
|
2399
|
+
"package": package_metadata,
|
|
2365
2400
|
"results": {
|
|
2366
2401
|
k: v for k, v in all_results.items() if k != "per_file_metrics"
|
|
2367
2402
|
}
|
|
@@ -2854,6 +2854,40 @@ def evaluate_rays_samples(
|
|
|
2854
2854
|
# ---------------------------------------------------------------------------
|
|
2855
2855
|
|
|
2856
2856
|
|
|
2857
|
+
def _points_3d_max_gt_depth(
|
|
2858
|
+
dataset: MultiModalDataset,
|
|
2859
|
+
*,
|
|
2860
|
+
gt_is_depth: bool,
|
|
2861
|
+
gt_depth_is_radial: bool,
|
|
2862
|
+
) -> float:
|
|
2863
|
+
"""Scan the GT once for the dataset maximum radial 3D depth."""
|
|
2864
|
+
max_depth = 0.0
|
|
2865
|
+
for index in range(len(dataset)):
|
|
2866
|
+
sample = dataset[index]
|
|
2867
|
+
if gt_is_depth:
|
|
2868
|
+
intrinsics = _get_intrinsics_K(sample)
|
|
2869
|
+
if intrinsics is None:
|
|
2870
|
+
_, entry_id = _extract_hierarchy(sample)
|
|
2871
|
+
raise ValueError(
|
|
2872
|
+
f"Sample {entry_id!r}: points_3d GT synthesis from depth "
|
|
2873
|
+
"requires intrinsics (gt.intrinsics or gt.calibration)."
|
|
2874
|
+
)
|
|
2875
|
+
gt_points = unproject_depth_to_points(
|
|
2876
|
+
to_numpy_depth(sample["gt"]),
|
|
2877
|
+
intrinsics,
|
|
2878
|
+
depth_is_radial=gt_depth_is_radial,
|
|
2879
|
+
)
|
|
2880
|
+
else:
|
|
2881
|
+
gt_points = to_numpy_points_3d(sample["gt"])
|
|
2882
|
+
finite = np.isfinite(gt_points).all(axis=-1)
|
|
2883
|
+
if np.any(finite):
|
|
2884
|
+
depths = np.linalg.norm(gt_points[finite], axis=-1)
|
|
2885
|
+
positive = depths[depths > 0]
|
|
2886
|
+
if positive.size:
|
|
2887
|
+
max_depth = max(max_depth, float(np.max(positive)))
|
|
2888
|
+
return max_depth
|
|
2889
|
+
|
|
2890
|
+
|
|
2857
2891
|
def evaluate_points_3d_samples(
|
|
2858
2892
|
dataset: MultiModalDataset,
|
|
2859
2893
|
fov_domain: Optional[str] = None,
|
|
@@ -2922,6 +2956,14 @@ def evaluate_points_3d_samples(
|
|
|
2922
2956
|
if num_samples == 0:
|
|
2923
2957
|
raise ValueError("Dataset has no matched samples")
|
|
2924
2958
|
|
|
2959
|
+
print("Scanning points_3d GT depth range for F_A...")
|
|
2960
|
+
dataset_max_depth = _points_3d_max_gt_depth(
|
|
2961
|
+
dataset,
|
|
2962
|
+
gt_is_depth=gt_is_depth,
|
|
2963
|
+
gt_depth_is_radial=gt_depth_is_radial,
|
|
2964
|
+
)
|
|
2965
|
+
f_a_max_threshold = dataset_max_depth / 20.0
|
|
2966
|
+
|
|
2925
2967
|
# Resolve the gauge alignment to apply.
|
|
2926
2968
|
if alignment_mode == "auto":
|
|
2927
2969
|
resolved_mode = "similarity" if input_space_hint == "relative" else "none"
|
|
@@ -3224,7 +3266,10 @@ def evaluate_points_3d_samples(
|
|
|
3224
3266
|
st["edge_images"].append(ef)
|
|
3225
3267
|
|
|
3226
3268
|
cd = compute_cloud_distance_metrics(
|
|
3227
|
-
pv_sp,
|
|
3269
|
+
pv_sp,
|
|
3270
|
+
gv,
|
|
3271
|
+
max_points=max_cloud_points,
|
|
3272
|
+
fscore_auc_max_threshold=f_a_max_threshold,
|
|
3228
3273
|
)
|
|
3229
3274
|
if cd:
|
|
3230
3275
|
st["cloud_images"].append(cd)
|
|
@@ -3325,6 +3370,8 @@ def evaluate_points_3d_samples(
|
|
|
3325
3370
|
"threshold_deg": threshold_deg,
|
|
3326
3371
|
"evaluated_points": total_evaluated_points,
|
|
3327
3372
|
"gt_synthesized_from_depth": gt_is_depth,
|
|
3373
|
+
"max_depth": dataset_max_depth,
|
|
3374
|
+
"f_a_max_threshold": f_a_max_threshold,
|
|
3328
3375
|
},
|
|
3329
3376
|
"space_info": {
|
|
3330
3377
|
"input_space_detected": input_space_detected,
|
|
@@ -3405,9 +3452,11 @@ def evaluate_points_3d_sparse_samples(
|
|
|
3405
3452
|
|
|
3406
3453
|
- ``cloud_distance`` — the **primary** geometry metric here. Because the
|
|
3407
3454
|
GT is sparse and the prediction dense, only the correspondence-free
|
|
3408
|
-
``gt→pred`` direction is
|
|
3409
|
-
(mean/median nearest-predicted distance
|
|
3410
|
-
|
|
3455
|
+
``gt→pred`` direction is used for the fixed-threshold diagnostics, so
|
|
3456
|
+
these report **completeness** (mean/median nearest-predicted distance
|
|
3457
|
+
per GT return) and **recall**. ``f_a`` additionally performs the
|
|
3458
|
+
symmetric comparison required for F-score AUC (see
|
|
3459
|
+
:func:`compute_sparse_cloud_distance_metrics`).
|
|
3411
3460
|
- ``point_error`` — Euclidean 3D agreement (EPE/RMSE/percentiles/δ-acc)
|
|
3412
3461
|
computed at the projected sparse correspondences.
|
|
3413
3462
|
- ``error_decomposition`` — radial (≈depth) vs lateral (≈camera-model)
|
|
@@ -3462,6 +3511,39 @@ def evaluate_points_3d_sparse_samples(
|
|
|
3462
3511
|
if num_samples == 0:
|
|
3463
3512
|
raise ValueError("Dataset has no matched samples")
|
|
3464
3513
|
|
|
3514
|
+
print("Scanning sparse points_3d GT depth range for F_A...")
|
|
3515
|
+
dataset_max_depth = 0.0
|
|
3516
|
+
for sample_index in range(num_samples):
|
|
3517
|
+
range_sample = dataset[sample_index]
|
|
3518
|
+
range_cloud = to_numpy_point_cloud(range_sample["gt"])
|
|
3519
|
+
if pred_is_depth:
|
|
3520
|
+
range_shape = to_numpy_depth(range_sample["pred"]).shape[:2]
|
|
3521
|
+
else:
|
|
3522
|
+
range_shape = to_numpy_points_3d(range_sample["pred"]).shape[:2]
|
|
3523
|
+
range_intrinsics = _get_intrinsics_K(range_sample)
|
|
3524
|
+
range_extrinsics, _ = _get_pointcloud_to_camera_extrinsics(range_sample)
|
|
3525
|
+
if range_intrinsics is None or range_extrinsics is None:
|
|
3526
|
+
# The main evaluation loop emits the detailed sample-specific
|
|
3527
|
+
# configuration error; do not duplicate it in this range scan.
|
|
3528
|
+
continue
|
|
3529
|
+
range_map, range_mask, _ = project_point_cloud_to_point_map(
|
|
3530
|
+
range_cloud,
|
|
3531
|
+
range_intrinsics,
|
|
3532
|
+
range_extrinsics,
|
|
3533
|
+
range_shape,
|
|
3534
|
+
)
|
|
3535
|
+
if np.any(range_mask):
|
|
3536
|
+
range_depths = np.linalg.norm(range_map[range_mask], axis=-1)
|
|
3537
|
+
finite_depths = range_depths[
|
|
3538
|
+
np.isfinite(range_depths) & (range_depths > 0)
|
|
3539
|
+
]
|
|
3540
|
+
if finite_depths.size:
|
|
3541
|
+
dataset_max_depth = max(
|
|
3542
|
+
dataset_max_depth,
|
|
3543
|
+
float(np.max(finite_depths)),
|
|
3544
|
+
)
|
|
3545
|
+
f_a_max_threshold = dataset_max_depth / 20.0
|
|
3546
|
+
|
|
3465
3547
|
resolved_domain: Optional[str] = fov_domain
|
|
3466
3548
|
threshold_deg: Optional[float] = (
|
|
3467
3549
|
get_threshold_for_domain(resolved_domain) if resolved_domain else None
|
|
@@ -3557,7 +3639,10 @@ def evaluate_points_3d_sparse_samples(
|
|
|
3557
3639
|
store["dec_images"].append(dec)
|
|
3558
3640
|
fval["error_decomposition"] = dict(dec)
|
|
3559
3641
|
cd = compute_sparse_cloud_distance_metrics(
|
|
3560
|
-
pred_map[pred_ok],
|
|
3642
|
+
pred_map[pred_ok],
|
|
3643
|
+
gt_cloud,
|
|
3644
|
+
max_points=max_cloud_points,
|
|
3645
|
+
fscore_auc_max_threshold=f_a_max_threshold,
|
|
3561
3646
|
)
|
|
3562
3647
|
if cd:
|
|
3563
3648
|
store["cloud_images"].append(cd)
|
|
@@ -3975,6 +4060,8 @@ def evaluate_points_3d_sparse_samples(
|
|
|
3975
4060
|
"projected_pixels": total_projected_pixels,
|
|
3976
4061
|
"evaluated_points": total_evaluated_points,
|
|
3977
4062
|
"gt_synthesized_from_depth": False,
|
|
4063
|
+
"max_depth": dataset_max_depth,
|
|
4064
|
+
"f_a_max_threshold": f_a_max_threshold,
|
|
3978
4065
|
},
|
|
3979
4066
|
"space_info": {
|
|
3980
4067
|
"input_space_detected": input_space_detected,
|
|
@@ -105,6 +105,7 @@ from .points3d_geometry import (
|
|
|
105
105
|
)
|
|
106
106
|
from .points3d_cloud import (
|
|
107
107
|
compute_cloud_distance_metrics,
|
|
108
|
+
compute_fscore_auc,
|
|
108
109
|
compute_sparse_cloud_distance_metrics,
|
|
109
110
|
FSCORE_THRESHOLDS as POINTS3D_FSCORE_THRESHOLDS,
|
|
110
111
|
DEFAULT_MAX_POINTS as POINTS3D_DEFAULT_MAX_POINTS,
|
|
@@ -197,6 +198,7 @@ __all__ = [
|
|
|
197
198
|
"compute_point_normal_angles",
|
|
198
199
|
"compute_point_edge_f1",
|
|
199
200
|
"compute_cloud_distance_metrics",
|
|
201
|
+
"compute_fscore_auc",
|
|
200
202
|
"compute_sparse_cloud_distance_metrics",
|
|
201
203
|
"POINTS3D_FSCORE_THRESHOLDS",
|
|
202
204
|
"POINTS3D_DEFAULT_MAX_POINTS",
|
|
@@ -36,11 +36,66 @@ def _subsample(points: np.ndarray, max_points: int) -> np.ndarray:
|
|
|
36
36
|
return points[idx]
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def compute_fscore_auc(
|
|
40
|
+
dist_pred_to_gt: np.ndarray,
|
|
41
|
+
dist_gt_to_pred: np.ndarray,
|
|
42
|
+
max_threshold: float,
|
|
43
|
+
) -> Optional[float]:
|
|
44
|
+
"""Return the normalized area under the point-cloud F1 curve.
|
|
45
|
+
|
|
46
|
+
``F_A`` integrates the symmetric nearest-neighbour F1 score over all
|
|
47
|
+
distance tolerances from zero through ``max_threshold``. The empirical
|
|
48
|
+
precision and recall curves are step functions, so this implementation
|
|
49
|
+
integrates their harmonic mean exactly rather than choosing a sampling
|
|
50
|
+
resolution. The result is reported as a percentage in ``[0, 100]``.
|
|
51
|
+
"""
|
|
52
|
+
pred_dist = np.asarray(dist_pred_to_gt, dtype=np.float64).reshape(-1)
|
|
53
|
+
gt_dist = np.asarray(dist_gt_to_pred, dtype=np.float64).reshape(-1)
|
|
54
|
+
if (
|
|
55
|
+
pred_dist.size == 0
|
|
56
|
+
or gt_dist.size == 0
|
|
57
|
+
or not np.isfinite(max_threshold)
|
|
58
|
+
or max_threshold <= 0
|
|
59
|
+
):
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
pred_dist = np.sort(pred_dist[np.isfinite(pred_dist)])
|
|
63
|
+
gt_dist = np.sort(gt_dist[np.isfinite(gt_dist)])
|
|
64
|
+
if pred_dist.size == 0 or gt_dist.size == 0:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
events = np.unique(
|
|
68
|
+
np.concatenate(
|
|
69
|
+
[
|
|
70
|
+
np.array([0.0, max_threshold], dtype=np.float64),
|
|
71
|
+
pred_dist[(pred_dist >= 0.0) & (pred_dist < max_threshold)],
|
|
72
|
+
gt_dist[(gt_dist >= 0.0) & (gt_dist < max_threshold)],
|
|
73
|
+
]
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
left = events[:-1]
|
|
77
|
+
widths = np.diff(events)
|
|
78
|
+
# Immediately to the right of an event, all distances equal to that event
|
|
79
|
+
# satisfy the paper's threshold test. Point values at the discontinuities
|
|
80
|
+
# have zero measure and therefore do not affect the integral.
|
|
81
|
+
precision = np.searchsorted(pred_dist, left, side="right") / pred_dist.size
|
|
82
|
+
recall = np.searchsorted(gt_dist, left, side="right") / gt_dist.size
|
|
83
|
+
denominator = precision + recall
|
|
84
|
+
f1 = np.divide(
|
|
85
|
+
2.0 * precision * recall,
|
|
86
|
+
denominator,
|
|
87
|
+
out=np.zeros_like(denominator),
|
|
88
|
+
where=denominator > 0,
|
|
89
|
+
)
|
|
90
|
+
return float(np.sum(f1 * widths) / max_threshold * 100.0)
|
|
91
|
+
|
|
92
|
+
|
|
39
93
|
def compute_cloud_distance_metrics(
|
|
40
94
|
pred_points: np.ndarray,
|
|
41
95
|
gt_points: np.ndarray,
|
|
42
96
|
max_points: int = DEFAULT_MAX_POINTS,
|
|
43
97
|
thresholds: tuple = FSCORE_THRESHOLDS,
|
|
98
|
+
fscore_auc_max_threshold: Optional[float] = None,
|
|
44
99
|
) -> Optional[dict]:
|
|
45
100
|
"""Chamfer distance and F-score between two 3D point sets.
|
|
46
101
|
|
|
@@ -49,6 +104,8 @@ def compute_cloud_distance_metrics(
|
|
|
49
104
|
gt_points: ``(M, 3)`` ground-truth points.
|
|
50
105
|
max_points: Per-cloud subsample cap (``None`` to disable).
|
|
51
106
|
thresholds: F-score distance thresholds in metres.
|
|
107
|
+
fscore_auc_max_threshold: When provided, also compute ``f_a``, the
|
|
108
|
+
normalized F1 AUC through this distance threshold, as a percentage.
|
|
52
109
|
|
|
53
110
|
Returns:
|
|
54
111
|
Dict with a ``chamfer`` block (``accuracy``, ``completeness``,
|
|
@@ -98,7 +155,16 @@ def compute_cloud_distance_metrics(
|
|
|
98
155
|
"f1": f1,
|
|
99
156
|
}
|
|
100
157
|
|
|
101
|
-
|
|
158
|
+
result = {"chamfer": chamfer, "fscore": fscore}
|
|
159
|
+
if fscore_auc_max_threshold is not None:
|
|
160
|
+
f_a = compute_fscore_auc(
|
|
161
|
+
dist_pred_to_gt,
|
|
162
|
+
dist_gt_to_pred,
|
|
163
|
+
fscore_auc_max_threshold,
|
|
164
|
+
)
|
|
165
|
+
if f_a is not None:
|
|
166
|
+
result["f_a"] = f_a
|
|
167
|
+
return result
|
|
102
168
|
|
|
103
169
|
|
|
104
170
|
def compute_sparse_cloud_distance_metrics(
|
|
@@ -106,6 +172,7 @@ def compute_sparse_cloud_distance_metrics(
|
|
|
106
172
|
gt_points: np.ndarray,
|
|
107
173
|
max_points: int = DEFAULT_MAX_POINTS,
|
|
108
174
|
thresholds: tuple = FSCORE_THRESHOLDS,
|
|
175
|
+
fscore_auc_max_threshold: Optional[float] = None,
|
|
109
176
|
) -> Optional[dict]:
|
|
110
177
|
"""Directed (GT→pred) cloud agreement for a sparse GT point set.
|
|
111
178
|
|
|
@@ -130,6 +197,9 @@ def compute_sparse_cloud_distance_metrics(
|
|
|
130
197
|
gt_points: ``(M, 3)`` ground-truth (sparse) points.
|
|
131
198
|
max_points: Per-cloud subsample cap (``None`` to disable).
|
|
132
199
|
thresholds: Recall distance thresholds in metres.
|
|
200
|
+
fscore_auc_max_threshold: When provided, compute symmetric ``f_a`` as
|
|
201
|
+
the normalized F1 AUC through this distance threshold. The fixed
|
|
202
|
+
threshold metrics remain directed because the GT is sparse.
|
|
133
203
|
|
|
134
204
|
Returns:
|
|
135
205
|
Dict with a ``chamfer`` block (``completeness``, ``median``) and an
|
|
@@ -160,4 +230,18 @@ def compute_sparse_cloud_distance_metrics(
|
|
|
160
230
|
recall = float(np.mean(dist_gt_to_pred < tau))
|
|
161
231
|
fscore[threshold_key(tau)] = {"recall": recall}
|
|
162
232
|
|
|
163
|
-
|
|
233
|
+
result = {"chamfer": chamfer, "fscore": fscore}
|
|
234
|
+
if fscore_auc_max_threshold is not None:
|
|
235
|
+
# F_A is defined from symmetric F1, so it additionally needs the
|
|
236
|
+
# prediction-to-GT distances even though the sparse diagnostic leaves
|
|
237
|
+
# above intentionally remain completeness/recall-only.
|
|
238
|
+
tree_gt = cKDTree(gt)
|
|
239
|
+
dist_pred_to_gt, _ = tree_gt.query(pred, k=1)
|
|
240
|
+
f_a = compute_fscore_auc(
|
|
241
|
+
dist_pred_to_gt,
|
|
242
|
+
dist_gt_to_pred,
|
|
243
|
+
fscore_auc_max_threshold,
|
|
244
|
+
)
|
|
245
|
+
if f_a is not None:
|
|
246
|
+
result["f_a"] = f_a
|
|
247
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-eval
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.0
|
|
4
4
|
Summary: Depth map evaluation toolkit with comprehensive metrics
|
|
5
5
|
Author: Depth Eval Contributors
|
|
6
6
|
License: MIT
|
|
@@ -493,7 +493,7 @@ a `points_3d` canonical alias, keyed as
|
|
|
493
493
|
| Euclidean 3D agreement | `point_error.{image_mean,image_median,pixel_pool}.*` | 3D EPE `mae3d`, `rmse3d`, `median3d`/`p90`/`p95`, relative error `rel_median`/`rel_p90`, and 3D δ-accuracy `acc_<τ>` (within τ m) / `acc_rel_<τ>` |
|
|
494
494
|
| Error decomposition | `error_decomposition.{radial_*,lateral_*,lateral_fraction}` + `angular_error.*` + `rho_a.*` | Splits each error vector into a **radial** (≈ depth) and **lateral** (≈ camera-model) component; `lateral_fraction ∈ [0,1]` attributes blame. Angular ray error and ρ_A (on native directions) measure the implied camera model |
|
|
495
495
|
| Geometric | `geometric.normal_consistency.*`, `geometric.point_edge_f1.*` | True-3D surface normals (cross-product of point-map tangents, no focal assumption) and a 3D discontinuity F1 |
|
|
496
|
-
| Cloud distance | `cloud_distance.chamfer.*`, `cloud_distance.fscore.tau_
|
|
496
|
+
| Cloud distance | `cloud_distance.chamfer.*`, `cloud_distance.fscore.tau_<τ>.*`, `cloud_distance.f_a` | Correspondence-free Chamfer (accuracy/completeness/distance/median), F-score (precision/recall/f1) at fixed distance thresholds τ, and F-score AUC (`f_a`) integrated up to 1/20 of the dataset's maximum GT depth |
|
|
497
497
|
|
|
498
498
|
#### Points-3D gauge alignment
|
|
499
499
|
|
|
@@ -607,7 +607,7 @@ For sparse depth outputs, the internal Python result dict still uses `sparse_dep
|
|
|
607
607
|
|
|
608
608
|
For points_3d outputs, the internal Python result dict uses `points_3d_native`, `points_3d_metric`, and `points_3d` (canonical alias), but serialized `eval.json` metric paths are rooted at `points3d.eval` (no underscore) to satisfy the namespace first-segment rule, mirroring `sparsedepth`. The `metric` space is emitted only when a gauge alignment is applied; otherwise only `native` is present and aliased.
|
|
609
609
|
|
|
610
|
-
When the points_3d metrics come from a sparse pointcloud GT (`gt.sparse_depth` + a predicted `points_3d` map or a dense depth prediction), they use the same `points3d.eval` namespace and are written as `eval.json` beside the selected prediction modality (or to the configured `output_file`). Only the `point_error`, `error_decomposition`, and
|
|
610
|
+
When the points_3d metrics come from a sparse pointcloud GT (`gt.sparse_depth` + a predicted `points_3d` map or a dense depth prediction), they use the same `points3d.eval` namespace and are written as `eval.json` beside the selected prediction modality (or to the configured `output_file`). Only the `point_error`, `error_decomposition`, and `cloud_distance` categories are present. Fixed-threshold cloud diagnostics are directed (completeness/recall), while `cloud_distance.f_a` performs the symmetric comparison required by the F-score AUC definition. The `metric` space is emitted only when the gauge (`--points-3d-alignment` for a point-map prediction, `--depth-alignment` for a depth prediction) calibrates the prediction.
|
|
611
611
|
|
|
612
612
|
Previous single-depth structure (kept under `depth`) is:
|
|
613
613
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tests for the structured meta block in eval.json output."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import re
|
|
4
5
|
|
|
5
6
|
from euler_eval.cli import (
|
|
@@ -7,6 +8,8 @@ from euler_eval.cli import (
|
|
|
7
8
|
_clean_per_file_metrics,
|
|
8
9
|
_depth_eval_axes,
|
|
9
10
|
_DEPTH_EVAL_DESCRIPTIONS,
|
|
11
|
+
_log_package_metadata,
|
|
12
|
+
_package_metadata,
|
|
10
13
|
_sparse_depth_eval_axes,
|
|
11
14
|
_SPARSE_DEPTH_EVAL_DESCRIPTIONS,
|
|
12
15
|
_SPARSE_DEPTH_METRIC_NAMESPACE,
|
|
@@ -26,6 +29,23 @@ _RGB_EVAL_AXES = _rgb_eval_axes()
|
|
|
26
29
|
_METRIC_NAMESPACE_RE = re.compile(r"^[a-z0-9]+(?:\.[a-z0-9_]+)+$")
|
|
27
30
|
|
|
28
31
|
|
|
32
|
+
class TestPackageMetadataLogging:
|
|
33
|
+
def test_package_metadata_contains_runtime_provenance(self):
|
|
34
|
+
metadata = _package_metadata()
|
|
35
|
+
assert metadata["name"] == "euler-eval"
|
|
36
|
+
assert metadata["version"]
|
|
37
|
+
assert metadata["python_version"]
|
|
38
|
+
assert metadata["torch_version"]
|
|
39
|
+
assert "cuda_version" in metadata
|
|
40
|
+
|
|
41
|
+
def test_package_metadata_log_is_structured(self, capsys):
|
|
42
|
+
metadata = _log_package_metadata()
|
|
43
|
+
output = capsys.readouterr().out.strip()
|
|
44
|
+
prefix = "Evaluation package: "
|
|
45
|
+
assert output.startswith(prefix)
|
|
46
|
+
assert json.loads(output[len(prefix):]) == metadata
|
|
47
|
+
|
|
48
|
+
|
|
29
49
|
def _flatten_numeric_metrics(obj, prefix=""):
|
|
30
50
|
results = []
|
|
31
51
|
for key, value in obj.items():
|
|
@@ -22,6 +22,7 @@ from euler_eval.evaluate import evaluate_points_3d_samples
|
|
|
22
22
|
from euler_eval.metrics import (
|
|
23
23
|
compute_cloud_distance_metrics,
|
|
24
24
|
compute_decomposition_metrics,
|
|
25
|
+
compute_fscore_auc,
|
|
25
26
|
compute_point_edge_f1,
|
|
26
27
|
compute_point_error_metrics,
|
|
27
28
|
compute_point_normal_angles,
|
|
@@ -245,6 +246,12 @@ class TestGeometry:
|
|
|
245
246
|
|
|
246
247
|
|
|
247
248
|
class TestCloud:
|
|
249
|
+
def test_fscore_auc_extremes(self):
|
|
250
|
+
perfect = np.zeros(4, dtype=np.float64)
|
|
251
|
+
missed = np.full(4, 2.0, dtype=np.float64)
|
|
252
|
+
assert compute_fscore_auc(perfect, perfect, 1.0) == 100.0
|
|
253
|
+
assert compute_fscore_auc(missed, missed, 1.0) == 0.0
|
|
254
|
+
|
|
248
255
|
def test_perfect_clouds(self):
|
|
249
256
|
gt, _ = _gt_pointmap()
|
|
250
257
|
pts = gt.reshape(-1, 3)
|
|
@@ -252,6 +259,16 @@ class TestCloud:
|
|
|
252
259
|
assert m["chamfer"]["distance"] < 1e-6
|
|
253
260
|
assert m["fscore"]["tau_0_1"]["f1"] == 1.0
|
|
254
261
|
|
|
262
|
+
def test_cloud_f_a_uses_requested_depth_range(self):
|
|
263
|
+
gt, _ = _gt_pointmap()
|
|
264
|
+
pts = gt.reshape(-1, 3)
|
|
265
|
+
m = compute_cloud_distance_metrics(
|
|
266
|
+
pts,
|
|
267
|
+
pts,
|
|
268
|
+
fscore_auc_max_threshold=0.5,
|
|
269
|
+
)
|
|
270
|
+
assert m["f_a"] == 100.0
|
|
271
|
+
|
|
255
272
|
def test_shifted_cloud(self):
|
|
256
273
|
gt, _ = _gt_pointmap()
|
|
257
274
|
pts = gt.reshape(-1, 3)
|
|
@@ -288,6 +305,10 @@ class TestEvaluate:
|
|
|
288
305
|
assert pe["acc_0_05"] == 100.0
|
|
289
306
|
assert result["points_3d"]["error_decomposition"]["rho_a"]["mean"] > 0.95
|
|
290
307
|
assert result["dataset_info"]["fov_domain"] == "sfov"
|
|
308
|
+
assert result["dataset_info"]["f_a_max_threshold"] == pytest.approx(
|
|
309
|
+
result["dataset_info"]["max_depth"] / 20.0
|
|
310
|
+
)
|
|
311
|
+
assert result["points_3d"]["cloud_distance"]["f_a"] == 100.0
|
|
291
312
|
|
|
292
313
|
def test_similarity_recovers_relative_prediction(self):
|
|
293
314
|
gt, K = _gt_pointmap()
|
|
@@ -304,6 +325,10 @@ class TestEvaluate:
|
|
|
304
325
|
metric_mae = result["points_3d_metric"]["point_error"]["image_mean"]["mae3d"]
|
|
305
326
|
assert metric_mae < 1e-3
|
|
306
327
|
assert native_mae > metric_mae
|
|
328
|
+
assert "f_a" in result["points_3d_native"]["cloud_distance"]
|
|
329
|
+
assert result["points_3d_metric"]["cloud_distance"]["f_a"] == pytest.approx(
|
|
330
|
+
100.0, abs=1e-3
|
|
331
|
+
)
|
|
307
332
|
assert result["points_3d"] is result["points_3d_metric"]
|
|
308
333
|
|
|
309
334
|
def test_auto_with_relative_hint_aligns(self):
|
|
@@ -418,6 +443,7 @@ class TestCliNamespace:
|
|
|
418
443
|
assert envelope["axes"]["space"]["position"] == 0
|
|
419
444
|
assert envelope["axes"]["reduction"]["position"] == 2
|
|
420
445
|
assert "mae3d" in envelope["metricDescriptions"]
|
|
446
|
+
assert "f_a" in envelope["metricDescriptions"]
|
|
421
447
|
|
|
422
448
|
def test_real_metric_tree_paths_conform(self):
|
|
423
449
|
"""Flattened paths of an actual eval result live under points3d.eval."""
|
|
@@ -132,6 +132,18 @@ class TestSparseCloud:
|
|
|
132
132
|
for tau_block in m["fscore"].values():
|
|
133
133
|
assert set(tau_block) == {"recall"}
|
|
134
134
|
|
|
135
|
+
def test_f_a_is_available_without_publishing_symmetric_diagnostics(self):
|
|
136
|
+
points = np.random.RandomState(2).randn(100, 3).astype(np.float32)
|
|
137
|
+
m = compute_sparse_cloud_distance_metrics(
|
|
138
|
+
points,
|
|
139
|
+
points,
|
|
140
|
+
fscore_auc_max_threshold=1.0,
|
|
141
|
+
)
|
|
142
|
+
assert m["f_a"] == 100.0
|
|
143
|
+
assert set(m["chamfer"]) == {"completeness", "median"}
|
|
144
|
+
for tau_block in m["fscore"].values():
|
|
145
|
+
assert set(tau_block) == {"recall"}
|
|
146
|
+
|
|
135
147
|
def test_offset_cloud_increases_completeness(self):
|
|
136
148
|
depth = _dense_depth()
|
|
137
149
|
K = _make_intrinsics(400.0, 400.0, 30.0, 20.0)
|
|
@@ -223,6 +235,10 @@ class TestEvaluate:
|
|
|
223
235
|
assert p["point_error"]["image_mean"]["mae3d"] < 1e-4
|
|
224
236
|
assert p["cloud_distance"]["chamfer"]["completeness"] < 1e-4
|
|
225
237
|
assert p["cloud_distance"]["fscore"]["tau_0_1"]["recall"] == 1.0
|
|
238
|
+
assert "f_a" in p["cloud_distance"]
|
|
239
|
+
assert res["dataset_info"]["f_a_max_threshold"] == pytest.approx(
|
|
240
|
+
res["dataset_info"]["max_depth"] / 20.0
|
|
241
|
+
)
|
|
226
242
|
assert p["error_decomposition"]["rho_a"]["mean"] > 0.9
|
|
227
243
|
# No dense-neighbourhood geometric metrics on sparse GT.
|
|
228
244
|
assert "geometric" not in p
|
|
@@ -393,6 +409,7 @@ class TestEvaluateNativePoints:
|
|
|
393
409
|
assert p["point_error"]["image_mean"]["mae3d"] < 1e-4
|
|
394
410
|
assert p["cloud_distance"]["chamfer"]["completeness"] < 1e-4
|
|
395
411
|
assert p["cloud_distance"]["fscore"]["tau_0_1"]["recall"] == 1.0
|
|
412
|
+
assert 0.0 <= p["cloud_distance"]["f_a"] <= 100.0
|
|
396
413
|
|
|
397
414
|
def test_similarity_recovers_relative_pointmap(self):
|
|
398
415
|
ds, gt_dense, K = self._pointmap_dataset(None)
|
|
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
|
|
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
|