euler-eval 2.28.0__tar.gz → 2.30.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 (105) hide show
  1. euler_eval-2.30.0/MANIFEST.in +3 -0
  2. {euler_eval-2.28.0 → euler_eval-2.30.0}/PKG-INFO +2 -1
  3. euler_eval-2.30.0/docs/README.md +58 -0
  4. euler_eval-2.30.0/docs/alignment.md +91 -0
  5. euler_eval-2.30.0/docs/cli.md +150 -0
  6. euler_eval-2.30.0/docs/configuration.md +269 -0
  7. euler_eval-2.30.0/docs/distributions.md +296 -0
  8. euler_eval-2.30.0/docs/feature-request-unified-evaluation-framework.md +381 -0
  9. euler_eval-2.30.0/docs/metrics.md +168 -0
  10. euler_eval-2.30.0/docs/output.md +160 -0
  11. euler_eval-2.30.0/docs/validation.md +109 -0
  12. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/cli.py +128 -16
  13. euler_eval-2.30.0/euler_eval/distribution_contract.py +183 -0
  14. euler_eval-2.30.0/euler_eval/distributions.py +167 -0
  15. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/evaluate.py +223 -25
  16. euler_eval-2.30.0/euler_eval/schemas/distribution-v1.schema.json +113 -0
  17. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval.egg-info/PKG-INFO +2 -1
  18. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval.egg-info/SOURCES.txt +32 -1
  19. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval.egg-info/requires.txt +1 -0
  20. {euler_eval-2.28.0 → euler_eval-2.30.0}/pyproject.toml +3 -2
  21. euler_eval-2.30.0/scripts/generate_distribution_fixtures.py +184 -0
  22. euler_eval-2.30.0/tests/__init__.py +0 -0
  23. euler_eval-2.30.0/tests/conftest.py +123 -0
  24. euler_eval-2.30.0/tests/distribution_contract_utils.py +40 -0
  25. euler_eval-2.30.0/tests/fixtures/distributions/README.md +42 -0
  26. euler_eval-2.30.0/tests/fixtures/distributions/counts-depth.eval.json +324 -0
  27. euler_eval-2.30.0/tests/fixtures/distributions/counts-points3d.eval.json +324 -0
  28. euler_eval-2.30.0/tests/fixtures/distributions/counts-sparsedepth.eval.json +322 -0
  29. euler_eval-2.30.0/tests/fixtures/distributions/future-sum-by-depth.eval.json +392 -0
  30. euler_eval-2.30.0/tests/fixtures/distributions/histogram-only.eval.json +157 -0
  31. euler_eval-2.30.0/tests/fixtures/distributions/per-file-only.eval.json +274 -0
  32. euler_eval-2.30.0/tests/fixtures/distributions/scalar-only.eval.json +74 -0
  33. euler_eval-2.30.0/tests/mock_files/calibration_output.json +19 -0
  34. euler_eval-2.30.0/tests/mock_files/depth_output.json +32 -0
  35. euler_eval-2.30.0/tests/mock_files/rgb_output.json +32 -0
  36. euler_eval-2.30.0/tests/mock_files/segmentation_output.json +32 -0
  37. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_depth_alignment_output.py +20 -0
  38. euler_eval-2.30.0/tests/test_distribution_contract.py +231 -0
  39. euler_eval-2.30.0/tests/test_distributions.py +764 -0
  40. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_evaluate_helpers.py +28 -0
  41. {euler_eval-2.28.0 → euler_eval-2.30.0}/LICENSE +0 -0
  42. {euler_eval-2.28.0 → euler_eval-2.30.0}/README.md +0 -0
  43. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/THIRD_PARTY_NOTICES.md +0 -0
  44. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/__init__.py +0 -0
  45. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/calibration.py +0 -0
  46. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/config_paths.py +0 -0
  47. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/data.py +0 -0
  48. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metric_sets.py +0 -0
  49. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/__init__.py +0 -0
  50. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/_natural_scene_models.py +0 -0
  51. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/absrel.py +0 -0
  52. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/daniel_error.py +0 -0
  53. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/depth_binned_error.py +0 -0
  54. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/depth_edge_f1.py +0 -0
  55. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/depth_standard.py +0 -0
  56. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/fade.py +0 -0
  57. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/fid_kid.py +0 -0
  58. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/gpu_depth_batch.py +0 -0
  59. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/gpu_image_batch.py +0 -0
  60. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/high_freq_energy.py +0 -0
  61. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/lpips_metric.py +0 -0
  62. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/niqe.py +0 -0
  63. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/normal_consistency.py +0 -0
  64. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/points3d_cloud.py +0 -0
  65. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/points3d_decomposition.py +0 -0
  66. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/points3d_distance.py +0 -0
  67. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/points3d_geometry.py +0 -0
  68. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/psnr.py +0 -0
  69. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/rgb_edge_f1.py +0 -0
  70. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/rgb_lpips.py +0 -0
  71. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/rgb_psnr_ssim.py +0 -0
  72. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/rho_a.py +0 -0
  73. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/rmse.py +0 -0
  74. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/scale_invariant_log.py +0 -0
  75. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/ssim.py +0 -0
  76. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/tail_errors.py +0 -0
  77. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/metrics/utils.py +0 -0
  78. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/sanity_checker.py +0 -0
  79. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/utils/hierarchy_parser.py +0 -0
  80. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval/validation.py +0 -0
  81. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval.egg-info/dependency_links.txt +0 -0
  82. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval.egg-info/entry_points.txt +0 -0
  83. {euler_eval-2.28.0 → euler_eval-2.30.0}/euler_eval.egg-info/top_level.txt +0 -0
  84. {euler_eval-2.28.0 → euler_eval-2.30.0}/init_cache.py +0 -0
  85. {euler_eval-2.28.0 → euler_eval-2.30.0}/setup.cfg +0 -0
  86. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_alignment.py +0 -0
  87. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_cli_device.py +0 -0
  88. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_cli_sparse_outputs.py +0 -0
  89. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_config.py +0 -0
  90. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_data.py +0 -0
  91. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_data_builders.py +0 -0
  92. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_depth_standard.py +0 -0
  93. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_domain_metrics.py +0 -0
  94. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_init_cache.py +0 -0
  95. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_integration.py +0 -0
  96. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_meta_output.py +0 -0
  97. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_metrics_utils.py +0 -0
  98. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_normal_consistency.py +0 -0
  99. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_points_3d.py +0 -0
  100. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_points_3d_sparse.py +0 -0
  101. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_rgb_fid_output.py +0 -0
  102. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_rho_a.py +0 -0
  103. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_save_results.py +0 -0
  104. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_sparse_depth.py +0 -0
  105. {euler_eval-2.28.0 → euler_eval-2.30.0}/tests/test_validation.py +0 -0
@@ -0,0 +1,3 @@
1
+ recursive-include docs *.md
2
+ recursive-include tests *.py *.json *.md
3
+ include scripts/generate_distribution_fixtures.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-eval
3
- Version: 2.28.0
3
+ Version: 2.30.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
@@ -42,6 +42,7 @@ Provides-Extra: fid
42
42
  Requires-Dist: clean-fid>=0.1.35; extra == "fid"
43
43
  Provides-Extra: dev
44
44
  Requires-Dist: pytest>=7.0.0; extra == "dev"
45
+ Requires-Dist: jsonschema>=4.18; extra == "dev"
45
46
  Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
46
47
  Requires-Dist: black>=22.0.0; extra == "dev"
47
48
  Requires-Dist: ruff>=0.2; extra == "dev"
@@ -0,0 +1,58 @@
1
+ # euler-eval documentation
2
+
3
+ | Guide | Covers |
4
+ |---|---|
5
+ | [Configuration](configuration.md) | The config file, `gt` and `datasets` sections, paths and selectors, sparse-depth and points-3d ground truth, euler-train logging |
6
+ | [CLI reference](cli.md) | Every flag, worked examples, device selection, offline cache warmup |
7
+ | [Spaces & alignment](alignment.md) | Why `native` and `metric` exist, depth affine fitting, points-3d gauge alignment, benchmark depth bins |
8
+ | [Metrics](metrics.md) | The full metric inventory per modality, with the key each one is written under |
9
+ | [Error distributions](distributions.md) | Optional per-sample and pooled RMSE histograms, bin settings, axes and JSON layout |
10
+ | [Results & output](output.md) | Where files land, the anatomy of `eval.json`, per-file metrics, the sanity-check report |
11
+ | [In-training validation](validation.md) | The programmatic API for scoring in-memory predictions during training |
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
+
19
+ ## Concepts in one page
20
+
21
+ **A dataset is an indexed root, not a folder of files.** Every path in the
22
+ config points at a root — a directory or a `.zip` — carrying
23
+ [ds-crawler](https://github.com/d-rothen/ds-crawler) metadata under
24
+ `.ds_crawler/`. That metadata is what makes a path self-describing: it names the
25
+ loader to use, whether depth is radial or planar, what the value range is, and
26
+ which columns of a point cloud are `x,y,z`.
27
+
28
+ **Ground truth and predictions are paired by ID, not by order.**
29
+ [euler-loading](https://github.com/d-rothen/euler-loading) intersects file IDs
30
+ across modalities, so sample *i* holds the GT depth, the predicted depth and the
31
+ RGB frame that genuinely belong together. Files present on one side only are
32
+ left out of the pairing rather than silently misaligned. Calibration and pose
33
+ data are matched hierarchically instead — one intrinsics file can serve every
34
+ sample beneath it in the tree.
35
+
36
+ **Predictions are scored in a space, and there can be two.** A metric model is
37
+ compared to GT directly. A relative or free-frame model first has its unknown
38
+ degrees of freedom fitted away — scale and shift for depth, a similarity
39
+ transform for point maps. euler-eval reports the raw comparison as `native` and
40
+ the fitted one as `metric`, so a scale-recovery failure never masquerades as a
41
+ geometry failure. See [Spaces & alignment](alignment.md).
42
+
43
+ **Per-image scores are reduced three ways.** `image_mean` and `image_median`
44
+ average per-image values across the dataset; `pixel_pool` pools every valid
45
+ pixel first and reduces once. They answer different questions — a few huge
46
+ images dominate `pixel_pool` but not `image_mean` — so all three are reported.
47
+
48
+ **Metric names are structured, not free-form.** Keys are built with
49
+ [euler-metric-naming](https://github.com/d-rothen/euler-metric-naming), so each
50
+ `eval.json` carries a `metricSet` envelope declaring its namespace and axes, and
51
+ consumers such as euler-train and euler-view can decompose a key like
52
+ `depth.eval.metric.standard.image_mean.absrel` structurally rather than by
53
+ string-splitting.
54
+
55
+ **Nothing is computed twice, and nothing is required.** Each modality is
56
+ evaluated only when both GT and prediction sides are configured (and not
57
+ skipped), and results are written per modality — so a depth-only prediction is a
58
+ perfectly valid run.
@@ -0,0 +1,91 @@
1
+ # Spaces & alignment
2
+
3
+ Not every prediction is directly comparable to metric ground truth. A monocular
4
+ depth model may output values that are only correct up to a scale and a shift; a
5
+ point-map model may reconstruct the scene in its own frame entirely. Scoring
6
+ such a prediction as-is measures the missing calibration, not the geometry.
7
+
8
+ euler-eval handles this by fitting away exactly the unknown degrees of freedom
9
+ and reporting **both** results:
10
+
11
+ | Space | Meaning |
12
+ |---|---|
13
+ | `native` | The prediction as the model emitted it, compared to GT directly |
14
+ | `metric` | The prediction after the unknown gauge has been fitted to GT |
15
+
16
+ `native` diagnoses whether the model is metric at all; `metric` isolates
17
+ geometry quality. A canonical alias (`depth`, `points_3d`) points at `metric`
18
+ when it exists, else `native`, so downstream tooling can read one key without
19
+ knowing how the run was configured — see [Results & output](output.md).
20
+
21
+ ## Depth alignment
22
+
23
+ Relative depth predictions are aligned to metric GT by fitting a global affine
24
+ map `d_aligned = s · d_pred + t` by least squares over valid pixels.
25
+ `--depth-alignment` controls when the fit runs:
26
+
27
+ | Mode | Behaviour |
28
+ |---|---|
29
+ | `none` | Never fit; score the prediction as-is (already-metric models) |
30
+ | `auto_affine` *(default)* | Fit only when the first prediction looks normalized (roughly `[-1, 1]`) |
31
+ | `affine` | Always fit |
32
+
33
+ The fit uses pixels where GT and prediction are finite and GT > 0. With
34
+ `--mask-sky`, sky pixels are excluded and the fit is trimmed to `gt ≤ P95(gt)`
35
+ so residual sky outliers do not dominate.
36
+
37
+ Declaring the prediction with the `relative_depth` or `affine_depth` config key
38
+ (instead of `depth`) tells the evaluator the model is not metric, so
39
+ `auto_affine` fits even when the values happen to fall outside the normalized
40
+ range — see [Configuration](configuration.md#datasets-section).
41
+
42
+ ## Points-3D gauge alignment
43
+
44
+ Point maps from different model families live in different frames, so
45
+ `points_3d` resolves an unknown **similarity gauge** — the 3D generalization of
46
+ depth's scale-and-shift — before the `metric`-space comparison.
47
+ `--points-3d-alignment` selects it:
48
+
49
+ | Mode | Fits | Typical model |
50
+ |---|---|---|
51
+ | `none` | nothing | Metric, predict-your-own-camera models |
52
+ | `scale` | a single global scalar | Scale-ambiguous but frame-correct predictions |
53
+ | `similarity` | a 7-DoF Umeyama transform `s·R·p + t` over known per-pixel correspondences | Relative / free-frame models |
54
+ | `auto` *(default)* | `similarity` for declared-relative predictions, else nothing | Mixed comparisons |
55
+
56
+ The angular `error_decomposition` ray metrics and `rho_a` are computed on the
57
+ **native** point-map directions — the camera-faithful frame — because rotating
58
+ the cloud into GT would erase exactly the camera error they measure.
59
+ Radial/lateral and Euclidean metrics are reported per space.
60
+
61
+ ## Benchmark depth bins
62
+
63
+ `--benchmark-depth-range MIN MAX` filters valid GT depth pixels to `[MIN, MAX]`,
64
+ then splits them into three equal-width intervals in **square-root** depth
65
+ space, which keeps the near field from being compressed into a single bin:
66
+
67
+ ```text
68
+ sqrt_min = sqrt(MIN); sqrt_max = sqrt(MAX); step = (sqrt_max - sqrt_min) / 3
69
+ near = [MIN, (sqrt_min + step)^2)
70
+ mid = [(sqrt_min + step)^2, (sqrt_min + 2·step)^2)
71
+ far = [(sqrt_min + 2·step)^2, MAX]
72
+ ```
73
+
74
+ For `--benchmark-depth-range 0.01 80.0` this yields `near=[0.01, 9.29)`,
75
+ `mid=[9.29, 35.95)`, `far=[35.95, 80.0]`, plus an `all` bin over the full range.
76
+
77
+ Benchmark bins are **additive**: the regular metrics are still computed and
78
+ written exactly as before, with the binned values added under a `bin` axis
79
+ (`…standard.image_mean.near.absrel`) and the resolved boundaries recorded in the
80
+ `metricSet` metadata. RGB metrics are binned too, using GT depth to decide which
81
+ pixels fall in which bin.
82
+
83
+ The same binning is available programmatically through
84
+ `benchmark_depth_range=(MIN, MAX)` — see
85
+ [In-training validation](validation.md).
86
+
87
+ With `--distributions`, default depth dataset/per-file histograms use the
88
+ benchmark `all` population, with optional sky-depth capping applied before GT
89
+ range selection. Their counts match the explicit `all` histogram, while the
90
+ unbinned scalar summaries retain their existing population. See
91
+ [Error distributions](distributions.md#benchmark-depth-range-and-sky-depth).
@@ -0,0 +1,150 @@
1
+ # CLI reference
2
+
3
+ ```bash
4
+ euler-eval <config> [options]
5
+ ```
6
+
7
+ The config is the only positional argument — a JSON file describing the ground
8
+ truth and the prediction datasets to score against it, documented in
9
+ [Configuration](configuration.md). Invalid JSON or a config that fails
10
+ validation exits with status `1` before any dataset is opened.
11
+
12
+ ## Options
13
+
14
+ | Flag | Type | Default | Description |
15
+ |---|---|---|---|
16
+ | `--device` | `{auto,cuda,cpu}` | `auto` | Compute device (`auto` prefers CUDA when available) |
17
+ | `--batch-size` | `int` | `16` | Batch size for metrics that support batching |
18
+ | `--num-workers` | `int` | `4` | Number of data-loading workers |
19
+ | `--verbose`, `-v` | flag | off | Enable verbose output |
20
+ | `--domain` | `{dehazing}` | none | Add a domain-specific metric set to the core metrics; repeatable |
21
+ | `--skip-depth` | flag | off | Skip depth evaluation |
22
+ | `--skip-rgb` | flag | off | Skip RGB evaluation |
23
+ | `--skip-rays` | flag | off | Skip rays (spherical direction map) evaluation |
24
+ | `--skip-points-3d` | flag | off | Skip points_3d (per-pixel 3D point map) evaluation |
25
+ | `--mask-sky` | flag | off | Mask sky regions from metrics using GT segmentation |
26
+ | `--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 |
27
+ | `--no-sanity-check` | flag | off | Disable sanity checking of metric configurations |
28
+ | `--metrics-config` | `str` | auto-detect | Path to `metrics_config.json` for sanity checking |
29
+ | `--depth-alignment` | `{none,auto_affine,affine}` | `auto_affine` | Depth calibration mode ([details](alignment.md#depth-alignment)) |
30
+ | `--points-3d-alignment` | `{none,scale,similarity,auto}` | `auto` | points_3d gauge alignment ([details](alignment.md#points-3d-gauge-alignment)) |
31
+ | `--rgb-fid-backend` | `{builtin,clean-fid}` | `builtin` | RGB FID backend; `clean-fid` requires the `[fid]` extra |
32
+ | `--benchmark-depth-range` | `float float` | none | Depth range `[MIN, MAX]` (m) for near/mid/far benchmark bins ([details](alignment.md#benchmark-depth-bins)) |
33
+ | `--distributions` | flag | off | Enable per-sample and pixel-pool RMSE error histograms for depth and point maps ([details](distributions.md)) |
34
+ | `--no-distributions` | flag | off | Disable histograms, overriding config settings |
35
+ | `--distribution-bins` | `int` | `50` | Total histogram bins, including tails; minimum 3 |
36
+ | `--distribution-scale` | `{log,linear}` | `log` | Spacing of finite histogram bins |
37
+ | `--distribution-range` | `float float` | `0.001 100` | Finite error range `[MIN, MAX)` in evaluated space units; zero/small-error and overflow bins retain the tails |
38
+
39
+ Histogram settings can also be specified in the config's `distributions`
40
+ section. A bin, scale or range flag enables histograms and overrides that
41
+ config field; `--no-distributions` disables them regardless.
42
+
43
+ For depth, `--benchmark-depth-range` also restricts the default dataset/per-file
44
+ histograms to the benchmark's inclusive GT range. `--sky-depth` caps GT and
45
+ predictions before this selection and error computation. See the
46
+ [population rules](distributions.md#benchmark-depth-range-and-sky-depth).
47
+
48
+ ## Examples
49
+
50
+ ```bash
51
+ # Default settings (auto-selects CUDA when available)
52
+ euler-eval config.json --batch-size 32
53
+
54
+ # Sky masking (requires gt.segmentation)
55
+ euler-eval config.json --mask-sky -v
56
+
57
+ # Cap depth at 100 m and fill predicted sky at 100 m (optional datasets[].sky_mask)
58
+ euler-eval config.json --sky-depth 100
59
+
60
+ # Only depth evaluation
61
+ euler-eval config.json --skip-rgb --skip-rays --skip-points-3d
62
+
63
+ # Force affine scale+shift alignment on all depth predictions
64
+ euler-eval config.json --depth-alignment affine
65
+
66
+ # Use clean-fid for RGB FID
67
+ euler-eval config.json --rgb-fid-backend clean-fid
68
+
69
+ # Keep all core metrics and add no-reference dehazing metrics (NIQE + FADE)
70
+ euler-eval config.json --domain dehazing
71
+
72
+ # Benchmark depth/RGB metrics within a depth range (near/mid/far bins)
73
+ euler-eval config.json --benchmark-depth-range 0.01 80.0
74
+
75
+ # Per-image and pooled RMSE error histograms with shared logarithmic bins
76
+ euler-eval config.json --distributions --distribution-bins 64
77
+
78
+ # Dense depth against sparse pointcloud GT (pointwise sparse-depth metrics)
79
+ euler-eval example_sparse_depth_config.json --skip-rgb --skip-rays
80
+
81
+ # Per-pixel 3D point maps (predict-your-own-camera models)
82
+ euler-eval example_points_3d_config.json --skip-depth --skip-rgb --skip-rays
83
+
84
+ # Align relative point maps to GT with a similarity (Umeyama) gauge
85
+ euler-eval example_points_3d_config.json --points-3d-alignment similarity
86
+ ```
87
+
88
+ ## Metric domains
89
+
90
+ The default metric set is named `core` and is always enabled. `--domain` adds
91
+ metrics suited to a particular evaluation domain without removing or changing
92
+ the core results. The flag is repeatable so future domains can be composed in a
93
+ single run.
94
+
95
+ `--domain dehazing` adds NIQE and FADE under `rgb.eval.dehazing`. Both are
96
+ no-reference metrics: they score each predicted RGB image, do not use the paired
97
+ GT image, and are averaged across valid images for the dataset result. They use
98
+ the full prediction even with `--mask-sky`, because filling masked pixels would
99
+ change the natural-scene statistics being measured. See [Metrics](metrics.md#dehazing-domain).
100
+
101
+ ## What a run prints
102
+
103
+ Before any dataset is opened, the run prints its own provenance — package
104
+ version, Python, torch and CUDA versions — which is also what gets recorded in
105
+ `meta` and, when configured, in the euler-train run. It then echoes the resolved
106
+ device, alignment mode, selected metric sets, FID backend and benchmark range,
107
+ followed by one block
108
+ per prediction dataset and modality with the GT and prediction paths, the
109
+ resolved modality metadata (such as `radial_depth`), and the number of matched
110
+ pairs.
111
+
112
+ A matched-pair count that is much lower than expected is the first sign that GT
113
+ and predictions are not indexed consistently — see
114
+ [Loader resolution & dataset metadata](configuration.md#loader-resolution--dataset-metadata).
115
+
116
+ ## Device selection
117
+
118
+ `--device auto` resolves to CUDA when `torch.cuda.is_available()`, otherwise
119
+ CPU. Requesting `--device cuda` on a machine without CUDA prints a warning and
120
+ falls back to CPU rather than failing.
121
+
122
+ On CUDA, the run enables cuDNN benchmarking and TF32 matmuls — throughput knobs
123
+ that do not change which metrics are computed.
124
+
125
+ `--batch-size` applies to the batched image-quality metrics (PSNR, SSIM, LPIPS,
126
+ FID/KID); `--num-workers` is passed to the underlying dataloaders. Sparse-depth
127
+ and points-3d evaluation is CPU/NumPy work, so `--num-workers` matters more than
128
+ `--batch-size` there.
129
+
130
+ ## Offline cache warmup
131
+
132
+ Compute nodes without network access need the model weights pre-fetched. Run the
133
+ cache warmup helper on a machine that does have network access, pointing the
134
+ cache environment variables at a shared location:
135
+
136
+ ```bash
137
+ HF_HOME=/shared/cache/hf \
138
+ TORCH_HOME=/shared/cache/hf/torch \
139
+ CLEANFID_CACHE_DIR=/shared/cache/clean-fid \
140
+ euler-eval.init
141
+ ```
142
+
143
+ This pre-downloads the torchvision AlexNet and Inception v3 weights (used by
144
+ LPIPS and the builtin FID/KID), the LPIPS AlexNet weights, and the clean-fid
145
+ Inception checkpoint if `clean-fid` is installed. `TORCH_HOME` is derived from
146
+ `HF_HOME` when it is not set explicitly, so pointing `HF_HOME` at the shared
147
+ cache is usually enough.
148
+
149
+ Set the same variables for the evaluation run itself so the cached weights are
150
+ found.
@@ -0,0 +1,269 @@
1
+ # Configuration
2
+
3
+ A run is described by one JSON file: the ground truth, the prediction datasets
4
+ to score against it, and optional experiment logging.
5
+
6
+ ```json
7
+ {
8
+ "euler_train": { "dir": "runs/my_project" },
9
+ "gt": {
10
+ "rgb": { "path": "/data/gt/rgb" },
11
+ "depth": { "path": "/data/gt/depth" },
12
+ "rays": { "path": "/data/gt/rays" },
13
+ "segmentation": { "path": "/data/gt/segmentation" },
14
+ "calibration": { "path": "/data/gt/calibration" }
15
+ },
16
+ "datasets": [
17
+ {
18
+ "name": "model_a",
19
+ "rgb": { "path": "/data/model_a/rgb" },
20
+ "depth": { "path": "/data/model_a/depth" },
21
+ "rays": { "path": "/data/model_a/rays" },
22
+ "output_file": "/path/to/output/model_a_eval.json"
23
+ },
24
+ { "name": "model_b_depth_only", "depth": { "path": "/data/model_b/depth" } }
25
+ ]
26
+ }
27
+ ```
28
+
29
+ Runnable starting points live in the repository root:
30
+ [`example_config.json`](../example_config.json),
31
+ [`example_sparse_depth_config.json`](../example_sparse_depth_config.json), and
32
+ [`example_points_3d_config.json`](../example_points_3d_config.json).
33
+
34
+ The config is validated before any work starts: a missing `gt` section, an empty
35
+ `datasets` array, a dataset without a `name` or without a single prediction
36
+ modality, and any path that does not exist all fail immediately with a message
37
+ naming the offending field.
38
+
39
+ The optional top-level [`distributions`](distributions.md#bin-settings) section
40
+ enables per-sample and pooled RMSE histograms. Use `true` for defaults or an
41
+ object with `n_bins`, `scale`, `min_error` and `max_error`; CLI histogram settings
42
+ override the corresponding fields. Omitted or `false` leaves histograms disabled.
43
+
44
+ ## Paths and splits
45
+
46
+ Every modality entry is an object with a `path`, and optionally a `split`:
47
+
48
+ ```json
49
+ { "path": "/data/gt/depth", "split": "test" }
50
+ ```
51
+
52
+ euler-loading inline selectors work directly in `path` as well, which is often
53
+ shorter:
54
+
55
+ | Form | Meaning |
56
+ |---|---|
57
+ | `/data/muses` | Directory root, canonical index |
58
+ | `/data/muses.zip` | Zip archive, read without extraction |
59
+ | `/data/muses.zip:test` | The `test` split of that archive |
60
+ | `/data/muses.zip:test#scope=rgb` | The `test` split, reading `.ds_crawler/rgb/` metadata |
61
+
62
+ ## `gt` section
63
+
64
+ At least one of `rgb`, `depth`, `sparse_depth`, `rays`, or `points_3d` is
65
+ required.
66
+
67
+ | Field | Required | Description |
68
+ |---|---|---|
69
+ | `gt.rgb.path` | no\* | GT RGB dataset |
70
+ | `gt.depth.path` | no\* | GT dense depth dataset |
71
+ | `gt.sparse_depth.path` | no\* | Sparse pointcloud GT, `(N,C)` points whose first columns are `x,y,z` in metres |
72
+ | `gt.rays.path` | no\* | GT ray direction map dataset |
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`) |
75
+ | `gt.calibration.path` | no | Calibration data (camera intrinsics); enables true camera-frame [surface normals](metrics.md#surface-normals-and-calibration) |
76
+ | `gt.intrinsics.path` | with `gt.sparse_depth` | Camera intrinsics for pointcloud projection |
77
+ | `gt.camera_extrinsics.path` | with `gt.sparse_depth` | Source-to-camera extrinsics (e.g. MUSES `lidar2rgb`) |
78
+ | `gt.lidar_extrinsics.path` | no | Optional lidar pose in a shared frame; composed with `gt.camera_extrinsics` |
79
+ | `gt.name` | no | Display name for ground truth (default `"GT"`) |
80
+
81
+ \* At least one of the five modality entries must be present.
82
+
83
+ ## `datasets` section
84
+
85
+ Each entry needs a `name` and at least one prediction modality. Use only **one**
86
+ dense depth-like entry (`depth`, `relative_depth`, or `affine_depth`) per
87
+ dataset — configuring two is an error, because the space the prediction lives in
88
+ would be ambiguous.
89
+
90
+ | Field | Required | Description |
91
+ |---|---|---|
92
+ | `name` | yes | Display name for this prediction dataset |
93
+ | `rgb.path` | no\* | Predicted RGB dataset |
94
+ | `depth.path` | no\* | Predicted dense metric depth; also used against sparse pointcloud GT |
95
+ | `relative_depth.path` | no\* | Predicted dense relative depth (scale/shift alignment supported) |
96
+ | `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` |
98
+ | `rays.path` | no\* | Predicted ray direction map dataset |
99
+ | `points_3d.path` | no\* | Predicted per-pixel 3D point map `(H,W,3)` |
100
+ | `output_file` | no | Custom results path (default: `eval.json` in the first modality path) |
101
+
102
+ \* At least one prediction modality must be present.
103
+
104
+ Which entry you use is also a *declaration*: `relative_depth` and `affine_depth`
105
+ tell the evaluator the prediction is not metric, which is what
106
+ `--depth-alignment auto_affine` and `--points-3d-alignment auto` key off. See
107
+ [Spaces & alignment](alignment.md).
108
+
109
+ ### Predicted sky depth
110
+
111
+ Add a `sky_mask` entry alongside `depth`, `relative_depth`, or `affine_depth`
112
+ and run with `--sky-depth 100` to evaluate predicted sky at 100 meters:
113
+
114
+ ```json
115
+ {
116
+ "name": "model_with_sky",
117
+ "depth": { "path": "/data/model/depth" },
118
+ "sky_mask": { "path": "/data/model/sky_mask", "split": "test" }
119
+ }
120
+ ```
121
+
122
+ The sky mask is paired with GT and predicted depth by sample ID and hierarchy.
123
+ It supports the same archive, split, and `#scope=` selectors as depth, defaulting
124
+ to the `sky_mask` metadata scope. Its loader must return a boolean mask with
125
+ **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
128
+ rule or use nearest-neighbor resizing.
129
+
130
+ `--sky-depth` applies to dense and sparse depth evaluation. GT and both native
131
+ and aligned prediction branches are capped after depth conversion and affine
132
+ fitting; predicted sky pixels are then set to the cap. Predicted sky pixels are
133
+ excluded from the affine fit. Benchmark bins use the capped GT depth.
134
+
135
+ The cap also works without a predicted mask. Positive infinity is capped;
136
+ NaNs and nonpositive depths remain invalid, except that masked prediction
137
+ pixels are filled even if their original depth is invalid. Sparse GT still
138
+ contributes only at projected points. Predicted sky stays in the scores where
139
+ GT is valid; `--mask-sky` separately excludes pixels using GT segmentation and
140
+ can be combined with `--sky-depth`.
141
+
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`.
145
+
146
+ ## Sparse depth (pointcloud GT)
147
+
148
+ Use `gt.sparse_depth` instead of `gt.depth` to evaluate a dense depth prediction
149
+ against a sparse pointcloud, e.g. raw LiDAR returns.
150
+
151
+ The evaluator projects the sparse GT cloud into the prediction plane using
152
+ `gt.intrinsics` and `gt.camera_extrinsics`, then computes pointwise depth
153
+ metrics only at projected valid pixels. The resulting depth `eval.json` is an
154
+ atomic `sparsedepth.eval` metric set; a depth prediction is not also unprojected
155
+ and evaluated as `points_3d`.
156
+
157
+ An explicit `datasets[].points_3d` prediction can be evaluated separately
158
+ against the same sparse cloud. That evaluation uses the points-3D similarity
159
+ gauge, writes an atomic `points3d.eval` metric set beside the point-map
160
+ prediction, and is controlled by `--skip-points-3d`. If a dataset provides both
161
+ depth and `points_3d`, each prediction modality gets its own `eval.json` at its
162
+ own path.
163
+
164
+ 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.
167
+
168
+ ```json
169
+ {
170
+ "gt": {
171
+ "name": "MUSES sparse GT",
172
+ "sparse_depth": { "path": "/data/muses.zip:test#scope=lidar" },
173
+ "intrinsics": { "path": "/data/muses.zip:test#scope=intrinsics" },
174
+ "camera_extrinsics": { "path": "/data/muses.zip:test#scope=extrinsics" }
175
+ },
176
+ "datasets": [
177
+ { "name": "model_a", "depth": { "path": "/data/model_a/dense_depth:test" } }
178
+ ]
179
+ }
180
+ ```
181
+
182
+ ## Points-3D ground truth sources
183
+
184
+ A `points_3d` prediction is compared against a GT point map from one of two
185
+ sources:
186
+
187
+ 1. **Explicit** — `gt.points_3d.path` (a stored `(H,W,3)` map).
188
+ 2. **Synthesized from depth** — if `gt.points_3d` is absent but `gt.depth.path`
189
+ and intrinsics (`gt.intrinsics` or `gt.calibration`) are present, GT depth is
190
+ unprojected on the fly. Whether the GT depth is radial or planar is read from
191
+ its metadata (`radial_depth`).
192
+
193
+ ## `euler_train` section (optional)
194
+
195
+ When present, results are logged to an
196
+ [euler-train](https://github.com/d-rothen/euler-train) run, which requires the
197
+ `[logging]` extra. `euler_train.dir` is either a project directory (a new run is
198
+ created and finished on completion) or a full path to an existing run directory
199
+ (the run is resumed and detached afterwards, leaving it active). `dir` is the
200
+ only supported key.
201
+
202
+ Each prediction dataset is registered as an evaluation named after its config
203
+ `name`, first as `running` and then with its metrics attached, alongside package
204
+ provenance (euler-eval version, Python, torch, CUDA).
205
+
206
+ ## Loader resolution & dataset metadata
207
+
208
+ Loaders are resolved automatically by euler-loading from each dataset's
209
+ ds-crawler index metadata — no manual loader selection is needed. Each dataset
210
+ directory declares its loader via the `euler_loading.loader` and
211
+ `euler_loading.function` fields of its index. Modality metadata
212
+ (`radial_depth`, `rgb_range`, sparse point columns, coordinate units) is read
213
+ automatically; depth and point-cloud coordinates are assumed to already be in
214
+ metres.
215
+
216
+ Each dataset root or archive must contain ds-crawler metadata under
217
+ `.ds_crawler/` (`dataset-head.json`, `ds-crawler.json`, `index.json`). When one
218
+ physical root holds several logical modalities, the artifacts may be scoped
219
+ under `.ds_crawler/<modality>/` with a `.ds_crawler/scopes.json` manifest,
220
+ letting a path like `/data/muses.zip:test` load `.ds_crawler/rgb/index.json`,
221
+ `.ds_crawler/depth/index.json`, and so on from the same archive. GT and
222
+ prediction datasets are matched by hierarchy path and file ID.
223
+
224
+ ### Multiple modalities from one archive
225
+
226
+ Give every modality the same archive root and let the scope select the metadata.
227
+ The scope defaults to the configuration key name, so the common case is compact:
228
+
229
+ ```json
230
+ {
231
+ "gt": {
232
+ "rgb": { "path": "/data/capture.zip:test" },
233
+ "depth": { "path": "/data/capture.zip:test" },
234
+ "intrinsics": { "path": "/data/capture.zip:test" },
235
+ "camera_extrinsics": { "path": "/data/capture.zip:test" }
236
+ }
237
+ }
238
+ ```
239
+
240
+ Use `#scope=<scope>` when an artifact scope is not named exactly like the config
241
+ 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.
247
+
248
+ ## `metrics_config.json`
249
+
250
+ Controls the thresholds used by sanity checking. When `--metrics-config` is not
251
+ given, the tool auto-detects `metrics_config.json` at the project root and falls
252
+ back to built-in defaults if there is none.
253
+
254
+ The file is grouped by modality, and each metric block carries its thresholds
255
+ plus a `description` explaining what the check is for:
256
+
257
+ ```json
258
+ {
259
+ "depth": {
260
+ "expected_range": { "min": 0.0, "max": 100.0 },
261
+ "absrel": { "warn_if_median_exceeds": 1.0, "warn_if_p90_exceeds": 2.0 },
262
+ "silog": { "warn_if_exceeds": 0.5 }
263
+ }
264
+ }
265
+ ```
266
+
267
+ See [`metrics_config.json`](../metrics_config.json) for the full set of options,
268
+ and [Results & output](output.md#sanity-check-report) for what the checks
269
+ produce.