euler-eval 2.33.0__tar.gz → 2.35.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 (114) hide show
  1. {euler_eval-2.33.0/euler_eval.egg-info → euler_eval-2.35.0}/PKG-INFO +1 -1
  2. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/configuration.md +15 -12
  3. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/cli.py +27 -18
  4. {euler_eval-2.33.0 → euler_eval-2.35.0/euler_eval.egg-info}/PKG-INFO +1 -1
  5. {euler_eval-2.33.0 → euler_eval-2.35.0}/pyproject.toml +1 -1
  6. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_experiments.py +104 -24
  7. {euler_eval-2.33.0 → euler_eval-2.35.0}/LICENSE +0 -0
  8. {euler_eval-2.33.0 → euler_eval-2.35.0}/MANIFEST.in +0 -0
  9. {euler_eval-2.33.0 → euler_eval-2.35.0}/README.md +0 -0
  10. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/README.md +0 -0
  11. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/alignment.md +0 -0
  12. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/cli.md +0 -0
  13. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/distributions.md +0 -0
  14. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/feature-request-unified-evaluation-framework.md +0 -0
  15. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/metrics.md +0 -0
  16. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/output.md +0 -0
  17. {euler_eval-2.33.0 → euler_eval-2.35.0}/docs/validation.md +0 -0
  18. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/THIRD_PARTY_NOTICES.md +0 -0
  19. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/__init__.py +0 -0
  20. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/calibration.py +0 -0
  21. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/config_paths.py +0 -0
  22. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/data.py +0 -0
  23. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/distribution_contract.py +0 -0
  24. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/distributions.py +0 -0
  25. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/evaluate.py +0 -0
  26. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metric_sets.py +0 -0
  27. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/__init__.py +0 -0
  28. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/_natural_scene_models.py +0 -0
  29. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/absrel.py +0 -0
  30. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/daniel_error.py +0 -0
  31. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/depth_binned_error.py +0 -0
  32. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/depth_edge_f1.py +0 -0
  33. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/depth_standard.py +0 -0
  34. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/fade.py +0 -0
  35. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/fid_kid.py +0 -0
  36. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/gpu_depth_batch.py +0 -0
  37. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/gpu_image_batch.py +0 -0
  38. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/high_freq_energy.py +0 -0
  39. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/lpips_metric.py +0 -0
  40. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/niqe.py +0 -0
  41. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/normal_consistency.py +0 -0
  42. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/points3d_cloud.py +0 -0
  43. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/points3d_decomposition.py +0 -0
  44. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/points3d_distance.py +0 -0
  45. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/points3d_geometry.py +0 -0
  46. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/psnr.py +0 -0
  47. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/rgb_edge_f1.py +0 -0
  48. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/rgb_lpips.py +0 -0
  49. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/rgb_psnr_ssim.py +0 -0
  50. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/rho_a.py +0 -0
  51. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/rmse.py +0 -0
  52. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/scale_invariant_log.py +0 -0
  53. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/sky_iou.py +0 -0
  54. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/ssim.py +0 -0
  55. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/tail_errors.py +0 -0
  56. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/metrics/utils.py +0 -0
  57. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/protocol.py +0 -0
  58. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/sanity_checker.py +0 -0
  59. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/schemas/distribution-v1.schema.json +0 -0
  60. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/utils/hierarchy_parser.py +0 -0
  61. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval/validation.py +0 -0
  62. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval.egg-info/SOURCES.txt +0 -0
  63. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval.egg-info/dependency_links.txt +0 -0
  64. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval.egg-info/entry_points.txt +0 -0
  65. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval.egg-info/requires.txt +0 -0
  66. {euler_eval-2.33.0 → euler_eval-2.35.0}/euler_eval.egg-info/top_level.txt +0 -0
  67. {euler_eval-2.33.0 → euler_eval-2.35.0}/init_cache.py +0 -0
  68. {euler_eval-2.33.0 → euler_eval-2.35.0}/scripts/generate_distribution_fixtures.py +0 -0
  69. {euler_eval-2.33.0 → euler_eval-2.35.0}/setup.cfg +0 -0
  70. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/__init__.py +0 -0
  71. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/conftest.py +0 -0
  72. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/distribution_contract_utils.py +0 -0
  73. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/eval_output.py +0 -0
  74. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/README.md +0 -0
  75. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/counts-depth.eval.json +0 -0
  76. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/counts-points3d.eval.json +0 -0
  77. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/counts-sparsedepth.eval.json +0 -0
  78. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/future-sum-by-depth.eval.json +0 -0
  79. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/histogram-only.eval.json +0 -0
  80. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/per-file-only.eval.json +0 -0
  81. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/fixtures/distributions/scalar-only.eval.json +0 -0
  82. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/mock_files/calibration_output.json +0 -0
  83. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/mock_files/depth_output.json +0 -0
  84. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/mock_files/rgb_output.json +0 -0
  85. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/mock_files/segmentation_output.json +0 -0
  86. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_alignment.py +0 -0
  87. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_cli_device.py +0 -0
  88. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_cli_sparse_outputs.py +0 -0
  89. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_config.py +0 -0
  90. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_data.py +0 -0
  91. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_data_builders.py +0 -0
  92. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_depth_alignment_output.py +0 -0
  93. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_depth_standard.py +0 -0
  94. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_distribution_contract.py +0 -0
  95. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_distributions.py +0 -0
  96. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_domain_metrics.py +0 -0
  97. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_evaluate_helpers.py +0 -0
  98. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_init_cache.py +0 -0
  99. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_integration.py +0 -0
  100. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_mask_sky_pairing.py +0 -0
  101. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_mask_sky_prediction.py +0 -0
  102. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_meta_output.py +0 -0
  103. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_metrics_utils.py +0 -0
  104. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_normal_consistency.py +0 -0
  105. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_points_3d.py +0 -0
  106. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_points_3d_sparse.py +0 -0
  107. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_protocol.py +0 -0
  108. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_rgb_fid_output.py +0 -0
  109. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_rho_a.py +0 -0
  110. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_save_results.py +0 -0
  111. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_sky_iou.py +0 -0
  112. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_sky_iou_cli.py +0 -0
  113. {euler_eval-2.33.0 → euler_eval-2.35.0}/tests/test_sparse_depth.py +0 -0
  114. {euler_eval-2.33.0 → euler_eval-2.35.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.33.0
3
+ Version: 2.35.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
@@ -272,7 +272,7 @@ command-line flags and under its own name:
272
272
  |---|---|---|
273
273
  | `name` | yes | Evaluation name: results are written as `<name>.json` in each prediction dataset's `.eval/` folder ([where results land](output.md#where-results-land)). Same rules as `--eval-name`; unique |
274
274
  | `args` | no | Command-line tokens this experiment adds, exactly as you would type them; numbers may be written as JSON numbers |
275
- | `modalities` | no | The configured modalities this experiment may load (default: all). Names: `depth` (also covers `relative_depth` and `affine_depth` predictions), `sparse_depth`, `rgb`, `rays`, `points_3d`, `sky_mask`, `segmentation` (also covers `semantic_segmentation`), `calibration`, `intrinsics`, `camera_extrinsics`, `lidar_extrinsics` |
275
+ | `modalities` | no | The prediction modalities (`datasets[]` entries) this experiment may load (default: all): `depth` (also covers `relative_depth` and `affine_depth`), `rgb`, `rays`, `points_3d`, `sky_mask`. The `gt` section is always used as configured |
276
276
 
277
277
  - **The command line is the base.** Each experiment runs with the flags given on
278
278
  the command line followed by its own `args`, so for a flag that takes a
@@ -283,17 +283,20 @@ command-line flags and under its own name:
283
283
  validated before the first one runs. `--eval-name` and `--experiment` are not
284
284
  allowed in `args`, and `--eval-name` cannot be combined with an `experiments`
285
285
  section, because `name` names the results.
286
- - **Modalities.** An experiment with `modalities` runs as if the unlisted GT
287
- inputs and prediction entries were not in the config; a name covers both
288
- sides. A listed modality is still read only when the evaluation needs it,
289
- e.g. `sky_mask` only with `--sky-depth`, `--mask-sky-prediction` or
290
- `--sky-iou`. This is how to compare runs where a modality's presence changes
291
- the output: above, `capped_no_mask` caps depth at 80 m without filling the
292
- predicted sky. A dataset left without a prediction is not evaluated in that
293
- experiment, and the reduced config must still be valid (e.g. `sparse_depth`
294
- needs `intrinsics` and `camera_extrinsics`). Listing a modality the config
295
- does not have only prints a warning, so one experiment list can serve several
296
- generated configs.
286
+ - **Modalities.** An experiment with `modalities` runs as if the unlisted
287
+ prediction entries were not in `datasets[]`; a `sky_mask` goes with its depth
288
+ prediction. GT inputs are not selected this way: they stay as configured,
289
+ and flags decide which ones an evaluation reads (`--mask-sky` the
290
+ segmentation, for example), so GT names such as `segmentation` or
291
+ `sparse_depth` are rejected. A listed modality is still read only when the
292
+ evaluation needs it, e.g. `sky_mask` only with `--sky-depth`,
293
+ `--mask-sky-prediction` or `--sky-iou`; with just `--mask-sky`, listing
294
+ `sky_mask` changes nothing. This is how to compare runs where a modality's
295
+ presence changes the output: above, `capped_no_mask` caps depth at 80 m
296
+ without filling the predicted sky. A dataset left without a prediction is
297
+ not evaluated in that experiment. Listing a modality no dataset has only
298
+ prints a warning, so one experiment list can serve several generated
299
+ configs.
297
300
  - **Selecting experiments.** By default all experiments run, in config order.
298
301
  `--experiment NAME` (repeatable) runs only the named ones, e.g. one per task
299
302
  of a job array.
@@ -273,24 +273,28 @@ def _points_3d_eval_axes(*, distributions: bool = False) -> dict[str, AxisDeclar
273
273
 
274
274
  _PRED_DEPTH_KEYS = ("depth", "relative_depth", "affine_depth")
275
275
 
276
- #: Names an experiment's ``modalities`` may list, and the GT and prediction
277
- #: config keys each one covers.
276
+ #: Prediction modalities an experiment's ``modalities`` may list, and the
277
+ #: ``datasets[]`` keys each one covers. GT inputs are not selected this way:
278
+ #: they are used as configured, and flags such as ``--mask-sky`` decide
279
+ #: which of them an evaluation reads.
278
280
  _EXPERIMENT_MODALITIES = {
279
281
  "depth": _PRED_DEPTH_KEYS,
280
- "sparse_depth": ("sparse_depth",),
281
282
  "rgb": ("rgb",),
282
283
  "rays": ("rays",),
283
284
  "points_3d": ("points_3d",),
284
285
  "sky_mask": ("sky_mask",),
285
- "segmentation": GT_SEGMENTATION_KEYS,
286
- "calibration": ("calibration",),
287
- "intrinsics": ("intrinsics",),
288
- "camera_extrinsics": ("camera_extrinsics",),
289
- "lidar_extrinsics": ("lidar_extrinsics",),
290
286
  }
291
287
  _MODALITY_OF_CONFIG_KEY = {
292
288
  key: name for name, keys in _EXPERIMENT_MODALITIES.items() for key in keys
293
289
  }
290
+ _GT_ONLY_KEYS = (
291
+ "sparse_depth",
292
+ *GT_SEGMENTATION_KEYS,
293
+ "calibration",
294
+ "intrinsics",
295
+ "camera_extrinsics",
296
+ "lidar_extrinsics",
297
+ )
294
298
 
295
299
  # Downstream eval consumers validate metricSet.metricNamespace with a stricter
296
300
  # first-segment rule than euler_metric_naming's modality validator. Keep the
@@ -1166,6 +1170,12 @@ def _validate_experiment_modalities(modalities, label: str) -> None:
1166
1170
  'e.g. ["depth", "sky_mask"]'
1167
1171
  )
1168
1172
  for name in modalities:
1173
+ if name in _GT_ONLY_KEYS:
1174
+ raise ValueError(
1175
+ f"{label}.modalities: {name!r} is a GT input; 'modalities' selects "
1176
+ "prediction modalities, and GT inputs are used as configured "
1177
+ "(flags such as --mask-sky decide which ones are read)"
1178
+ )
1169
1179
  if name not in _EXPERIMENT_MODALITIES:
1170
1180
  covered_by = _MODALITY_OF_CONFIG_KEY.get(name)
1171
1181
  hint = f" (use {covered_by!r}, which covers it)" if covered_by else ""
@@ -1185,21 +1195,21 @@ def _has_prediction(entry: dict) -> bool:
1185
1195
 
1186
1196
 
1187
1197
  def _configured_modalities(config: dict) -> set[str]:
1188
- sections = [config["gt"], *config["datasets"]]
1189
1198
  return {
1190
1199
  _MODALITY_OF_CONFIG_KEY[key]
1191
- for section in sections
1192
- for key in section
1200
+ for entry in config["datasets"]
1201
+ for key in entry
1193
1202
  if key in _MODALITY_OF_CONFIG_KEY
1194
1203
  }
1195
1204
 
1196
1205
 
1197
1206
  def _experiment_config(config: dict, modalities: list[str] | None) -> dict:
1198
- """Return *config* reduced to the modalities an experiment lists.
1207
+ """Return *config* with only the prediction modalities an experiment lists.
1199
1208
 
1200
- Unlisted GT inputs and prediction entries are left out, as if they were
1201
- not configured. A dataset left without a prediction is not evaluated, and
1202
- a ``sky_mask`` is dropped with the depth prediction it belongs to.
1209
+ Unlisted ``datasets[]`` entries are left out, as if they were not
1210
+ configured; the ``gt`` section is unchanged. A dataset left without a
1211
+ prediction is not evaluated, and a ``sky_mask`` is dropped with the depth
1212
+ prediction it belongs to.
1203
1213
  """
1204
1214
  if modalities is None:
1205
1215
  return config
@@ -1220,7 +1230,7 @@ def _experiment_config(config: dict, modalities: list[str] | None) -> dict:
1220
1230
  entry.pop("sky_mask", None)
1221
1231
  if _has_prediction(entry):
1222
1232
  datasets.append(entry)
1223
- return {**config, "gt": reduced(config["gt"]), "datasets": datasets}
1233
+ return {**config, "datasets": datasets}
1224
1234
 
1225
1235
 
1226
1236
  def load_config(config_path: str) -> dict:
@@ -1652,7 +1662,6 @@ def _experiment_runs(
1652
1662
  parser.raise_errors = True
1653
1663
  try:
1654
1664
  if modalities is not None:
1655
- validate_gt_config(run_config["gt"])
1656
1665
  if not run_config["datasets"]:
1657
1666
  raise ValueError(
1658
1667
  "'modalities' leave no dataset with a prediction to evaluate"
@@ -1679,7 +1688,7 @@ def _experiment_runs(
1679
1688
  if run_args.sky_iou and _gt_segmentation_entry(run_config["gt"])[1] is None:
1680
1689
  raise ValueError(
1681
1690
  "--sky-iou requires gt.segmentation (or its alias "
1682
- "gt.semantic_segmentation), which this experiment does not load"
1691
+ "gt.semantic_segmentation) in config"
1683
1692
  )
1684
1693
  runs.append(
1685
1694
  _prepare_run(run_args, run_config, name=name, extra_args=tuple(extra))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-eval
3
- Version: 2.33.0
3
+ Version: 2.35.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "euler-eval"
7
- version = "2.33.0"
7
+ version = "2.35.0"
8
8
  description = "Evaluation toolkit for depth, RGB, ray and 3D point-map predictions"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,12 +1,14 @@
1
1
  """Config ``experiments``: several named argument sets in one invocation."""
2
2
 
3
3
  import json
4
+ import zipfile
4
5
  from pathlib import Path
5
6
 
6
7
  import pytest
7
8
 
8
9
  from euler_eval import cli
9
10
  from euler_eval.cli import default_eval_path, validate_experiments_config
11
+ from tests.test_cli_sparse_outputs import _sparse_depth_results
10
12
  from tests.test_depth_alignment_output import _patch_depth_metrics
11
13
  from tests.test_sky_iou_cli import write_gt_segmentation
12
14
 
@@ -293,20 +295,20 @@ def test_modalities_decide_whether_the_sky_mask_is_used(
293
295
  assert capped["meta"]["pred"].get("sky_mask") is None
294
296
 
295
297
 
296
- def test_modalities_leave_out_gt_inputs(monkeypatch, tmp_path, indexed_sky_prediction):
298
+ def test_modalities_keep_every_gt_input(monkeypatch, tmp_path, indexed_sky_prediction):
299
+ """'modalities' selects predictions; GT segmentation stays for --mask-sky."""
297
300
  _patch_depth_metrics(monkeypatch)
298
301
  pred = indexed_sky_prediction["pred"]
299
302
  seg = write_gt_segmentation(tmp_path / "segmentation")
300
303
  _main(monkeypatch, _sky_config(
301
304
  tmp_path, indexed_sky_prediction,
302
- [{"name": "all"}, {"name": "no_seg", "modalities": ["depth", "sky_mask"]}],
305
+ [{"name": "eval", "modalities": ["depth"], "args": ["--mask-sky"]}],
303
306
  gt_extra={"segmentation": {"path": seg}},
304
307
  ))
305
308
 
306
- full = _results(pred, "all")["protocol"]["parameters"]
307
- reduced = _results(pred, "no_seg")["protocol"]["parameters"]
308
- assert full["reference.segmentation.dataset"] == "sky_iou_test"
309
- assert reduced["reference.segmentation.dataset"] is None
309
+ parameters = _results(pred, "eval")["protocol"]["parameters"]
310
+ assert parameters["population.gt_sky_masking"] is True
311
+ assert parameters["reference.segmentation.dataset"] == "sky_iou_test"
310
312
 
311
313
 
312
314
  def test_unconfigured_modalities_only_warn(
@@ -314,40 +316,46 @@ def test_unconfigured_modalities_only_warn(
314
316
  ):
315
317
  _patch_depth_metrics(monkeypatch)
316
318
  _main(monkeypatch, _sky_config(tmp_path, indexed_sky_prediction, [
317
- {"name": "eval", "modalities": ["depth", "calibration"]},
319
+ {"name": "eval", "modalities": ["depth", "rays"]},
318
320
  ]))
319
321
 
320
- assert "modalities ['calibration'] are not configured" in capsys.readouterr().err
322
+ assert "modalities ['rays'] are not configured" in capsys.readouterr().err
321
323
  assert _results(indexed_sky_prediction["pred"], "eval") is not None
322
324
 
323
325
 
324
326
  @pytest.mark.parametrize(
325
- ("experiment", "message"),
327
+ ("experiment", "with_segmentation", "message"),
326
328
  [
327
329
  (
328
330
  {"name": "iou", "args": ["--sky-iou"], "modalities": ["depth"]},
331
+ False,
329
332
  "experiments[0] ('iou'): --sky-iou requires gt.segmentation",
330
333
  ),
331
334
  (
335
+ # A sky_mask is dropped with the depth prediction it belongs to.
332
336
  {"name": "masks", "modalities": ["sky_mask"]},
333
- "experiments[0] ('masks'): gt must have at least one of",
337
+ True,
338
+ "experiments[0] ('masks'): 'modalities' leave no dataset with a prediction",
334
339
  ),
335
340
  (
336
341
  {"name": "rgb", "modalities": ["rgb"]},
342
+ True,
337
343
  "experiments[0] ('rgb'): 'modalities' leave no dataset with a prediction",
338
344
  ),
339
345
  ],
340
346
  )
341
347
  def test_modalities_are_checked_before_any_work(
342
- monkeypatch, capsys, tmp_path, indexed_sky_prediction, experiment, message
348
+ monkeypatch, capsys, tmp_path, indexed_sky_prediction, experiment,
349
+ with_segmentation, message,
343
350
  ):
344
351
  monkeypatch.setattr(cli, "evaluate_depth_samples", pytest.fail)
345
- gt_rgb = tmp_path / "gt_rgb"
346
- gt_rgb.mkdir()
347
- seg = write_gt_segmentation(tmp_path / "segmentation")
352
+ gt_extra = {}
353
+ if with_segmentation:
354
+ gt_extra["segmentation"] = {
355
+ "path": write_gt_segmentation(tmp_path / "segmentation")
356
+ }
348
357
  config_path = _sky_config(
349
- tmp_path, indexed_sky_prediction, [experiment],
350
- gt_extra={"rgb": {"path": str(gt_rgb)}, "segmentation": {"path": seg}},
358
+ tmp_path, indexed_sky_prediction, [experiment], gt_extra=gt_extra
351
359
  )
352
360
  with pytest.raises(SystemExit) as exc:
353
361
  _main(monkeypatch, config_path)
@@ -356,7 +364,76 @@ def test_modalities_are_checked_before_any_work(
356
364
  assert f"Error loading config: {message}" in capsys.readouterr().err
357
365
 
358
366
 
359
- def test_experiment_config_keeps_only_listed_modalities():
367
+ def test_sparse_gt_config_with_prediction_modalities(monkeypatch, tmp_path):
368
+ """The reported MUSES config: experiments choose predictions, GT stays whole."""
369
+ paths = {}
370
+ for name in ("lidar", "camera", "semantic", "depth", "dehazed", "points_3d",
371
+ "sky_mask"):
372
+ paths[name] = tmp_path / f"{name}.zip"
373
+ with zipfile.ZipFile(paths[name], "w") as zf:
374
+ zf.writestr("placeholder", b"data")
375
+ config_path = tmp_path / "config.json"
376
+ config_path.write_text(json.dumps({
377
+ "experiments": [
378
+ {"name": "eval", "modalities": ["depth"], "args": ["--mask-sky"]},
379
+ {"name": "gt_sky_with_mask", "modalities": ["depth", "sky_mask"],
380
+ "args": ["--mask-sky"]},
381
+ {"name": "gt_and_pred_sky", "modalities": ["depth", "sky_mask"],
382
+ "args": ["--mask-sky", "--mask-sky-prediction"]},
383
+ ],
384
+ "gt": {
385
+ "sparse_depth": {"path": str(paths["lidar"])},
386
+ "rgb": {"path": f"{paths['camera']}:fog_day"},
387
+ "intrinsics": {"path": f"{paths['camera']}#scope=intrinsics"},
388
+ "camera_extrinsics": {"path": f"{paths['camera']}#scope=camera_extrinsics"},
389
+ "segmentation": {"path": str(paths["semantic"])},
390
+ },
391
+ "datasets": [{
392
+ "name": "SD 2.1. Mari",
393
+ "depth": {"path": str(paths["depth"])},
394
+ "rgb": {"path": str(paths["dehazed"])},
395
+ "points_3d": {"path": str(paths["points_3d"])},
396
+ "sky_mask": {"path": str(paths["sky_mask"])},
397
+ }],
398
+ "distributions": {"n_bins": 30, "scale": "log", "min_error": 0.001,
399
+ "max_error": 240.0},
400
+ }))
401
+
402
+ built, evaluated = [], []
403
+ monkeypatch.setattr(
404
+ cli, "build_sparse_depth_eval_dataset",
405
+ lambda **kwargs: built.append(kwargs) or [object()],
406
+ )
407
+ monkeypatch.setattr(
408
+ cli, "get_sparse_depth_metadata", lambda _ds: {"pred_radial_depth": False}
409
+ )
410
+ monkeypatch.setattr(
411
+ cli, "evaluate_sparse_depth_samples",
412
+ lambda **kwargs: evaluated.append(kwargs) or _sparse_depth_results(),
413
+ )
414
+ for unused in ("evaluate_rgb_samples", "evaluate_points_3d_sparse_samples"):
415
+ monkeypatch.setattr(cli, unused, pytest.fail)
416
+ _main(monkeypatch, config_path)
417
+
418
+ gt_masking = [kwargs["sky_mask_enabled"] for kwargs in evaluated]
419
+ pred_masking = [kwargs["pred_sky_mask_enabled"] for kwargs in evaluated]
420
+ assert gt_masking == [True, True, True]
421
+ assert pred_masking == [False, False, True]
422
+ assert [kwargs["segmentation_path"] for kwargs in built] == [str(paths["semantic"])] * 3
423
+ # The sky_mask is only read by a flag that uses it (here --mask-sky-prediction).
424
+ assert [kwargs["pred_sky_mask_path"] for kwargs in built] == [
425
+ None, None, str(paths["sky_mask"]),
426
+ ]
427
+ with zipfile.ZipFile(paths["depth"]) as zf:
428
+ saved = {
429
+ name: json.loads(zf.read(f".eval/{name}.json"))
430
+ for name in ("eval", "gt_sky_with_mask", "gt_and_pred_sky")
431
+ }
432
+ assert [s["protocol"]["parameters"]["method.pred_sky_mask"]
433
+ for s in saved.values()] == [False, False, True]
434
+
435
+
436
+ def test_experiment_config_keeps_only_listed_predictions():
360
437
  config = {
361
438
  "gt": {
362
439
  "name": "GT",
@@ -375,17 +452,17 @@ def test_experiment_config_keeps_only_listed_modalities():
375
452
  "distributions": True,
376
453
  }
377
454
 
378
- reduced = cli._experiment_config(config, ["depth", "segmentation"])
455
+ reduced = cli._experiment_config(config, ["depth"])
379
456
 
380
- assert reduced["gt"] == {
381
- "name": "GT",
382
- "depth": {"path": "/gt/depth"},
383
- "semantic_segmentation": {"path": "/gt/seg"},
384
- }
457
+ assert reduced["gt"] == config["gt"]
385
458
  assert reduced["datasets"] == [
386
459
  {"name": "a", "output_file": "/out/a.json", "relative_depth": {"path": "/a/depth"}},
387
460
  ]
388
461
  assert reduced["distributions"] is True
462
+ assert cli._experiment_config(config, ["depth", "sky_mask"])["datasets"] == [{
463
+ "name": "a", "output_file": "/out/a.json",
464
+ "relative_depth": {"path": "/a/depth"}, "sky_mask": {"path": "/a/sky"},
465
+ }]
389
466
  assert cli._experiment_config(config, ["rgb", "sky_mask"])["datasets"] == [
390
467
  {"name": "a", "output_file": "/out/a.json", "rgb": {"path": "/a/rgb"}},
391
468
  {"name": "b", "rgb": {"path": "/b/rgb"}},
@@ -399,7 +476,10 @@ def test_experiment_config_keeps_only_listed_modalities():
399
476
  ([], "non-empty array of modality names"),
400
477
  ("depth", "non-empty array of modality names"),
401
478
  (["relative_depth"], r"unknown modality 'relative_depth' \(use 'depth'"),
402
- (["semantic_segmentation"], r"\(use 'segmentation', which covers it\)"),
479
+ (["segmentation"], "'segmentation' is a GT input"),
480
+ (["semantic_segmentation"], "'semantic_segmentation' is a GT input"),
481
+ (["sparse_depth"], "'sparse_depth' is a GT input"),
482
+ (["intrinsics"], "'intrinsics' is a GT input"),
403
483
  (["normals"], "unknown modality 'normals'"),
404
484
  (["depth", "depth"], r"lists \['depth'\] more than once"),
405
485
  ],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes