euler-eval 2.29.0__tar.gz → 2.31.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 (110) hide show
  1. {euler_eval-2.29.0/euler_eval.egg-info → euler_eval-2.31.0}/PKG-INFO +4 -3
  2. {euler_eval-2.29.0 → euler_eval-2.31.0}/README.md +2 -1
  3. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/README.md +6 -0
  4. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/alignment.md +2 -1
  5. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/cli.md +16 -0
  6. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/configuration.md +77 -14
  7. euler_eval-2.31.0/docs/feature-request-unified-evaluation-framework.md +381 -0
  8. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/metrics.md +82 -0
  9. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/output.md +59 -0
  10. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/cli.py +430 -101
  11. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/config_paths.py +78 -2
  12. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/data.py +72 -46
  13. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/evaluate.py +407 -10
  14. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/__init__.py +18 -0
  15. euler_eval-2.31.0/euler_eval/metrics/sky_iou.py +167 -0
  16. {euler_eval-2.29.0 → euler_eval-2.31.0/euler_eval.egg-info}/PKG-INFO +4 -3
  17. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval.egg-info/SOURCES.txt +6 -0
  18. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval.egg-info/requires.txt +1 -1
  19. {euler_eval-2.29.0 → euler_eval-2.31.0}/pyproject.toml +2 -2
  20. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/conftest.py +2 -2
  21. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_config.py +148 -0
  22. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_data_builders.py +10 -5
  23. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_depth_alignment_output.py +20 -0
  24. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_evaluate_helpers.py +48 -6
  25. euler_eval-2.31.0/tests/test_mask_sky_pairing.py +197 -0
  26. euler_eval-2.31.0/tests/test_mask_sky_prediction.py +239 -0
  27. euler_eval-2.31.0/tests/test_sky_iou.py +550 -0
  28. euler_eval-2.31.0/tests/test_sky_iou_cli.py +497 -0
  29. {euler_eval-2.29.0 → euler_eval-2.31.0}/LICENSE +0 -0
  30. {euler_eval-2.29.0 → euler_eval-2.31.0}/MANIFEST.in +0 -0
  31. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/distributions.md +0 -0
  32. {euler_eval-2.29.0 → euler_eval-2.31.0}/docs/validation.md +0 -0
  33. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/THIRD_PARTY_NOTICES.md +0 -0
  34. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/__init__.py +0 -0
  35. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/calibration.py +0 -0
  36. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/distribution_contract.py +0 -0
  37. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/distributions.py +0 -0
  38. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metric_sets.py +0 -0
  39. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/_natural_scene_models.py +0 -0
  40. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/absrel.py +0 -0
  41. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/daniel_error.py +0 -0
  42. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/depth_binned_error.py +0 -0
  43. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/depth_edge_f1.py +0 -0
  44. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/depth_standard.py +0 -0
  45. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/fade.py +0 -0
  46. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/fid_kid.py +0 -0
  47. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/gpu_depth_batch.py +0 -0
  48. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/gpu_image_batch.py +0 -0
  49. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/high_freq_energy.py +0 -0
  50. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/lpips_metric.py +0 -0
  51. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/niqe.py +0 -0
  52. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/normal_consistency.py +0 -0
  53. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/points3d_cloud.py +0 -0
  54. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/points3d_decomposition.py +0 -0
  55. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/points3d_distance.py +0 -0
  56. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/points3d_geometry.py +0 -0
  57. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/psnr.py +0 -0
  58. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/rgb_edge_f1.py +0 -0
  59. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/rgb_lpips.py +0 -0
  60. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/rgb_psnr_ssim.py +0 -0
  61. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/rho_a.py +0 -0
  62. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/rmse.py +0 -0
  63. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/scale_invariant_log.py +0 -0
  64. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/ssim.py +0 -0
  65. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/tail_errors.py +0 -0
  66. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/metrics/utils.py +0 -0
  67. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/sanity_checker.py +0 -0
  68. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/schemas/distribution-v1.schema.json +0 -0
  69. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/utils/hierarchy_parser.py +0 -0
  70. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval/validation.py +0 -0
  71. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval.egg-info/dependency_links.txt +0 -0
  72. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval.egg-info/entry_points.txt +0 -0
  73. {euler_eval-2.29.0 → euler_eval-2.31.0}/euler_eval.egg-info/top_level.txt +0 -0
  74. {euler_eval-2.29.0 → euler_eval-2.31.0}/init_cache.py +0 -0
  75. {euler_eval-2.29.0 → euler_eval-2.31.0}/scripts/generate_distribution_fixtures.py +0 -0
  76. {euler_eval-2.29.0 → euler_eval-2.31.0}/setup.cfg +0 -0
  77. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/__init__.py +0 -0
  78. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/distribution_contract_utils.py +0 -0
  79. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/README.md +0 -0
  80. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/counts-depth.eval.json +0 -0
  81. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/counts-points3d.eval.json +0 -0
  82. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/counts-sparsedepth.eval.json +0 -0
  83. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/future-sum-by-depth.eval.json +0 -0
  84. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/histogram-only.eval.json +0 -0
  85. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/per-file-only.eval.json +0 -0
  86. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/fixtures/distributions/scalar-only.eval.json +0 -0
  87. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/mock_files/calibration_output.json +0 -0
  88. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/mock_files/depth_output.json +0 -0
  89. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/mock_files/rgb_output.json +0 -0
  90. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/mock_files/segmentation_output.json +0 -0
  91. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_alignment.py +0 -0
  92. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_cli_device.py +0 -0
  93. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_cli_sparse_outputs.py +0 -0
  94. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_data.py +0 -0
  95. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_depth_standard.py +0 -0
  96. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_distribution_contract.py +0 -0
  97. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_distributions.py +0 -0
  98. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_domain_metrics.py +0 -0
  99. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_init_cache.py +0 -0
  100. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_integration.py +0 -0
  101. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_meta_output.py +0 -0
  102. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_metrics_utils.py +0 -0
  103. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_normal_consistency.py +0 -0
  104. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_points_3d.py +0 -0
  105. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_points_3d_sparse.py +0 -0
  106. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_rgb_fid_output.py +0 -0
  107. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_rho_a.py +0 -0
  108. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_save_results.py +0 -0
  109. {euler_eval-2.29.0 → euler_eval-2.31.0}/tests/test_sparse_depth.py +0 -0
  110. {euler_eval-2.29.0 → euler_eval-2.31.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.29.0
3
+ Version: 2.31.0
4
4
  Summary: Evaluation toolkit for depth, RGB, ray and 3D point-map predictions
5
5
  Author-email: Daniel Rothenpieler <rothenpielerdaniel@gmail.com>
6
6
  License: MIT
@@ -33,7 +33,7 @@ Requires-Dist: opencv-python-headless>=4.5.0
33
33
  Requires-Dist: lpips>=0.1.4
34
34
  Requires-Dist: torchmetrics>=1.0
35
35
  Requires-Dist: tqdm>=4.62.0
36
- Requires-Dist: euler-loading>=2.15
36
+ Requires-Dist: euler-loading>=2.29
37
37
  Requires-Dist: ds-crawler>=2.10
38
38
  Requires-Dist: euler-metric-naming>=0.2
39
39
  Provides-Extra: logging
@@ -176,7 +176,8 @@ metric is written under, is in [Metrics](docs/metrics.md).
176
176
  | **Metadata-driven decoding** | Radial vs planar depth, value ranges, point-cloud column layout and loader choice all come from the dataset index — no per-dataset flags. |
177
177
  | **Honest spaces** | Relative models are scored `native` *and* `metric` (after scale/shift or a similarity gauge), so a scale failure never reads as a geometry failure. |
178
178
  | **Benchmark depth bins** | `--benchmark-depth-range MIN MAX` adds near/mid/far bins in square-root depth space, additive to the regular metrics. |
179
- | **Sky masking** | `--mask-sky` drops sky pixels using GT segmentation — from the metrics *and* from the alignment fit. |
179
+ | **Sky masking** | `--mask-sky` drops sky pixels using GT segmentation — from the metrics *and* from the alignment fit. `--mask-sky-prediction` does the same with each prediction's own `sky_mask`. |
180
+ | **Sky IoU** | `--sky-iou` scores predicted sky — a `sky_mask`, or depth reaching a fixed cap — against GT segmentation, without changing any depth metric. |
180
181
  | **Per-file + aggregate** | Dataset-level numbers plus per-image metrics in dataset-hierarchy order, in the same file. |
181
182
  | **Sanity checks** | Results are validated against configurable thresholds; implausible ranges, degenerate inputs and scale mismatches are reported instead of quietly scored. |
182
183
  | **Structured metric names** | Each result carries a `metricSet` envelope declaring its namespace, axes, units and metric directions. |
@@ -126,7 +126,8 @@ metric is written under, is in [Metrics](docs/metrics.md).
126
126
  | **Metadata-driven decoding** | Radial vs planar depth, value ranges, point-cloud column layout and loader choice all come from the dataset index — no per-dataset flags. |
127
127
  | **Honest spaces** | Relative models are scored `native` *and* `metric` (after scale/shift or a similarity gauge), so a scale failure never reads as a geometry failure. |
128
128
  | **Benchmark depth bins** | `--benchmark-depth-range MIN MAX` adds near/mid/far bins in square-root depth space, additive to the regular metrics. |
129
- | **Sky masking** | `--mask-sky` drops sky pixels using GT segmentation — from the metrics *and* from the alignment fit. |
129
+ | **Sky masking** | `--mask-sky` drops sky pixels using GT segmentation — from the metrics *and* from the alignment fit. `--mask-sky-prediction` does the same with each prediction's own `sky_mask`. |
130
+ | **Sky IoU** | `--sky-iou` scores predicted sky — a `sky_mask`, or depth reaching a fixed cap — against GT segmentation, without changing any depth metric. |
130
131
  | **Per-file + aggregate** | Dataset-level numbers plus per-image metrics in dataset-hierarchy order, in the same file. |
131
132
  | **Sanity checks** | Results are validated against configurable thresholds; implausible ranges, degenerate inputs and scale mismatches are reported instead of quietly scored. |
132
133
  | **Structured metric names** | Each result carries a `metricSet` envelope declaring its namespace, axes, units and metric directions. |
@@ -10,6 +10,12 @@
10
10
  | [Results & output](output.md) | Where files land, the anatomy of `eval.json`, per-file metrics, the sanity-check report |
11
11
  | [In-training validation](validation.md) | The programmatic API for scoring in-memory predictions during training |
12
12
 
13
+ ## Proposed features
14
+
15
+ - [Unified evaluation framework](feature-request-unified-evaluation-framework.md):
16
+ a shared evaluation lifecycle with explicit modality-specific preparation,
17
+ metric dependencies, populations, aggregation and output compatibility.
18
+
13
19
  ## Concepts in one page
14
20
 
15
21
  **A dataset is an indexed root, not a folder of files.** Every path in the
@@ -32,7 +32,8 @@ map `d_aligned = s · d_pred + t` by least squares over valid pixels.
32
32
 
33
33
  The fit uses pixels where GT and prediction are finite and GT > 0. With
34
34
  `--mask-sky`, sky pixels are excluded and the fit is trimmed to `gt ≤ P95(gt)`
35
- so residual sky outliers do not dominate.
35
+ so residual sky outliers do not dominate. Pixels in a prediction's `sky_mask`
36
+ are excluded from the fit with `--sky-depth` or `--mask-sky-prediction`.
36
37
 
37
38
  Declaring the prediction with the `relative_depth` or `affine_depth` config key
38
39
  (instead of `depth`) tells the evaluator the model is not metric, so
@@ -23,7 +23,9 @@ validation exits with status `1` before any dataset is opened.
23
23
  | `--skip-rays` | flag | off | Skip rays (spherical direction map) evaluation |
24
24
  | `--skip-points-3d` | flag | off | Skip points_3d (per-pixel 3D point map) evaluation |
25
25
  | `--mask-sky` | flag | off | Mask sky regions from metrics using GT segmentation |
26
+ | `--mask-sky-prediction` | flag | off | Exclude pixels in a prediction's `datasets[].sky_mask` from dense and sparse depth metrics and the alignment fit; predictions without a `sky_mask` are scored in full ([details](configuration.md#masking-predicted-sky)) |
26
27
  | `--sky-depth` | `float` | none | Cap GT and predicted depths in meters and fill predicted `sky_mask` pixels with this value; must be finite and positive |
28
+ | `--sky-iou` | flag | off | Score predicted sky against GT segmentation sky: IoU, precision and recall for the `explicit` (`datasets[].sky_mask`) and `implicit` (`depth >= --sky-depth`, metric predictions only) variants. Requires `gt.segmentation`; depth metrics are unchanged ([details](metrics.md#sky-segmentation)) |
27
29
  | `--no-sanity-check` | flag | off | Disable sanity checking of metric configurations |
28
30
  | `--metrics-config` | `str` | auto-detect | Path to `metrics_config.json` for sanity checking |
29
31
  | `--depth-alignment` | `{none,auto_affine,affine}` | `auto_affine` | Depth calibration mode ([details](alignment.md#depth-alignment)) |
@@ -54,9 +56,16 @@ euler-eval config.json --batch-size 32
54
56
  # Sky masking (requires gt.segmentation)
55
57
  euler-eval config.json --mask-sky -v
56
58
 
59
+ # Exclude each prediction's own sky (datasets[].sky_mask) from depth metrics
60
+ euler-eval config.json --mask-sky-prediction
61
+
57
62
  # Cap depth at 100 m and fill predicted sky at 100 m (optional datasets[].sky_mask)
58
63
  euler-eval config.json --sky-depth 100
59
64
 
65
+ # Sky IoU: explicit from datasets[].sky_mask, implicit at depth >= 100 m
66
+ # (requires gt.segmentation; the threshold is also the depth cap)
67
+ euler-eval config.json --sky-iou --sky-depth 100
68
+
60
69
  # Only depth evaluation
61
70
  euler-eval config.json --skip-rgb --skip-rays --skip-points-3d
62
71
 
@@ -109,6 +118,13 @@ per prediction dataset and modality with the GT and prediction paths, the
109
118
  resolved modality metadata (such as `radial_depth`), and the number of matched
110
119
  pairs.
111
120
 
121
+ With `--sky-iou`, each depth block ends with one line holding the pooled
122
+ (`pixel_pool`) sky IoU, precision and recall of every computed variant, for
123
+ example `Sky IoU, pixel_pool, 'model': explicit 0.9120 (precision 0.9500,
124
+ recall 0.9580); implicit (depth >= 100 m) 0.4410 (precision 0.9800, recall
125
+ 0.4450)`. Skipped variants, GT segmentation without sky and an empty implicit
126
+ sky are reported as warnings above it.
127
+
112
128
  A matched-pair count that is much lower than expected is the first sign that GT
113
129
  and predictions are not indexed consistently — see
114
130
  [Loader resolution & dataset metadata](configuration.md#loader-resolution--dataset-metadata).
@@ -71,7 +71,8 @@ required.
71
71
  | `gt.sparse_depth.path` | no\* | Sparse pointcloud GT, `(N,C)` points whose first columns are `x,y,z` in metres |
72
72
  | `gt.rays.path` | no\* | GT ray direction map dataset |
73
73
  | `gt.points_3d.path` | no\* | GT per-pixel 3D point map `(H,W,3)` in camera-frame metres |
74
- | `gt.segmentation.path` | no | GT segmentation (needed for `--mask-sky`) |
74
+ | `gt.segmentation.path` | no | GT segmentation (needed for `--mask-sky` and `--sky-iou`); see [GT segmentation](#gt-segmentation) |
75
+ | `gt.semantic_segmentation.path` | no | Alias of `gt.segmentation`; configure only one of the two |
75
76
  | `gt.calibration.path` | no | Calibration data (camera intrinsics); enables true camera-frame [surface normals](metrics.md#surface-normals-and-calibration) |
76
77
  | `gt.intrinsics.path` | with `gt.sparse_depth` | Camera intrinsics for pointcloud projection |
77
78
  | `gt.camera_extrinsics.path` | with `gt.sparse_depth` | Source-to-camera extrinsics (e.g. MUSES `lidar2rgb`) |
@@ -80,6 +81,35 @@ required.
80
81
 
81
82
  \* At least one of the five modality entries must be present.
82
83
 
84
+ ### GT segmentation
85
+
86
+ `gt.segmentation` is the canonical key. `gt.semantic_segmentation` is accepted
87
+ as an alias, for archives whose segmentation scope carries that name (MUSES,
88
+ for example); configuring both keys is an error. The segmentation is read only
89
+ when `--mask-sky` or `--sky-iou` needs it. `--mask-sky` without it warns and
90
+ disables itself, while `--sky-iou` without it exits with an error.
91
+
92
+ The dataset head's loader module must expose a `sky_mask` function, and the
93
+ head must declare the sky label as `skyclass` (euler-loading 2.29 or later).
94
+ euler-eval contains no label IDs.
95
+
96
+ Segmentation is per frame, so it is paired with the evaluated frames by
97
+ hierarchy path and file ID, like depth: its index must use the same folder
98
+ structure and IDs as the frames it masks. A frame without its own segmentation
99
+ file is not evaluated, and the run prints how many pairs the segmentation
100
+ dropped. When segmentation is read only for `--sky-iou`, it must not change
101
+ the evaluated pairs, so a segmentation that lacks frames is not loaded; the run
102
+ warns and the sky IoU is skipped. A segmentation whose IDs match no frame
103
+ stops the run with euler-loading's "No common IDs" error and per-modality file
104
+ counts.
105
+
106
+ The config key is also the default metadata scope. If the path has no
107
+ `#scope=` selector, no root-level metadata, and no scope of that name, the
108
+ scope is chosen among the archive's scopes: the one whose name or
109
+ `dataset-head.json` modality key is `segmentation` or `semantic_segmentation`
110
+ (both names are candidates under either key), or the only scope. If several
111
+ scopes match, or none among several, the run stops and asks for `#scope=`.
112
+
83
113
  ## `datasets` section
84
114
 
85
115
  Each entry needs a `name` and at least one prediction modality. Use only **one**
@@ -94,7 +124,7 @@ would be ambiguous.
94
124
  | `depth.path` | no\* | Predicted dense metric depth; also used against sparse pointcloud GT |
95
125
  | `relative_depth.path` | no\* | Predicted dense relative depth (scale/shift alignment supported) |
96
126
  | `affine_depth.path` | no\* | Predicted dense affine depth (scale/shift alignment supported) |
97
- | `sky_mask.path` | no | Auxiliary predicted sky mask for a dense depth entry; used with `--sky-depth` |
127
+ | `sky_mask.path` | no | Auxiliary predicted sky mask for a dense depth entry; used with `--sky-depth`, `--mask-sky-prediction` and `--sky-iou` |
98
128
  | `rays.path` | no\* | Predicted ray direction map dataset |
99
129
  | `points_3d.path` | no\* | Predicted per-pixel 3D point map `(H,W,3)` |
100
130
  | `output_file` | no | Custom results path (default: `eval.json` in the first modality path) |
@@ -123,8 +153,8 @@ The sky mask is paired with GT and predicted depth by sample ID and hierarchy.
123
153
  It supports the same archive, split, and `#scope=` selectors as depth, defaulting
124
154
  to the `sky_mask` metadata scope. Its loader must return a boolean mask with
125
155
  **True meaning sky**. For example, `generic_dense_depth.sky_mask` uses the RGB
126
- sky color in the mask dataset's `meta.sky_mask` (`[R, G, B]`) and returns a
127
- `(1, H, W)` tensor. Masks at another resolution follow the depth grid's crop
156
+ sky color in the mask dataset's `meta.skyclass` (`[R, G, B]`; the older
157
+ `meta.sky_mask` key is deprecated) and returns a `(1, H, W)` tensor. Masks at another resolution follow the depth grid's crop
128
158
  rule or use nearest-neighbor resizing.
129
159
 
130
160
  `--sky-depth` applies to dense and sparse depth evaluation. GT and both native
@@ -139,9 +169,37 @@ contributes only at projected points. Predicted sky stays in the scores where
139
169
  GT is valid; `--mask-sky` separately excludes pixels using GT segmentation and
140
170
  can be combined with `--sky-depth`.
141
171
 
142
- Without `--sky-depth`, the predicted mask is not loaded and depth evaluation
143
- uses the usual uncapped values. The effective cap and mask source are recorded
144
- in `eval.json` under `meta.eval_params.sky_depth` and `meta.pred.sky_mask`.
172
+ Without `--sky-depth`, `--mask-sky-prediction` or `--sky-iou`, the predicted
173
+ mask is not loaded and depth evaluation uses the usual uncapped values.
174
+ `--sky-iou` loads the mask
175
+ even without `--sky-depth`, but only to score it against GT sky
176
+ ([Sky segmentation](metrics.md#sky-segmentation)); the depth fill and the
177
+ affine-fit exclusion stay tied to `--sky-depth` (and `--mask-sky-prediction`),
178
+ so depth values do not change.
179
+ Because the mask is paired by sample ID, a mask that lacks some depth frames
180
+ also drops them from the depth metrics; the run prints how many. When the mask
181
+ is loaded only for `--sky-iou`, such a mask is not loaded at all: the run
182
+ warns, keeps every depth pair and skips the explicit variant. The effective cap and mask
183
+ source are recorded in `eval.json` under `meta.eval_params.sky_depth` and
184
+ `meta.pred.sky_mask`.
185
+
186
+ ### Masking predicted sky
187
+
188
+ `--mask-sky-prediction` excludes the pixels of a prediction's `sky_mask` from
189
+ dense and sparse depth evaluation, as `--mask-sky` does for GT sky: from every
190
+ metric, benchmark bin and histogram, and from the scale/shift fit. For sparse
191
+ depth, lidar points that project into predicted sky are not scored. A dataset
192
+ without a `sky_mask` is scored in full and the run warns. The flag loads the
193
+ mask without `--sky-depth`, combines with `--mask-sky` (the union of both skies
194
+ is excluded) and with `--sky-depth` (the cap still applies to the remaining
195
+ pixels). It does not change `--sky-iou`, which scores the masks themselves.
196
+
197
+ The excluded pixels come from the evaluated model, so two models are scored on
198
+ different pixel sets, and a model can lower its error by calling hard pixels
199
+ sky. Each result records how much it excluded under `meta.pred_sky_masking`
200
+ ([Results & output](output.md#predicted-sky-masking)); compare models with
201
+ this flag only alongside that fraction, or prefer `--mask-sky`, whose GT sky is
202
+ the same for every model.
145
203
 
146
204
  ## Sparse depth (pointcloud GT)
147
205
 
@@ -162,8 +220,9 @@ depth and `points_3d`, each prediction modality gets its own `eval.json` at its
162
220
  own path.
163
221
 
164
222
  Sparse depth does not require segmentation GT. `gt.segmentation` is loaded only
165
- when `--mask-sky` is set, and then excludes sky pixels from projected-point
166
- metrics and from scale/shift fitting.
223
+ when `--mask-sky` or `--sky-iou` is set. `--mask-sky` excludes sky pixels from
224
+ projected-point metrics and from scale/shift fitting; `--sky-iou` scores the
225
+ dense prediction's sky against it without changing any depth metric.
167
226
 
168
227
  ```json
169
228
  {
@@ -239,11 +298,15 @@ The scope defaults to the configuration key name, so the common case is compact:
239
298
 
240
299
  Use `#scope=<scope>` when an artifact scope is not named exactly like the config
241
300
  key, e.g. `"sparse_depth": { "path": "/data/capture.zip:test#scope=lidar" }`.
242
-
243
- Regular modalities (`rgb`, `depth`, `rays`, `points_3d`, `sparse_depth`)
244
- participate in file-ID intersection; calibration and pose data (`intrinsics`,
245
- `calibration`, `camera_extrinsics`, `lidar_extrinsics`, segmentation) are loaded
246
- hierarchically and do not.
301
+ GT segmentation is the exception: under either of its keys it also finds a
302
+ scope named after the other key, as described in [GT segmentation](#gt-segmentation).
303
+
304
+ Regular modalities (`rgb`, `depth`, `rays`, `points_3d`, `sparse_depth`, GT
305
+ segmentation and a predicted `sky_mask`) participate in file-ID intersection;
306
+ calibration and pose data (`intrinsics`, `calibration`, `camera_extrinsics`,
307
+ `lidar_extrinsics`) are loaded hierarchically and do not. A hierarchical file
308
+ applies to every frame at or below its folder, which suits one calibration per
309
+ scene but not per-frame data.
247
310
 
248
311
  ## `metrics_config.json`
249
312
 
@@ -0,0 +1,381 @@
1
+ # Feature request: a unified evaluation framework
2
+
3
+ Status: proposed for a future release. This document specifies an architectural
4
+ change; it does not introduce a new API or change evaluation behavior.
5
+
6
+ ## Recommendation
7
+
8
+ Use a **modality-agnostic evaluation lifecycle with modality-aware preparation,
9
+ observations and metric definitions**. Represent each evaluation as an explicit
10
+ plan of typed steps and dependencies. The same plan should drive computation,
11
+ aggregation, explanations and output metadata.
12
+
13
+ Depth, RGB, rays and point clouds can share this lifecycle without sharing one
14
+ preprocessing function, validity mask or mathematical reduction. A metric may
15
+ consume several modalities, a whole image, two point sets, a group of frames or
16
+ dataset-wide features. Those requirements belong in its definition.
17
+
18
+ Start with ordinary Python data classes, protocols and a small dependency
19
+ executor. Keep the current CLI and library entry points as compatibility
20
+ wrappers while moving their computation into the shared framework.
21
+
22
+ ## Problem and motivation
23
+
24
+ Today, adding a core metric requires wiring computation and storage in
25
+ [`evaluate.py`](../euler_eval/evaluate.py), descriptions and paths in
26
+ [`cli.py`](../euler_eval/cli.py), and sometimes separate per-file and dataset
27
+ serialization logic. Deferred CPU/GPU batch results also patch mutable output
28
+ dictionaries. The distribution contract is attached through a later tree walk
29
+ in [`distribution_contract.py`](../euler_eval/distribution_contract.py).
30
+
31
+ This makes several questions difficult to answer from one place:
32
+
33
+ - Which transformed prediction and GT does a metric consume?
34
+ - Which observations are included, and when is that selection applied?
35
+ - Is a dataset result a mean of image scores, a pooled observation statistic,
36
+ or a computation over the dataset as a whole?
37
+ - Do a scalar and its associated distribution describe the same population?
38
+ - When is a deferred result complete, and which accumulators receive it?
39
+
40
+ The recent depth benchmark-range fix is a concrete example: the default
41
+ distribution needed its selection explicitly connected to the benchmark
42
+ population after sky-depth processing. Shared population definitions should
43
+ make that relationship part of the plan.
44
+
45
+ As a metric author, I should be able to declare inputs, computation, supported
46
+ reductions and result semantics once. As a user, I should be able to inspect
47
+ how any emitted result was produced. As a consumer, euler-view should receive
48
+ stable output with enough provenance to distinguish incompatible evaluations.
49
+
50
+ ## Unified flow
51
+
52
+ ```mermaid
53
+ flowchart TD
54
+ C[Configuration and selected metric sets] --> P[Build and validate evaluation plan]
55
+ M[Input metadata and available capabilities] --> P
56
+ P --> S[Resolve required inputs and sample or group identities]
57
+ S --> A[Prepare typed artifacts through declared adapter steps]
58
+ A --> V[Resolve views, populations and correspondences]
59
+ V --> K[Compute declared observations, scores or features]
60
+ K --> I[Completed item contributions]
61
+ I --> R[Reducers or retained dataset inputs]
62
+ R --> F[Finalize dataset or group computations]
63
+ K --> N[Optional dependent stages]
64
+ F --> N
65
+ N --> O[Typed result collection]
66
+ I --> O
67
+ F --> O
68
+ O --> E[Compatibility output adapter]
69
+ P -.-> X[Plan explanation and execution provenance]
70
+ O -.-> X
71
+ ```
72
+
73
+ This is a lifecycle overview. An individual plan is a directed acyclic graph:
74
+ preparation may itself require correspondence construction before alignment,
75
+ and later metrics may consume artifacts or results from earlier stages. The
76
+ diagram does not mandate that every metric executes every box.
77
+
78
+ For example, RGB plus GT depth can produce depth-binned photometric scores;
79
+ depth plus calibration can produce a point map; a future reconstruction stage
80
+ can consume several frames and produce a cloud for geometric evaluation.
81
+ Each dependency must be explicit, including its coordinate system and scope.
82
+
83
+ ## Responsibilities and contracts
84
+
85
+ The initial implementation should formalize these concepts. Names below are
86
+ design suggestions, not a promised public Python API.
87
+
88
+ | Concept | Required responsibility |
89
+ |---|---|
90
+ | `EvaluationPlan` and step definitions | Requested outputs, typed dependencies, execution scope, effective policies and implementation versions. Validate unsupported combinations and cycles before expensive work. |
91
+ | Artifact specification and reference | Payload kind, GT/prediction role, source identity, units, coordinate frame, spatial layout, transformation lineage and ownership. A reference may point to memory or managed storage. |
92
+ | View and population definitions | Evaluated representation, eligibility predicates, selection order, correspondence policy and observation unit. Record distinct source and target populations where needed. |
93
+ | `MetricSpec` | Required artifacts or observations, computation, validity requirements, parameters, supported scopes/reducers and semantic descriptions. Metric sets select these definitions. |
94
+ | `ReducerSpec` | Accepted contribution type, state, update/finalize operations, merge capability, weighting, precision and retention requirements. |
95
+ | Contribution and result records | Stable request/item identity, payload, status, counts and provenance references. Results are completed values; pending work belongs to execution state. |
96
+
97
+ The shared core owns planning, dependency resolution, resource lifetime,
98
+ batch completion, aggregation and result collection. It dispatches by declared
99
+ capabilities and contracts. Modality-specific adapters and metric kernels own
100
+ geometry, image preprocessing, feature extraction and domain-specific math.
101
+
102
+ Metric definitions should supply descriptions, units and allowed reductions to
103
+ the exporter. Adding a metric using existing capabilities should not require
104
+ new modality conditionals in the executor or manual CLI output-tree assembly.
105
+
106
+ ### Inputs and execution scope
107
+
108
+ Treat the required inputs as named roles, rather than assuming every operation
109
+ takes a `(prediction, GT)` array pair. Support declarations for:
110
+
111
+ - One item: paired images, predicted image alone, ray maps, or two point sets.
112
+ - A group: a scene, sequence, temporal window or another explicitly identified
113
+ collection, including any ordering requirement.
114
+ - A dataset: feature collections, accumulated statistics or other retained
115
+ inputs needed after the item stream completes.
116
+
117
+ An execution batch is a performance choice, not a statistical grouping. A
118
+ batch size must not silently define a sample, population or dataset reducer.
119
+
120
+ Each request identifies its matching/join policy and required modalities. A
121
+ missing optional modality should affect the requests that need it; it should
122
+ not silently shrink unrelated evaluation populations. Preserve existing
123
+ matching behavior during migration, and make any change to that behavior an
124
+ explicit, separately tested semantic change.
125
+
126
+ NIQE/FADE kernels require predictions only, although today's RGB evaluation
127
+ workflow pairs GT and predictions. Express that kernel requirement now;
128
+ enabling a standalone prediction-only CLI workflow can be a separate feature.
129
+
130
+ ### Artifacts, views and populations
131
+
132
+ An artifact must carry enough information to reject invalid combinations:
133
+ metres versus unspecified relative values, incompatible coordinate frames,
134
+ unaligned image grids, or sparse samples supplied to dense-neighbourhood
135
+ metrics. Calibration assumptions and inferred input spaces must be recorded.
136
+
137
+ A population is a named selection over a declared observation domain. It need
138
+ not be a dense Boolean mask: it may be projected correspondences, valid
139
+ directions, selected images, or a directed set of nearest-neighbour queries.
140
+ Two populations with the same display name are not automatically compatible.
141
+
142
+ Separate fitting and scoring populations. For depth, the current intended
143
+ order must remain explicit:
144
+
145
+ 1. Spatial preparation, calibration handling and planar/radial conversion.
146
+ 2. Alignment fitting on its declared fitting population, where requested.
147
+ 3. GT/prediction sky-depth capping and predicted-sky filling under the existing
148
+ policy, with GT sky exclusion recorded separately.
149
+ 4. Scoring validity and benchmark membership on evaluated GT. The benchmark
150
+ `all` interval includes both endpoints.
151
+ 5. Scalar and distribution computations for the selected space/population.
152
+
153
+ An unrestricted scalar and a benchmark-filtered histogram may intentionally
154
+ coexist, as they currently do. Their internal identities must expose that
155
+ difference. Scalar and distribution requests for the *same* population must
156
+ consume the same selected observations.
157
+
158
+ Additional requirements such as positive inputs for logarithms or a valid
159
+ neighbourhood for normals belong to the relevant computation. Explicitly name
160
+ the resulting population or support policy. Likewise, RGB sky filling and
161
+ RGB pixel exclusion are different operations; the framework must preserve
162
+ that distinction and the current unfilled inputs used for NIQE/FADE.
163
+
164
+ ## Mapping current modalities into the framework
165
+
166
+ | Evaluation family | Preparation and dependencies | Computation and scope |
167
+ |---|---|---|
168
+ | Dense depth | Spatial alignment, intrinsics, radial/planar conversion, optional affine fitting, sky policy and benchmark populations | Corresponding-pixel errors and standard reducers; dense geometry; image scores; dataset feature metrics |
169
+ | Sparse depth | Point-cloud transforms, projection, visibility/collision policy, depth preparation and alignment | Errors at evaluated projected correspondences; dense-neighbourhood metrics remain unavailable |
170
+ | RGB | Image layout/range handling, spatial alignment and metric-specific sky policy | Pixel errors, local/image scores, prediction-only quality kernels and dataset FID |
171
+ | RGB with depth | RGB inputs plus explicitly registered GT depth and calibration | Photometric observations selected by GT depth; demonstrates an existing metric with multiple modalities |
172
+ | Rays | Direction preparation, spatial alignment, validity and resolved FoV policy | Angular observations, thresholds and accuracy curves; declared exact/approximate summaries |
173
+ | Dense points-3D | Point maps or unprojected depth, coordinate-frame handling and optional gauge fitting | Correspondence errors, radial/lateral decomposition, dense geometry and separate cloud comparisons |
174
+ | Sparse points-3D | Sparse cloud visibility and projection plus predicted points or the existing programmatic depth conversion | Correspondence errors and cloud queries with explicitly declared directions |
175
+
176
+ Preserve current entry-point eligibility: representing an existing library
177
+ conversion in the plan does not automatically enable an additional CLI result
178
+ family. For point-map metrics, also preserve computations intentionally made in
179
+ the native frame even when aligned results are available.
180
+
181
+ Correspondence error and cloud distance require different observations.
182
+ Nearest-neighbour cloud queries must declare their source, target, direction
183
+ and subsampling policy. Current sparse fixed-threshold diagnostics are
184
+ GT-to-prediction completeness/recall, while optional F-score AUC also needs the
185
+ reverse direction. Keep those requirements separate. Do not pool points from
186
+ unrelated frames before nearest-neighbour matching; a scene-level comparison
187
+ would require an explicit scene artifact and common coordinate frame.
188
+
189
+ The ray evaluator already uses a histogram approximation for its pooled
190
+ median. Migrating it must preserve and disclose that approximation instead of
191
+ implicitly promising an exact quantile.
192
+
193
+ ## Observations and aggregation
194
+
195
+ Share computations only when their full input, view, population and numerical
196
+ policies match. Reuse references or chunks where practical; the framework
197
+ must not require retaining all pixel errors in memory.
198
+
199
+ A scalar, histogram and curve are different outputs derived from declared
200
+ observations. A depth histogram associated with `rmse` bins absolute residuals;
201
+ the points-3D counterpart bins Euclidean correspondence errors. Neither is a
202
+ distribution of repeated estimates of dataset RMSE.
203
+
204
+ | Requested result | Contribution/state | Final computation |
205
+ |---|---|---|
206
+ | Pixel-pool depth RMSE | Squared-error sum and valid count | `sqrt(sum_sq / count)` |
207
+ | Mean per-image RMSE | Valid image RMSE sum and image count | Divide; preserve equal image weighting |
208
+ | Error-magnitude histogram | Fixed-bin counts | Elementwise sum |
209
+ | Error sum by GT-depth bin | Fixed-bin error sums | Elementwise sum |
210
+ | RMSE by GT-depth bin | Squared-error sums and counts for matching bins | Square root of each bin's mean squared error |
211
+ | Exact quantile | Retained values or another exact supported state | Quantile over the declared population |
212
+ | LPIPS dataset mean | Completed per-image scores and validity | Declared image-score reducer |
213
+ | FID | Compatible GT/prediction feature collections, or sufficient feature moments if supported by that backend | One dataset-level calculation after ingestion |
214
+ | KID | Feature collections plus declared subset selection/seed policy | Dataset-level estimator with its required retained state |
215
+
216
+ All reducers have `update` and `finalize`; only reducers declaring a compatible
217
+ merge operation may combine partial states. State may be sufficient statistics,
218
+ retained values or ordered/global inputs. Do not force all reducers into sums
219
+ or average finalized shard scores. In particular, per-shard FID values cannot
220
+ be averaged to obtain dataset FID. Backend-specific feature preprocessing,
221
+ model weights and covariance conventions are part of FID compatibility.
222
+
223
+ Define merge compatibility from the complete resolved semantics: observation,
224
+ units, view, population policy, reducer, bin edges/intervals, implementation
225
+ and estimator settings. Registry IDs and matching output paths alone are
226
+ insufficient. Check partition identity separately so overlapping inputs are
227
+ not double counted. Integer counts must remain exact; floating accumulation
228
+ and backend equivalence require declared tolerances.
229
+
230
+ For matching populations and fixed bins, dataset histogram counts must equal
231
+ the sum of item contributions. Benchmark subsets and canonical aliases must
232
+ not be accumulated again as additional observations. Exact RMSE continues to
233
+ use sufficient statistics, independently of histogram resolution.
234
+
235
+ ## Execution and later stages
236
+
237
+ Compile selected requests into explicit dependencies. Resolve metadata-based
238
+ choices early; leave data-dependent choices marked unresolved until the
239
+ required input is available, then record the decision and its policy. Reject
240
+ missing required capabilities and incompatible artifacts with useful messages.
241
+
242
+ CPU kernels, GPU batches and dataset computations should emit typed completed
243
+ contributions into the same coordinator. Completion is tracked per logical
244
+ request and item/group; each contribution reaches its reducer once. A failed
245
+ or retried batch must not duplicate counts. Exported dictionaries must not be
246
+ used as pending-result storage.
247
+
248
+ Define `ok`, `no_observations`, `skipped`, `unavailable` and `failed` outcomes
249
+ with reasons and relevant counts. Empty or failed measurements must not enter
250
+ means as zero. The plan declares whether a failure stops the run or leaves a
251
+ result unavailable, preserving existing failure behavior during migration.
252
+
253
+ Use bounded queues and explicit artifact ownership. Release intermediates
254
+ after their consumers finish; declare temporary-file/feature retention and
255
+ clean it up on completion or failure. Expensive optional metrics should only
256
+ load models and compute features when selected. Shared preparation should not
257
+ force an entire dataset or all modalities into memory.
258
+
259
+ Allow a later stage to consume an earlier artifact or typed result through a
260
+ declared dependency. Examples include frame fusion followed by cloud scoring,
261
+ temporal metrics over ordered frame groups, and summaries derived from
262
+ completed dataset results. Specify group membership, ordering, frame transforms
263
+ and missing-input behavior. These examples are extension requirements, not
264
+ requests to implement reconstruction or temporal metrics in this refactor.
265
+
266
+ Initially, a local dependency executor is sufficient. A distributed scheduler,
267
+ plugin marketplace and user-authored workflow language are outside this
268
+ feature's scope. Merge contracts prepare suitable computations for later
269
+ parallel execution without making distribution a prerequisite.
270
+
271
+ ## Transparency and output compatibility
272
+
273
+ Provide a proposed `--explain` mode and a programmatic plan description. They
274
+ should identify inputs, transforms, fitting/scoring populations, computation
275
+ dependencies, scope, reducer, approximation and requested output paths.
276
+ An explanation that does not evaluate data must mark inferred settings as
277
+ pending. Runtime provenance records resolved choices and contribution counts;
278
+ it should be possible to follow an output back to its plan definition without
279
+ dumping arrays or masks.
280
+
281
+ Use a structured internal result identity containing evaluation family, view,
282
+ population, measurement, reducer, scope/group and output kind, referencing the
283
+ resolved semantic definitions. A path is an export representation of that
284
+ identity. Canonical aliases reference existing results and perform no work.
285
+
286
+ Keep the current `eval.json` envelope, per-modality files, metric paths, aliases,
287
+ library return shapes, and distribution v1 leaves/registry during migration.
288
+ One serialization layer should translate typed results to those contracts.
289
+ Generate descriptions from metric definitions rather than rediscovering
290
+ semantics through dictionary names. Keep transport validation, including
291
+ JavaScript-safe integer counts and nonfinite-value handling, at that boundary.
292
+
293
+ Preserve existing `distributionPopulation` metadata. A later, separately
294
+ versioned provenance extension can describe per-result views and populations
295
+ for all output kinds, including scalar results. Distinguish evaluation
296
+ semantics versions from wire-schema versions: an internal refactor must not
297
+ silently change metric meaning, and a new meaning does not justify pretending
298
+ older results used the new policy.
299
+
300
+ euler-view should be able to ingest compatibility output without changes.
301
+ Future provenance can support richer explanations and comparison checks, but
302
+ this feature request contains no euler-view implementation work.
303
+
304
+ ## Delivery plan
305
+
306
+ 1. **Characterize existing behavior.** Inventory current metric definitions,
307
+ requirements, populations, reduction rules, aliases and exceptions. Capture
308
+ representative existing outputs and numerical behavior, including known
309
+ policy differences between CLI and training validation.
310
+ 2. **Introduce the contracts through depth.** Extract prepared observations,
311
+ explicit populations and standard/distribution reducers. Reuse the
312
+ sufficient-statistic foundation in
313
+ [`depth_standard.py`](../euler_eval/metrics/depth_standard.py) and the typed
314
+ sample/aggregator approach in [`validation.py`](../euler_eval/validation.py).
315
+ Move dense and sparse depth plus training validation onto the same core
316
+ where semantics match. Keep heavyweight optional metrics outside the
317
+ lightweight validation API.
318
+ 3. **Validate the design against different computations.** Migrate RGB image
319
+ and prediction-only kernels, LPIPS completion, dataset FID/KID paths where
320
+ supported, and RGB-with-depth metrics. This milestone must demonstrate
321
+ image, dataset and multiple-modality dependencies before treating the
322
+ abstraction as stable.
323
+ 4. **Migrate rays and points.** Preserve frame choices, directional cloud
324
+ comparisons, sparse eligibility and approximation policies. Share declared
325
+ preparation where compatible and retain specialized kernels.
326
+ 5. **Complete explanations and remove duplicate wiring.** Generate result
327
+ descriptions and compatibility output from definitions. Document how to
328
+ add a metric, adapter and reducer. Stabilize extension contracts only after
329
+ the current modalities exercise them.
330
+
331
+ Each milestone must leave the existing entry points usable. Numerical or
332
+ selection-policy corrections discovered during migration should be reviewed,
333
+ tested and release-noted separately from structural changes.
334
+
335
+ ## Acceptance criteria
336
+
337
+ - All current evaluation families use the same planning, contribution and
338
+ result lifecycle while preserving modality-specific preparation.
339
+ - A metric using existing artifact and reducer types can be added through its
340
+ kernel and declaration without editing the executor or CLI tree-building
341
+ logic. Its descriptions and supported output forms come from that definition.
342
+ - Plans cover a pixel metric, an image metric, a prediction-only kernel, a
343
+ dataset feature metric and a metric requiring RGB plus depth. A small test
344
+ stage can consume an upstream artifact/result without adding a scheduler
345
+ branch for that modality.
346
+ - Depth parity cases cover dense/sparse inputs, native/aligned spaces,
347
+ planar/radial conversion, benchmark boundaries, sky capping/filling,
348
+ excluded/invalid inputs and empty populations. Fitting and scoring
349
+ populations are independently inspectable.
350
+ - Scalars and distributions requesting the same population agree on selected
351
+ observations. Fixed-bin dataset counts equal summed item counts, and
352
+ aliases/subset summaries do not double count.
353
+ - Reducer tests distinguish pooled RMSE from mean image RMSE, verify compatible
354
+ partition merging, reject incompatible state merges and preserve exact or
355
+ approximate quantile policies. Dataset metrics are finalized at their
356
+ declared scope instead of averaging arbitrary batch/shard scores.
357
+ - Cloud tests preserve coordinate frames, correspondence/nearest-neighbour
358
+ distinctions, sparse directions and subsampling. RGB tests preserve filled
359
+ versus unfilled inputs and dependencies on optional depth.
360
+ - CPU and batched implementations agree within metric-specific tolerances;
361
+ integer histograms agree exactly. Completion-order tests detect lost or
362
+ duplicated contributions, including failures and empty results. Stochastic
363
+ estimator checks use declared seed/subset policies.
364
+ - Public CLI/library compatibility fixtures retain result paths, aliases,
365
+ values within the existing tolerances, metadata and distribution v1 schema
366
+ validity. Changes in eligibility, weights or semantics are explicit changes.
367
+ - Explanation output identifies effective policies and unresolved choices;
368
+ result provenance includes resolved dependencies, counts and outcome reasons.
369
+ - Streaming checks demonstrate bounded intermediate memory for streaming
370
+ reducers and cleanup of retained resources. Existing tests remain usable
371
+ without datasets, network access or a GPU; backend stubs can exercise
372
+ scheduling and feature contracts.
373
+
374
+ ## Design boundary
375
+
376
+ The framework should make the computation inspectable and extensible through
377
+ shared contracts. It should preserve specialized mathematical kernels and
378
+ make their differences visible. Its success is measured by fewer independent
379
+ places to wire a metric, demonstrable scalar/distribution population agreement,
380
+ and the ability to express the current non-pixel and dataset computations
381
+ within the same lifecycle.