euler-eval 2.17.0__tar.gz → 2.20.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.
Files changed (65) hide show
  1. {euler_eval-2.17.0 → euler_eval-2.20.0}/PKG-INFO +3 -3
  2. {euler_eval-2.17.0 → euler_eval-2.20.0}/README.md +2 -2
  3. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/cli.py +3 -22
  4. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/evaluate.py +9 -0
  5. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/sanity_checker.py +16 -4
  6. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval.egg-info/PKG-INFO +3 -3
  7. {euler_eval-2.17.0 → euler_eval-2.20.0}/pyproject.toml +1 -1
  8. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_points_3d.py +9 -0
  9. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_points_3d_sparse.py +43 -19
  10. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/__init__.py +0 -0
  11. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/calibration.py +0 -0
  12. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/config_paths.py +0 -0
  13. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/data.py +0 -0
  14. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/__init__.py +0 -0
  15. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/absrel.py +0 -0
  16. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/daniel_error.py +0 -0
  17. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/depth_binned_error.py +0 -0
  18. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/depth_edge_f1.py +0 -0
  19. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/depth_standard.py +0 -0
  20. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/fid_kid.py +0 -0
  21. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/gpu_depth_batch.py +0 -0
  22. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/gpu_image_batch.py +0 -0
  23. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/high_freq_energy.py +0 -0
  24. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/lpips_metric.py +0 -0
  25. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/normal_consistency.py +0 -0
  26. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/points3d_cloud.py +0 -0
  27. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/points3d_decomposition.py +0 -0
  28. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/points3d_distance.py +0 -0
  29. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/points3d_geometry.py +0 -0
  30. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/psnr.py +0 -0
  31. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/rgb_edge_f1.py +0 -0
  32. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/rgb_lpips.py +0 -0
  33. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/rgb_psnr_ssim.py +0 -0
  34. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/rho_a.py +0 -0
  35. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/rmse.py +0 -0
  36. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/scale_invariant_log.py +0 -0
  37. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/ssim.py +0 -0
  38. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/tail_errors.py +0 -0
  39. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/metrics/utils.py +0 -0
  40. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/utils/hierarchy_parser.py +0 -0
  41. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval/validation.py +0 -0
  42. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval.egg-info/SOURCES.txt +0 -0
  43. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval.egg-info/dependency_links.txt +0 -0
  44. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval.egg-info/entry_points.txt +0 -0
  45. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval.egg-info/requires.txt +0 -0
  46. {euler_eval-2.17.0 → euler_eval-2.20.0}/euler_eval.egg-info/top_level.txt +0 -0
  47. {euler_eval-2.17.0 → euler_eval-2.20.0}/init_cache.py +0 -0
  48. {euler_eval-2.17.0 → euler_eval-2.20.0}/setup.cfg +0 -0
  49. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_alignment.py +0 -0
  50. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_cli_device.py +0 -0
  51. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_config.py +0 -0
  52. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_data.py +0 -0
  53. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_data_builders.py +0 -0
  54. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_depth_alignment_output.py +0 -0
  55. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_depth_standard.py +0 -0
  56. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_evaluate_helpers.py +0 -0
  57. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_init_cache.py +0 -0
  58. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_integration.py +0 -0
  59. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_meta_output.py +0 -0
  60. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_metrics_utils.py +0 -0
  61. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_rgb_fid_output.py +0 -0
  62. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_rho_a.py +0 -0
  63. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_save_results.py +0 -0
  64. {euler_eval-2.17.0 → euler_eval-2.20.0}/tests/test_sparse_depth.py +0 -0
  65. {euler_eval-2.17.0 → euler_eval-2.20.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.17.0
3
+ Version: 2.20.0
4
4
  Summary: Depth map evaluation toolkit with comprehensive metrics
5
5
  Author: Depth Eval Contributors
6
6
  License: MIT
@@ -339,7 +339,7 @@ When the GT is a sparse pointcloud, the evaluator also produces **3D (`points_3d
339
339
  - a **predicted point map** (`datasets[].points_3d`, an `(H,W,3)` map) — evaluated *directly* against the sparse GT cloud, with the 3D **similarity** gauge selected by `--points-3d-alignment`; or
340
340
  - a **dense depth map** (`datasets[].depth`/`relative_depth`/`affine_depth`) — unprojected with `gt.intrinsics` into a point map, with the depth **affine** gauge selected by `--depth-alignment`.
341
341
 
342
- If a dataset entry provides both, the predicted `points_3d` map is preferred (the depth is still scored by the pointwise sparse-depth metrics). Either way the sparse GT cloud is projected into the prediction plane, and the 3D results are written to a separate `points3d_eval.json` (or `<output_file>_points3d.json`) so they never clobber the sparse-depth `eval.json`.
342
+ If a dataset entry provides both, the predicted `points_3d` map is preferred (the depth is still scored by the pointwise sparse-depth metrics). Either way the sparse GT cloud is projected into the prediction plane, and the 3D results are written as `eval.json` in the selected prediction modality path (or to the configured `output_file`). Give simultaneous evaluations distinct prediction paths: one `output_file` applies to the whole dataset entry.
343
343
 
344
344
  Sparse depth does not require segmentation GT. `gt.segmentation` and
345
345
  `gt.semantic_segmentation` are optional aliases for the same sky-mask source.
@@ -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 but are written to a distinct file — `points3d_eval.json` beside the prediction, or `<output_file>_points3d.json` when `output_file` is set — so they do not overwrite the sparse-depth `eval.json`. Only the `point_error`, `error_decomposition`, and (directed) `cloud_distance` categories are present; 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.
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 (directed) `cloud_distance` categories are present; 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
 
@@ -301,7 +301,7 @@ When the GT is a sparse pointcloud, the evaluator also produces **3D (`points_3d
301
301
  - a **predicted point map** (`datasets[].points_3d`, an `(H,W,3)` map) — evaluated *directly* against the sparse GT cloud, with the 3D **similarity** gauge selected by `--points-3d-alignment`; or
302
302
  - a **dense depth map** (`datasets[].depth`/`relative_depth`/`affine_depth`) — unprojected with `gt.intrinsics` into a point map, with the depth **affine** gauge selected by `--depth-alignment`.
303
303
 
304
- If a dataset entry provides both, the predicted `points_3d` map is preferred (the depth is still scored by the pointwise sparse-depth metrics). Either way the sparse GT cloud is projected into the prediction plane, and the 3D results are written to a separate `points3d_eval.json` (or `<output_file>_points3d.json`) so they never clobber the sparse-depth `eval.json`.
304
+ If a dataset entry provides both, the predicted `points_3d` map is preferred (the depth is still scored by the pointwise sparse-depth metrics). Either way the sparse GT cloud is projected into the prediction plane, and the 3D results are written as `eval.json` in the selected prediction modality path (or to the configured `output_file`). Give simultaneous evaluations distinct prediction paths: one `output_file` applies to the whole dataset entry.
305
305
 
306
306
  Sparse depth does not require segmentation GT. `gt.segmentation` and
307
307
  `gt.semantic_segmentation` are optional aliases for the same sky-mask source.
@@ -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 but are written to a distinct file — `points3d_eval.json` beside the prediction, or `<output_file>_points3d.json` when `output_file` is set — so they do not overwrite the sparse-depth `eval.json`. Only the `point_error`, `error_decomposition`, and (directed) `cloud_distance` categories are present; 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.
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 (directed) `cloud_distance` categories are present; 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
 
@@ -978,9 +978,6 @@ def save_results(
978
978
  results: dict,
979
979
  dataset_config: dict,
980
980
  modality: str | None = None,
981
- *,
982
- default_basename: str = "eval.json",
983
- output_file_suffix: str | None = None,
984
981
  ) -> Path:
985
982
  """Save results to output file.
986
983
 
@@ -994,14 +991,6 @@ def save_results(
994
991
  modality: When set, save to this specific modality's path
995
992
  (e.g. ``"depth"`` or ``"rgb"``). Falls back to the first
996
993
  available modality path when *None*.
997
- default_basename: File name used when no explicit ``output_file`` is
998
- configured (default ``"eval.json"``). Lets a second modality that
999
- shares a prediction path (e.g. sparse ``points_3d`` alongside sparse
1000
- ``depth``) write to a distinct file instead of clobbering it.
1001
- output_file_suffix: When an explicit ``output_file`` is configured, this
1002
- suffix is inserted before its extension (e.g. ``"_points3d"`` turns
1003
- ``model_a.json`` into ``model_a_points3d.json``) so multiple
1004
- modalities can honour a single configured path without collision.
1005
994
 
1006
995
  Returns:
1007
996
  Path where results were saved.
@@ -1021,7 +1010,7 @@ def save_results(
1021
1010
  dataset_config[selected_modality]["path"],
1022
1011
  split=dataset_config[selected_modality].get("split"),
1023
1012
  )
1024
- / default_basename
1013
+ / "eval.json"
1025
1014
  )
1026
1015
  else:
1027
1016
  # Default: save alongside first available modality path
@@ -1032,17 +1021,13 @@ def save_results(
1032
1021
  dataset_config[mod]["path"],
1033
1022
  split=dataset_config[mod].get("split"),
1034
1023
  )
1035
- / default_basename
1024
+ / "eval.json"
1036
1025
  )
1037
1026
  break
1038
1027
  if output_file is None:
1039
- output_file = Path(default_basename)
1028
+ output_file = Path("eval.json")
1040
1029
  else:
1041
1030
  output_file = Path(output_file)
1042
- if output_file_suffix:
1043
- output_file = output_file.with_name(
1044
- f"{output_file.stem}{output_file_suffix}{output_file.suffix}"
1045
- )
1046
1031
 
1047
1032
  zip_path, internal_name = _find_zip_ancestor(output_file)
1048
1033
  if zip_path is not None and internal_name:
@@ -2360,14 +2345,10 @@ def main():
2360
2345
  )
2361
2346
  print(f"\n Points-3D results saved to: {points_3d_out}")
2362
2347
  if points_3d_sparse_save:
2363
- # Distinct file so it never clobbers the sparse-depth eval.json that
2364
- # shares the same dense-depth prediction path / output_file.
2365
2348
  points_3d_sparse_out = save_results(
2366
2349
  points_3d_sparse_save,
2367
2350
  dataset_config,
2368
2351
  modality="points_3d",
2369
- default_basename="points3d_eval.json",
2370
- output_file_suffix="_points3d",
2371
2352
  )
2372
2353
  print(
2373
2354
  f"\n Points-3D (sparse GT) results saved to: {points_3d_sparse_out}"
@@ -3829,6 +3829,15 @@ def evaluate_points_3d_sparse_samples(
3829
3829
  pred_ok_native = pred_ok_native & sky_valid
3830
3830
  pred_ok_metric = pred_ok_metric & sky_valid
3831
3831
 
3832
+ if sanity_checker is not None:
3833
+ # Keep the sanity-check sample accounting consistent with
3834
+ # the dense points_3d evaluator. Metric validation below
3835
+ # may add warnings, but sample counting belongs to input
3836
+ # validation so each evaluated pair is counted once.
3837
+ sanity_checker.validate_points_3d_input(
3838
+ pred_map_native, gt_point_map, entry_id
3839
+ )
3840
+
3832
3841
  # Native correspondence mask drives the shared angular metrics.
3833
3842
  native_corr = gt_valid & pred_ok_native
3834
3843
  total_evaluated_points += int(native_corr.sum())
@@ -1119,7 +1119,10 @@ class SanityChecker:
1119
1119
  print("-" * 70)
1120
1120
 
1121
1121
  for key, info in depth_report["warnings_by_type"].items():
1122
- ratio = info["count"] / depth_report["total_samples"]
1122
+ ratio = (
1123
+ info["count"] / depth_report["total_samples"]
1124
+ if depth_report["total_samples"] > 0 else 0
1125
+ )
1123
1126
  print(f"\n {info['metric']} - {info['type']}:")
1124
1127
  print(f" Occurrences: {info['count']}/{depth_report['total_samples']} ({ratio*100:.1f}%)")
1125
1128
  print(f" {info['message']}")
@@ -1134,7 +1137,10 @@ class SanityChecker:
1134
1137
  print("-" * 70)
1135
1138
 
1136
1139
  for key, info in rgb_report["warnings_by_type"].items():
1137
- ratio = info["count"] / rgb_report["total_samples"]
1140
+ ratio = (
1141
+ info["count"] / rgb_report["total_samples"]
1142
+ if rgb_report["total_samples"] > 0 else 0
1143
+ )
1138
1144
  print(f"\n {info['metric']} - {info['type']}:")
1139
1145
  print(f" Occurrences: {info['count']}/{rgb_report['total_samples']} ({ratio*100:.1f}%)")
1140
1146
  print(f" {info['message']}")
@@ -1149,7 +1155,10 @@ class SanityChecker:
1149
1155
  print("-" * 70)
1150
1156
 
1151
1157
  for key, info in rays_report["warnings_by_type"].items():
1152
- ratio = info["count"] / rays_report["total_samples"]
1158
+ ratio = (
1159
+ info["count"] / rays_report["total_samples"]
1160
+ if rays_report["total_samples"] > 0 else 0
1161
+ )
1153
1162
  print(f"\n {info['metric']} - {info['type']}:")
1154
1163
  print(f" Occurrences: {info['count']}/{rays_report['total_samples']} ({ratio*100:.1f}%)")
1155
1164
  print(f" {info['message']}")
@@ -1164,7 +1173,10 @@ class SanityChecker:
1164
1173
  print("-" * 70)
1165
1174
 
1166
1175
  for key, info in points_3d_report["warnings_by_type"].items():
1167
- ratio = info["count"] / points_3d_report["total_samples"]
1176
+ ratio = (
1177
+ info["count"] / points_3d_report["total_samples"]
1178
+ if points_3d_report["total_samples"] > 0 else 0
1179
+ )
1168
1180
  print(f"\n {info['metric']} - {info['type']}:")
1169
1181
  print(f" Occurrences: {info['count']}/{points_3d_report['total_samples']} ({ratio*100:.1f}%)")
1170
1182
  print(f" {info['message']}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-eval
3
- Version: 2.17.0
3
+ Version: 2.20.0
4
4
  Summary: Depth map evaluation toolkit with comprehensive metrics
5
5
  Author: Depth Eval Contributors
6
6
  License: MIT
@@ -339,7 +339,7 @@ When the GT is a sparse pointcloud, the evaluator also produces **3D (`points_3d
339
339
  - a **predicted point map** (`datasets[].points_3d`, an `(H,W,3)` map) — evaluated *directly* against the sparse GT cloud, with the 3D **similarity** gauge selected by `--points-3d-alignment`; or
340
340
  - a **dense depth map** (`datasets[].depth`/`relative_depth`/`affine_depth`) — unprojected with `gt.intrinsics` into a point map, with the depth **affine** gauge selected by `--depth-alignment`.
341
341
 
342
- If a dataset entry provides both, the predicted `points_3d` map is preferred (the depth is still scored by the pointwise sparse-depth metrics). Either way the sparse GT cloud is projected into the prediction plane, and the 3D results are written to a separate `points3d_eval.json` (or `<output_file>_points3d.json`) so they never clobber the sparse-depth `eval.json`.
342
+ If a dataset entry provides both, the predicted `points_3d` map is preferred (the depth is still scored by the pointwise sparse-depth metrics). Either way the sparse GT cloud is projected into the prediction plane, and the 3D results are written as `eval.json` in the selected prediction modality path (or to the configured `output_file`). Give simultaneous evaluations distinct prediction paths: one `output_file` applies to the whole dataset entry.
343
343
 
344
344
  Sparse depth does not require segmentation GT. `gt.segmentation` and
345
345
  `gt.semantic_segmentation` are optional aliases for the same sky-mask source.
@@ -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 but are written to a distinct file — `points3d_eval.json` beside the prediction, or `<output_file>_points3d.json` when `output_file` is set — so they do not overwrite the sparse-depth `eval.json`. Only the `point_error`, `error_decomposition`, and (directed) `cloud_distance` categories are present; 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.
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 (directed) `cloud_distance` categories are present; 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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "euler-eval"
7
- version = "2.17.0"
7
+ version = "2.20.0"
8
8
  description = "Depth map evaluation toolkit with comprehensive metrics"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -485,6 +485,15 @@ class TestSanity:
485
485
  assert "camera_dominated_error" in types
486
486
  assert "rho_a_too_low" in types
487
487
 
488
+ def test_report_handles_metric_warning_without_counted_input(self, capsys):
489
+ sc = self._checker()
490
+ sc.validate_points_3d_metrics(lateral_fraction=0.95, file_id="f0")
491
+
492
+ sc.print_report()
493
+
494
+ output = capsys.readouterr().out
495
+ assert "Occurrences: 1/0 (0.0%)" in output
496
+
488
497
  def test_full_report_includes_points_3d(self):
489
498
  sc = self._checker()
490
499
  assert "points_3d" in sc.get_full_report()
@@ -7,6 +7,9 @@ prediction and scores it as a 3D point map against a sparse LiDAR cloud
7
7
  (``points_3d_metrics_proposal.md`` §4-D).
8
8
  """
9
9
 
10
+ import json
11
+ import zipfile
12
+
10
13
  import numpy as np
11
14
  import pytest
12
15
 
@@ -17,6 +20,7 @@ from euler_eval.data import (
17
20
  )
18
21
  from euler_eval.evaluate import evaluate_points_3d_sparse_samples
19
22
  from euler_eval.metrics import compute_sparse_cloud_distance_metrics
23
+ from euler_eval.sanity_checker import SanityChecker
20
24
 
21
25
 
22
26
  def _rot_z(theta):
@@ -88,6 +92,21 @@ def _perfect_dataset(seed=0, **kwargs):
88
92
  return _SparsePointDataset(cloud, depth, K, **kwargs), depth, K, cloud
89
93
 
90
94
 
95
+ def test_sparse_evaluation_counts_sanity_check_samples():
96
+ dataset, _, _, _ = _perfect_dataset()
97
+ sanity_checker = SanityChecker()
98
+
99
+ evaluate_points_3d_sparse_samples(
100
+ dataset,
101
+ pred_is_radial=False,
102
+ num_workers=0,
103
+ sanity_checker=sanity_checker,
104
+ alignment_mode="none",
105
+ )
106
+
107
+ assert sanity_checker.get_points_3d_report()["total_samples"] == 1
108
+
109
+
91
110
  # ---------------------------------------------------------------------------
92
111
  # Directed (sparse) cloud distance
93
112
  # ---------------------------------------------------------------------------
@@ -440,39 +459,44 @@ class TestEvaluateNativePoints:
440
459
 
441
460
 
442
461
  # ---------------------------------------------------------------------------
443
- # Save wiring (distinct output file)
462
+ # Save wiring
444
463
  # ---------------------------------------------------------------------------
445
464
 
446
465
 
447
466
  class TestSaveWiring:
448
- def test_default_basename_and_suffix(self, tmp_path):
467
+ def test_points_3d_uses_eval_json_in_its_modality_path(self, tmp_path):
449
468
  from euler_eval.cli import save_results
450
469
 
451
470
  pred_dir = tmp_path / "pred"
452
471
  pred_dir.mkdir()
453
- cfg = {"name": "m", "depth": {"path": str(pred_dir)}}
472
+ cfg = {"name": "m", "points_3d": {"path": str(pred_dir)}}
454
473
 
455
- # Sparse-depth writes eval.json; sparse-points_3d writes points3d_eval.json.
456
- depth_out = save_results({"a": 1}, cfg, modality="depth")
457
474
  p3d_out = save_results(
458
- {"b": 2}, cfg, modality="points_3d",
459
- default_basename="points3d_eval.json",
475
+ {"points3d": {"eval": {}}}, cfg, modality="points_3d"
460
476
  )
461
- assert depth_out.name == "eval.json"
462
- assert p3d_out.name == "points3d_eval.json"
463
- assert depth_out != p3d_out
464
- assert depth_out.exists() and p3d_out.exists()
465
477
 
466
- def test_output_file_suffix(self, tmp_path):
478
+ assert p3d_out == pred_dir / "eval.json"
479
+ assert p3d_out.exists()
480
+ assert not (pred_dir / "points3d_eval.json").exists()
481
+ assert not (pred_dir / "points_3d_eval.json").exists()
482
+
483
+ def test_points_3d_writes_eval_json_into_zip(self, tmp_path):
467
484
  from euler_eval.cli import save_results
468
485
 
469
- out = tmp_path / "model_a.json"
470
- cfg = {"name": "m", "output_file": str(out),
471
- "depth": {"path": str(tmp_path)}}
472
- p3d_out = save_results({"b": 2}, cfg, modality="points_3d",
473
- output_file_suffix="_points3d")
474
- assert p3d_out.name == "model_a_points3d.json"
475
- assert p3d_out != out
486
+ archive = tmp_path / "predictions.zip"
487
+ with zipfile.ZipFile(archive, "w") as zf:
488
+ zf.writestr("points/frame_000.npy", b"points")
489
+
490
+ results = {"points3d": {"eval": {"native": {}}}}
491
+ cfg = {"name": "m", "points_3d": {"path": str(archive)}}
492
+ p3d_out = save_results(results, cfg, modality="points_3d")
493
+
494
+ assert p3d_out == archive / "eval.json"
495
+ with zipfile.ZipFile(archive) as zf:
496
+ assert "eval.json" in zf.namelist()
497
+ assert "points3d_eval.json" not in zf.namelist()
498
+ assert "points_3d_eval.json" not in zf.namelist()
499
+ assert json.loads(zf.read("eval.json")) == results
476
500
 
477
501
 
478
502
  # ---------------------------------------------------------------------------
File without changes
File without changes