euler-eval 2.9.0__tar.gz → 2.11.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 (56) hide show
  1. {euler_eval-2.9.0 → euler_eval-2.11.0}/PKG-INFO +5 -3
  2. {euler_eval-2.9.0 → euler_eval-2.11.0}/README.md +4 -2
  3. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/cli.py +23 -4
  4. euler_eval-2.11.0/euler_eval/config_paths.py +125 -0
  5. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/data.py +9 -8
  6. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval.egg-info/PKG-INFO +5 -3
  7. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval.egg-info/SOURCES.txt +1 -0
  8. {euler_eval-2.9.0 → euler_eval-2.11.0}/pyproject.toml +1 -1
  9. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_config.py +44 -0
  10. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_data_builders.py +34 -0
  11. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_save_results.py +30 -0
  12. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/__init__.py +0 -0
  13. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/evaluate.py +0 -0
  14. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/__init__.py +0 -0
  15. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/absrel.py +0 -0
  16. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/daniel_error.py +0 -0
  17. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/depth_binned_error.py +0 -0
  18. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/depth_edge_f1.py +0 -0
  19. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/depth_standard.py +0 -0
  20. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/fid_kid.py +0 -0
  21. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/gpu_depth_batch.py +0 -0
  22. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/gpu_image_batch.py +0 -0
  23. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/high_freq_energy.py +0 -0
  24. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/lpips_metric.py +0 -0
  25. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/normal_consistency.py +0 -0
  26. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/psnr.py +0 -0
  27. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/rgb_edge_f1.py +0 -0
  28. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/rgb_lpips.py +0 -0
  29. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/rgb_psnr_ssim.py +0 -0
  30. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/rho_a.py +0 -0
  31. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/rmse.py +0 -0
  32. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/scale_invariant_log.py +0 -0
  33. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/ssim.py +0 -0
  34. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/tail_errors.py +0 -0
  35. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/metrics/utils.py +0 -0
  36. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/sanity_checker.py +0 -0
  37. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval/utils/hierarchy_parser.py +0 -0
  38. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval.egg-info/dependency_links.txt +0 -0
  39. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval.egg-info/entry_points.txt +0 -0
  40. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval.egg-info/requires.txt +0 -0
  41. {euler_eval-2.9.0 → euler_eval-2.11.0}/euler_eval.egg-info/top_level.txt +0 -0
  42. {euler_eval-2.9.0 → euler_eval-2.11.0}/init_cache.py +0 -0
  43. {euler_eval-2.9.0 → euler_eval-2.11.0}/setup.cfg +0 -0
  44. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_alignment.py +0 -0
  45. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_cli_device.py +0 -0
  46. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_data.py +0 -0
  47. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_depth_alignment_output.py +0 -0
  48. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_depth_standard.py +0 -0
  49. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_evaluate_helpers.py +0 -0
  50. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_init_cache.py +0 -0
  51. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_integration.py +0 -0
  52. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_meta_output.py +0 -0
  53. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_metrics_utils.py +0 -0
  54. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_rgb_fid_output.py +0 -0
  55. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_rho_a.py +0 -0
  56. {euler_eval-2.9.0 → euler_eval-2.11.0}/tests/test_sparse_depth.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-eval
3
- Version: 2.9.0
3
+ Version: 2.11.0
4
4
  Summary: Depth map evaluation toolkit with comprehensive metrics
5
5
  Author: Depth Eval Contributors
6
6
  License: MIT
@@ -239,7 +239,7 @@ Defines GT modalities, prediction datasets to evaluate, and optional euler_train
239
239
  }
240
240
  ```
241
241
 
242
- Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`).
242
+ Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`). euler-loading inline selectors are also accepted in `path`, such as `/data/muses.zip:test` or `/data/muses.zip:test#scope=rgb`.
243
243
 
244
244
  For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction still uses `datasets[].depth` because the model output is a dense depth map. The evaluator projects the sparse GT point cloud into the prediction image plane using `gt.intrinsics` and `gt.camera_extrinsics`, then computes pointwise depth metrics only at projected valid pixels.
245
245
 
@@ -293,7 +293,7 @@ Dataset metadata (e.g. `radial_depth`, `rgb_range`, sparse point columns, and co
293
293
 
294
294
  ### Dataset Metadata
295
295
 
296
- Each dataset directory must contain ds-crawler metadata artifacts, typically generated under `.ds_crawler/`:
296
+ Each dataset directory or archive must contain ds-crawler metadata artifacts, typically generated under `.ds_crawler/`:
297
297
 
298
298
  ```text
299
299
  dataset-root/
@@ -303,6 +303,8 @@ dataset-root/
303
303
  index.json
304
304
  ```
305
305
 
306
+ When one physical root or archive contains several logical modalities, the artifacts may instead be scoped under `.ds_crawler/<modality>/` with a `.ds_crawler/scopes.json` manifest. `euler-eval` passes modality scopes to euler-loading so paths such as `/data/muses.zip:test` can load `.ds_crawler/rgb/index.json`, `.ds_crawler/depth/index.json`, and related scoped artifacts from the same archive.
307
+
306
308
  GT and prediction datasets are matched by hierarchy path and file ID through `MultiModalDataset`.
307
309
 
308
310
  ### `metrics_config.json`
@@ -201,7 +201,7 @@ Defines GT modalities, prediction datasets to evaluate, and optional euler_train
201
201
  }
202
202
  ```
203
203
 
204
- Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`).
204
+ Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`). euler-loading inline selectors are also accepted in `path`, such as `/data/muses.zip:test` or `/data/muses.zip:test#scope=rgb`.
205
205
 
206
206
  For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction still uses `datasets[].depth` because the model output is a dense depth map. The evaluator projects the sparse GT point cloud into the prediction image plane using `gt.intrinsics` and `gt.camera_extrinsics`, then computes pointwise depth metrics only at projected valid pixels.
207
207
 
@@ -255,7 +255,7 @@ Dataset metadata (e.g. `radial_depth`, `rgb_range`, sparse point columns, and co
255
255
 
256
256
  ### Dataset Metadata
257
257
 
258
- Each dataset directory must contain ds-crawler metadata artifacts, typically generated under `.ds_crawler/`:
258
+ Each dataset directory or archive must contain ds-crawler metadata artifacts, typically generated under `.ds_crawler/`:
259
259
 
260
260
  ```text
261
261
  dataset-root/
@@ -265,6 +265,8 @@ dataset-root/
265
265
  index.json
266
266
  ```
267
267
 
268
+ When one physical root or archive contains several logical modalities, the artifacts may instead be scoped under `.ds_crawler/<modality>/` with a `.ds_crawler/scopes.json` manifest. `euler-eval` passes modality scopes to euler-loading so paths such as `/data/muses.zip:test` can load `.ds_crawler/rgb/index.json`, `.ds_crawler/depth/index.json`, and related scoped artifacts from the same archive.
269
+
268
270
  GT and prediction datasets are matched by hierarchy path and file ID through `MultiModalDataset`.
269
271
 
270
272
  ### `metrics_config.json`
@@ -14,6 +14,7 @@ from pathlib import Path
14
14
 
15
15
  import torch
16
16
 
17
+ from .config_paths import normalize_modality_path
17
18
  from .data import (
18
19
  build_depth_eval_dataset,
19
20
  build_rays_eval_dataset,
@@ -599,7 +600,10 @@ def validate_gt_config(gt: dict) -> None:
599
600
  "camera_extrinsics",
600
601
  ):
601
602
  if modality in gt and "path" in gt[modality]:
602
- p = Path(gt[modality]["path"])
603
+ p = normalize_modality_path(
604
+ gt[modality]["path"],
605
+ split=gt[modality].get("split"),
606
+ )
603
607
  if not p.exists():
604
608
  raise ValueError(f"gt.{modality}.path does not exist: {p}")
605
609
 
@@ -624,7 +628,10 @@ def validate_dataset_entry(entry: dict, index: int) -> None:
624
628
 
625
629
  for modality in ("rgb", "depth", "rays"):
626
630
  if modality in entry and "path" in entry[modality]:
627
- p = Path(entry[modality]["path"])
631
+ p = normalize_modality_path(
632
+ entry[modality]["path"],
633
+ split=entry[modality].get("split"),
634
+ )
628
635
  if not p.exists():
629
636
  raise ValueError(f"{label}.{modality}.path does not exist: {p}")
630
637
 
@@ -749,12 +756,24 @@ def save_results(
749
756
  and modality in dataset_config
750
757
  and "path" in dataset_config[modality]
751
758
  ):
752
- output_file = Path(dataset_config[modality]["path"]) / "eval.json"
759
+ output_file = (
760
+ normalize_modality_path(
761
+ dataset_config[modality]["path"],
762
+ split=dataset_config[modality].get("split"),
763
+ )
764
+ / "eval.json"
765
+ )
753
766
  else:
754
767
  # Default: save alongside first available modality path
755
768
  for mod in ("depth", "rgb", "rays"):
756
769
  if mod in dataset_config and "path" in dataset_config[mod]:
757
- output_file = Path(dataset_config[mod]["path"]) / "eval.json"
770
+ output_file = (
771
+ normalize_modality_path(
772
+ dataset_config[mod]["path"],
773
+ split=dataset_config[mod].get("split"),
774
+ )
775
+ / "eval.json"
776
+ )
758
777
  break
759
778
  if output_file is None:
760
779
  output_file = Path("eval.json")
@@ -0,0 +1,125 @@
1
+ """Helpers for euler-loading modality path selectors in config files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from urllib.parse import parse_qsl
8
+
9
+ from ds_crawler import validate_metadata_scope
10
+ from ds_crawler.zip_utils import validate_split_name
11
+ from euler_loading import Modality
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ModalityPath:
16
+ path: str
17
+ split: str | None = None
18
+ metadata_scope: str | None = None
19
+
20
+
21
+ def _parse_inline_scope(path: str) -> tuple[str, str | None]:
22
+ candidate_path, sep, fragment = path.rpartition("#")
23
+ if not sep:
24
+ return path, None
25
+
26
+ params = parse_qsl(fragment, keep_blank_values=True)
27
+ scope_values = [value for key, value in params if key == "scope"]
28
+ if not scope_values:
29
+ return path, None
30
+
31
+ unsupported = [key for key, _ in params if key != "scope"]
32
+ if unsupported:
33
+ raise ValueError(
34
+ "Unsupported modality path fragment parameter(s): "
35
+ f"{', '.join(sorted(set(unsupported)))}. "
36
+ "Only '#scope=<metadata_scope>' is supported."
37
+ )
38
+ if len(scope_values) > 1:
39
+ raise ValueError("Modality path may contain at most one '#scope=' selector.")
40
+
41
+ return candidate_path, validate_metadata_scope(scope_values[0])
42
+
43
+
44
+ def _parse_inline_split(path: str) -> tuple[str, str | None]:
45
+ colon_pos = path.rfind(":")
46
+ if colon_pos <= 1:
47
+ return path, None
48
+
49
+ candidate_split = path[colon_pos + 1 :]
50
+ candidate_path = path[:colon_pos]
51
+
52
+ try:
53
+ split = validate_split_name(candidate_split)
54
+ except ValueError:
55
+ return path, None
56
+
57
+ return candidate_path, split
58
+
59
+
60
+ def parse_modality_path(
61
+ path: str | Path,
62
+ *,
63
+ split: str | None = None,
64
+ metadata_scope: str | None = None,
65
+ ) -> ModalityPath:
66
+ """Parse euler-loading inline split/scope selectors from a config path."""
67
+ raw_path = str(path)
68
+ parsed_path, inline_scope = _parse_inline_scope(raw_path)
69
+ parsed_path, inline_split = _parse_inline_split(parsed_path)
70
+
71
+ if inline_split is not None and split is not None:
72
+ raise ValueError(
73
+ f"Modality path {raw_path!r} contains an inline split "
74
+ f"({inline_split!r}) but an explicit split={split!r} was also "
75
+ "provided. Use one or the other, not both."
76
+ )
77
+
78
+ normalized_scope = (
79
+ validate_metadata_scope(metadata_scope)
80
+ if metadata_scope is not None
81
+ else None
82
+ )
83
+ if inline_scope is not None:
84
+ if normalized_scope is not None and normalized_scope != inline_scope:
85
+ raise ValueError(
86
+ f"Modality path {raw_path!r} contains an inline metadata scope "
87
+ f"({inline_scope!r}) but an explicit "
88
+ f"metadata_scope={metadata_scope!r} was also provided. Use one "
89
+ "metadata scope selector."
90
+ )
91
+ normalized_scope = inline_scope
92
+
93
+ return ModalityPath(
94
+ path=parsed_path,
95
+ split=split if split is not None else inline_split,
96
+ metadata_scope=normalized_scope,
97
+ )
98
+
99
+
100
+ def normalize_modality_path(path: str | Path, *, split: str | None = None) -> Path:
101
+ """Return the filesystem/archive root for a config modality path."""
102
+ return Path(parse_modality_path(path, split=split).path)
103
+
104
+
105
+ def build_modality(
106
+ *,
107
+ path: str | Path,
108
+ modality_key: str,
109
+ split: str | None = None,
110
+ loader=None,
111
+ used_as: str | None = None,
112
+ ) -> Modality:
113
+ """Create a Modality after parsing inline config selectors locally."""
114
+ parsed = parse_modality_path(
115
+ path,
116
+ split=split,
117
+ )
118
+ return Modality(
119
+ path=parsed.path,
120
+ loader=loader,
121
+ used_as=used_as,
122
+ modality_type=modality_key,
123
+ metadata_scope=parsed.metadata_scope or modality_key,
124
+ split=parsed.split,
125
+ )
@@ -13,6 +13,7 @@ import torch
13
13
  from ds_crawler import get_dataset_contract, index_dataset_from_path
14
14
  from euler_loading import Modality, MultiModalDataset, resolve_loader_module
15
15
 
16
+ from .config_paths import build_modality
16
17
  from .metrics.utils import convert_planar_to_radial
17
18
 
18
19
  # ---------------------------------------------------------------------------
@@ -525,13 +526,14 @@ def _resolve_sky_mask_loader(segmentation_path: str) -> Callable[..., Any]:
525
526
  its ``sky_mask`` function so that raw class-segmentation data is
526
527
  converted to a boolean sky mask at load time.
527
528
  """
529
+ modality = _modality(path=segmentation_path, modality_key="segmentation")
528
530
  try:
529
531
  index = index_dataset_from_path(
530
- segmentation_path,
531
- metadata_scope="segmentation",
532
+ modality.path,
533
+ metadata_scope=modality.metadata_scope,
532
534
  )
533
535
  except FileNotFoundError:
534
- index = index_dataset_from_path(segmentation_path)
536
+ index = index_dataset_from_path(modality.path)
535
537
  # Resolve euler_loading metadata the same way euler-loading does:
536
538
  # try the contract addon API first (handles addons.euler_loading),
537
539
  # then fall back to top-level euler_loading for legacy indices.
@@ -546,14 +548,14 @@ def _resolve_sky_mask_loader(segmentation_path: str) -> Callable[..., Any]:
546
548
  loader_name = euler_meta.get("loader")
547
549
  if loader_name is None:
548
550
  raise ValueError(
549
- f"Segmentation dataset at {segmentation_path!r} does not declare "
551
+ f"Segmentation dataset at {modality.path!r} does not declare "
550
552
  f"an 'euler_loading.loader' in its ds-crawler index."
551
553
  )
552
554
  module = resolve_loader_module(loader_name)
553
555
  sky_fn = getattr(module, "sky_mask", None)
554
556
  if sky_fn is None or not callable(sky_fn):
555
557
  raise ValueError(
556
- f"Loader module {loader_name!r} (resolved from {segmentation_path!r}) "
558
+ f"Loader module {loader_name!r} (resolved from {modality.path!r}) "
557
559
  f"does not expose a 'sky_mask' function."
558
560
  )
559
561
  return sky_fn
@@ -573,12 +575,11 @@ def _modality(
573
575
  used_as: Optional[str] = None,
574
576
  ) -> Modality:
575
577
  """Create an euler-loading modality with an explicit metadata selector."""
576
- return Modality(
578
+ return build_modality(
577
579
  path=path,
578
580
  loader=loader,
579
581
  used_as=used_as,
580
- modality_type=modality_key,
581
- metadata_scope=modality_key,
582
+ modality_key=modality_key,
582
583
  split=split,
583
584
  )
584
585
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-eval
3
- Version: 2.9.0
3
+ Version: 2.11.0
4
4
  Summary: Depth map evaluation toolkit with comprehensive metrics
5
5
  Author: Depth Eval Contributors
6
6
  License: MIT
@@ -239,7 +239,7 @@ Defines GT modalities, prediction datasets to evaluate, and optional euler_train
239
239
  }
240
240
  ```
241
241
 
242
- Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`).
242
+ Each modality entry can optionally include a `split` field to select a specific split from the dataset (e.g. `{ "path": "/data/gt/depth", "split": "test" }`). euler-loading inline selectors are also accepted in `path`, such as `/data/muses.zip:test` or `/data/muses.zip:test#scope=rgb`.
243
243
 
244
244
  For sparse pointcloud depth evaluation, use `gt.sparse_depth` instead of `gt.depth`. The prediction still uses `datasets[].depth` because the model output is a dense depth map. The evaluator projects the sparse GT point cloud into the prediction image plane using `gt.intrinsics` and `gt.camera_extrinsics`, then computes pointwise depth metrics only at projected valid pixels.
245
245
 
@@ -293,7 +293,7 @@ Dataset metadata (e.g. `radial_depth`, `rgb_range`, sparse point columns, and co
293
293
 
294
294
  ### Dataset Metadata
295
295
 
296
- Each dataset directory must contain ds-crawler metadata artifacts, typically generated under `.ds_crawler/`:
296
+ Each dataset directory or archive must contain ds-crawler metadata artifacts, typically generated under `.ds_crawler/`:
297
297
 
298
298
  ```text
299
299
  dataset-root/
@@ -303,6 +303,8 @@ dataset-root/
303
303
  index.json
304
304
  ```
305
305
 
306
+ When one physical root or archive contains several logical modalities, the artifacts may instead be scoped under `.ds_crawler/<modality>/` with a `.ds_crawler/scopes.json` manifest. `euler-eval` passes modality scopes to euler-loading so paths such as `/data/muses.zip:test` can load `.ds_crawler/rgb/index.json`, `.ds_crawler/depth/index.json`, and related scoped artifacts from the same archive.
307
+
306
308
  GT and prediction datasets are matched by hierarchy path and file ID through `MultiModalDataset`.
307
309
 
308
310
  ### `metrics_config.json`
@@ -3,6 +3,7 @@ init_cache.py
3
3
  pyproject.toml
4
4
  euler_eval/__init__.py
5
5
  euler_eval/cli.py
6
+ euler_eval/config_paths.py
6
7
  euler_eval/data.py
7
8
  euler_eval/evaluate.py
8
9
  euler_eval/sanity_checker.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "euler-eval"
7
- version = "2.9.0"
7
+ version = "2.11.0"
8
8
  description = "Depth map evaluation toolkit with comprehensive metrics"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,6 +1,7 @@
1
1
  """Tests for main.py config validation."""
2
2
 
3
3
  import json
4
+ import zipfile
4
5
  import pytest
5
6
 
6
7
  from euler_eval.cli import load_config, validate_dataset_entry, validate_gt_config
@@ -50,6 +51,39 @@ class TestValidateGtConfig:
50
51
  gt = {"rgb": {"path": str(rgb_path)}}
51
52
  validate_gt_config(gt)
52
53
 
54
+ def test_inline_zip_split_gt_path_is_valid(self, tmp_path):
55
+ rgb_path = tmp_path / "reference.zip"
56
+ with zipfile.ZipFile(rgb_path, "w") as zf:
57
+ zf.writestr("dummy.txt", "x")
58
+ gt = {"rgb": {"path": f"{rgb_path}:fog_day"}}
59
+ validate_gt_config(gt)
60
+
61
+ def test_inline_scope_gt_path_is_valid(self, tmp_path):
62
+ rgb_path = tmp_path / "reference.zip"
63
+ with zipfile.ZipFile(rgb_path, "w") as zf:
64
+ zf.writestr("dummy.txt", "x")
65
+ gt = {"rgb": {"path": f"{rgb_path}#scope=rgb"}}
66
+ validate_gt_config(gt)
67
+
68
+ def test_inline_scope_intrinsics_gt_path_is_valid(self, tmp_path):
69
+ depth_path = tmp_path / "depth"
70
+ depth_path.mkdir()
71
+ intrinsics_path = tmp_path / "frame_camera_trainvaltest.zip"
72
+ with zipfile.ZipFile(intrinsics_path, "w") as zf:
73
+ zf.writestr("dummy.txt", "x")
74
+ gt = {
75
+ "depth": {"path": str(depth_path)},
76
+ "intrinsics": {"path": f"{intrinsics_path}#scope=intrinsics"},
77
+ }
78
+ validate_gt_config(gt)
79
+
80
+ def test_inline_split_and_scope_gt_path_is_valid(self, tmp_path):
81
+ rgb_path = tmp_path / "reference.zip"
82
+ with zipfile.ZipFile(rgb_path, "w") as zf:
83
+ zf.writestr("dummy.txt", "x")
84
+ gt = {"rgb": {"path": f"{rgb_path}:fog_day#scope=rgb"}}
85
+ validate_gt_config(gt)
86
+
53
87
  def test_sparse_depth_requires_projection_modalities(self, tmp_path):
54
88
  sparse_path = tmp_path / "sparse_depth"
55
89
  sparse_path.mkdir()
@@ -132,6 +166,16 @@ class TestValidateDatasetEntry:
132
166
  with pytest.raises(ValueError, match="does not exist"):
133
167
  validate_dataset_entry(entry, 0)
134
168
 
169
+ def test_inline_zip_split_dataset_path_is_valid(self, tmp_path):
170
+ rgb_path = tmp_path / "prediction.zip"
171
+ with zipfile.ZipFile(rgb_path, "w") as zf:
172
+ zf.writestr("dummy.txt", "x")
173
+ entry = {
174
+ "name": "model_a",
175
+ "rgb": {"path": f"{rgb_path}:fog_day"},
176
+ }
177
+ validate_dataset_entry(entry, 0)
178
+
135
179
  def test_index_in_error_message(self, tmp_path):
136
180
  entry = {"name": "test"}
137
181
  with pytest.raises(ValueError, match=r"datasets\[3\]"):
@@ -169,3 +169,37 @@ def test_sky_mask_loader_resolution_uses_segmentation_scope(monkeypatch):
169
169
  assert calls == [
170
170
  ("/datasets/shared", {"metadata_scope": "segmentation"}),
171
171
  ]
172
+
173
+
174
+ def test_sky_mask_loader_resolution_strips_inline_split(monkeypatch):
175
+ calls = []
176
+
177
+ def fake_index_dataset_from_path(path, **kwargs):
178
+ calls.append((path, kwargs))
179
+ return {"euler_loading": {"loader": "vkitti2"}}
180
+
181
+ def sky_mask(path, meta=None):
182
+ return None
183
+
184
+ monkeypatch.setattr(data, "index_dataset_from_path", fake_index_dataset_from_path)
185
+ monkeypatch.setattr(
186
+ data,
187
+ "resolve_loader_module",
188
+ lambda name: SimpleNamespace(sky_mask=sky_mask),
189
+ )
190
+
191
+ assert data._resolve_sky_mask_loader("/datasets/shared.zip:fog_day") is sky_mask
192
+ assert calls == [
193
+ ("/datasets/shared.zip", {"metadata_scope": "segmentation"}),
194
+ ]
195
+
196
+
197
+ def test_modality_parses_inline_split_and_scope():
198
+ modality = data._modality(
199
+ path="/datasets/frame_camera_trainvaltest.zip:fog_day#scope=intrinsics",
200
+ modality_key="intrinsics",
201
+ )
202
+
203
+ assert modality.path == "/datasets/frame_camera_trainvaltest.zip"
204
+ assert modality.split == "fog_day"
205
+ assert modality.metadata_scope == "intrinsics"
@@ -214,6 +214,36 @@ class TestSaveResultsZip:
214
214
  stored = json.loads(zf.read("eval.json"))
215
215
  assert stored == results
216
216
 
217
+ def test_inline_zip_split_writes_eval_json_into_zip(self, tmp_path):
218
+ zp = tmp_path / "dataset.zip"
219
+ with zipfile.ZipFile(zp, "w") as zf:
220
+ zf.writestr("image_001.png", b"\x89PNG")
221
+
222
+ results = {"depth": {"rmse": 0.42}}
223
+ config = {"name": "model", "depth": {"path": f"{zp}:fog_day"}}
224
+ out = save_results(results, config)
225
+
226
+ assert out == zp / "eval.json"
227
+ with zipfile.ZipFile(zp, "r") as zf:
228
+ assert "eval.json" in zf.namelist()
229
+ stored = json.loads(zf.read("eval.json"))
230
+ assert stored == results
231
+
232
+ def test_inline_zip_scope_writes_eval_json_into_zip(self, tmp_path):
233
+ zp = tmp_path / "dataset.zip"
234
+ with zipfile.ZipFile(zp, "w") as zf:
235
+ zf.writestr("image_001.png", b"\x89PNG")
236
+
237
+ results = {"rgb": {"psnr": 30.0}}
238
+ config = {"name": "model", "rgb": {"path": f"{zp}#scope=rgb"}}
239
+ out = save_results(results, config, modality="rgb")
240
+
241
+ assert out == zp / "eval.json"
242
+ with zipfile.ZipFile(zp, "r") as zf:
243
+ assert "eval.json" in zf.namelist()
244
+ stored = json.loads(zf.read("eval.json"))
245
+ assert stored == results
246
+
217
247
  def test_preserves_existing_zip_contents(self, tmp_path):
218
248
  zp = tmp_path / "dataset.zip"
219
249
  with zipfile.ZipFile(zp, "w") as zf:
File without changes
File without changes