euler-preprocess 3.15.0__tar.gz → 3.16.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.
- {euler_preprocess-3.15.0/euler_preprocess.egg-info → euler_preprocess-3.16.0}/PKG-INFO +18 -10
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/README.md +17 -9
- euler_preprocess-3.16.0/euler_preprocess/common/dataset.py +212 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/intrinsics.py +3 -2
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/transform.py +9 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/transform.py +3 -2
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/radial/transform.py +3 -3
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/spatial.py +9 -1
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0/euler_preprocess.egg-info}/PKG-INFO +18 -10
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess.egg-info/SOURCES.txt +1 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/pyproject.toml +1 -1
- euler_preprocess-3.16.0/tests/test_modality_roles.py +192 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_spatial.py +11 -0
- euler_preprocess-3.15.0/euler_preprocess/common/dataset.py +0 -73
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/CHANGELOG.md +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/LICENSE +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/MANIFEST.in +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/docs/spatial.md +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/__init__.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/cli.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/__init__.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/color.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/device.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/io.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/logging.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/noise.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/normalize.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/output.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/common/sampling.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/__init__.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/airlight_from_sky.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/atmospheric_light.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/augmentations.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/capture.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/dcp_airlight.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/dcp_airlight_torch.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/dcp_heuristic_airlight_torch.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/inference.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/logging.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/models.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/pipeline.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/radial/__init__.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/sky_depth/__init__.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/sky_depth/transform.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess.egg-info/dependency_links.txt +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess.egg-info/entry_points.txt +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess.egg-info/requires.txt +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess.egg-info/top_level.txt +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/examples/spatial-five-field.json +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/setup.cfg +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_airlight_fallback.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_cli_sample_selection.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_dcp_heuristic_airlight.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_dense_gloomy_daylight_config.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_fog_aux_outputs.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_fog_aware_auto_exposure.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_fog_cpu_gpu_parity.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_fog_integration.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_radial.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_real_drive_sim_scenario_profiles.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_scene_illumination.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_sensor_identity.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_sky_depth.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_source_backed_output.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_tone_map_lut.py +0 -0
- {euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_zip_output.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-preprocess
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.16.0
|
|
4
4
|
Summary: Physics-based preprocessing (fog, etc.) for RGB+depth datasets
|
|
5
5
|
Author-email: Daniel Rothenpieler <rothenpielerdaniel@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -94,9 +94,7 @@ loader.
|
|
|
94
94
|
"modalities": {
|
|
95
95
|
"rgb": {"path": "/path/to/rgb", "split": "train"},
|
|
96
96
|
"depth": "/path/to/depth",
|
|
97
|
-
"semantic_segmentation": "/path/to/classSegmentation"
|
|
98
|
-
},
|
|
99
|
-
"hierarchical_modalities": {
|
|
97
|
+
"semantic_segmentation": "/path/to/classSegmentation",
|
|
100
98
|
"intrinsics": {"path": "/path/to/intrinsics"}
|
|
101
99
|
}
|
|
102
100
|
}
|
|
@@ -109,17 +107,27 @@ loader.
|
|
|
109
107
|
| `output_slot` | Optional slot selector when `pipeline.output_targets` has several entries. Defaults to `rgb` for `fog` and `depth` for `sky-depth` / `radial`. |
|
|
110
108
|
| `sample` | Optional 0-based dataset index. Only `dataset[sample]` is transformed — useful for small benchmark slices of large datasets. |
|
|
111
109
|
| `samples` | Optional multi-sample selector: a list of indices (`[0, 10, 20]`) or a slice object (`{"start": 0, "stop": 1000, "step": 2, "count": 100}`). `stop` is exclusive, `count` caps the result. Mutually exclusive with `sample`. |
|
|
112
|
-
| `modalities` |
|
|
113
|
-
| `hierarchical_modalities` |
|
|
110
|
+
| `modalities` | All input modalities. Each value is a path string or an object with `path` and optional `split`. Whether a modality is per-sample or hierarchical is inferred (see below). |
|
|
111
|
+
| `hierarchical_modalities` | Legacy, optional. Same format; entries are pooled with `modalities` and resolved the same way. |
|
|
114
112
|
| `pipeline` | Optional runtime routing block compatible with `euler-inference`. |
|
|
115
113
|
|
|
116
114
|
**Required modalities per transform:**
|
|
117
115
|
|
|
118
|
-
| Transform |
|
|
116
|
+
| Transform | Required | Optional |
|
|
119
117
|
|---|---|---|
|
|
120
|
-
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics
|
|
118
|
+
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics`; used for radial depth conversion and camera-profile optics |
|
|
121
119
|
| `sky-depth` | `depth`, `semantic_segmentation` | — |
|
|
122
|
-
| `radial` | `depth
|
|
120
|
+
| `radial` | `depth`, `intrinsics` | — |
|
|
121
|
+
|
|
122
|
+
**Per-sample vs. hierarchical modalities.** You don't declare a modality's role:
|
|
123
|
+
it is inferred from the ds-crawler indexes. A modality whose qualified IDs
|
|
124
|
+
(hierarchy path plus file ID) match the transform's per-sample inputs is joined per
|
|
125
|
+
sample, e.g. Synscapes' per-image `meta/<id>.json` intrinsics. A modality whose
|
|
126
|
+
files share no sample IDs but sit at or above the samples' hierarchy level is
|
|
127
|
+
matched hierarchically and shared by every sample beneath it, e.g. one intrinsics
|
|
128
|
+
file per scene or camera. A modality that is neither raises an error, and the
|
|
129
|
+
inferred roles are logged. Listing a per-sample modality under
|
|
130
|
+
`hierarchical_modalities` logs a warning and loads it per sample.
|
|
123
131
|
|
|
124
132
|
When a modality directory contains ds-crawler split files
|
|
125
133
|
(`.ds_crawler/split_<name>.json`), set `split` on that modality to select a subset.
|
|
@@ -677,7 +685,7 @@ d_radial(u, v) = d_planar(u, v) * sqrt(((u - cx)/fx)^2 + ((v - cy)/fy)^2 + 1)
|
|
|
677
685
|
```
|
|
678
686
|
|
|
679
687
|
The transform config takes no parameters (`{}`); intrinsics are read from the
|
|
680
|
-
`intrinsics`
|
|
688
|
+
`intrinsics` modality, stored either per sample or per scene/camera.
|
|
681
689
|
|
|
682
690
|
CLI runs write a source-backed depth dataset mirroring the input depth modality's layout
|
|
683
691
|
and writer metadata, with `meta.radial_depth` set to `true` in the emitted `index.json`.
|
|
@@ -74,9 +74,7 @@ loader.
|
|
|
74
74
|
"modalities": {
|
|
75
75
|
"rgb": {"path": "/path/to/rgb", "split": "train"},
|
|
76
76
|
"depth": "/path/to/depth",
|
|
77
|
-
"semantic_segmentation": "/path/to/classSegmentation"
|
|
78
|
-
},
|
|
79
|
-
"hierarchical_modalities": {
|
|
77
|
+
"semantic_segmentation": "/path/to/classSegmentation",
|
|
80
78
|
"intrinsics": {"path": "/path/to/intrinsics"}
|
|
81
79
|
}
|
|
82
80
|
}
|
|
@@ -89,17 +87,27 @@ loader.
|
|
|
89
87
|
| `output_slot` | Optional slot selector when `pipeline.output_targets` has several entries. Defaults to `rgb` for `fog` and `depth` for `sky-depth` / `radial`. |
|
|
90
88
|
| `sample` | Optional 0-based dataset index. Only `dataset[sample]` is transformed — useful for small benchmark slices of large datasets. |
|
|
91
89
|
| `samples` | Optional multi-sample selector: a list of indices (`[0, 10, 20]`) or a slice object (`{"start": 0, "stop": 1000, "step": 2, "count": 100}`). `stop` is exclusive, `count` caps the result. Mutually exclusive with `sample`. |
|
|
92
|
-
| `modalities` |
|
|
93
|
-
| `hierarchical_modalities` |
|
|
90
|
+
| `modalities` | All input modalities. Each value is a path string or an object with `path` and optional `split`. Whether a modality is per-sample or hierarchical is inferred (see below). |
|
|
91
|
+
| `hierarchical_modalities` | Legacy, optional. Same format; entries are pooled with `modalities` and resolved the same way. |
|
|
94
92
|
| `pipeline` | Optional runtime routing block compatible with `euler-inference`. |
|
|
95
93
|
|
|
96
94
|
**Required modalities per transform:**
|
|
97
95
|
|
|
98
|
-
| Transform |
|
|
96
|
+
| Transform | Required | Optional |
|
|
99
97
|
|---|---|---|
|
|
100
|
-
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics
|
|
98
|
+
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics`; used for radial depth conversion and camera-profile optics |
|
|
101
99
|
| `sky-depth` | `depth`, `semantic_segmentation` | — |
|
|
102
|
-
| `radial` | `depth
|
|
100
|
+
| `radial` | `depth`, `intrinsics` | — |
|
|
101
|
+
|
|
102
|
+
**Per-sample vs. hierarchical modalities.** You don't declare a modality's role:
|
|
103
|
+
it is inferred from the ds-crawler indexes. A modality whose qualified IDs
|
|
104
|
+
(hierarchy path plus file ID) match the transform's per-sample inputs is joined per
|
|
105
|
+
sample, e.g. Synscapes' per-image `meta/<id>.json` intrinsics. A modality whose
|
|
106
|
+
files share no sample IDs but sit at or above the samples' hierarchy level is
|
|
107
|
+
matched hierarchically and shared by every sample beneath it, e.g. one intrinsics
|
|
108
|
+
file per scene or camera. A modality that is neither raises an error, and the
|
|
109
|
+
inferred roles are logged. Listing a per-sample modality under
|
|
110
|
+
`hierarchical_modalities` logs a warning and loads it per sample.
|
|
103
111
|
|
|
104
112
|
When a modality directory contains ds-crawler split files
|
|
105
113
|
(`.ds_crawler/split_<name>.json`), set `split` on that modality to select a subset.
|
|
@@ -657,7 +665,7 @@ d_radial(u, v) = d_planar(u, v) * sqrt(((u - cx)/fx)^2 + ((v - cy)/fy)^2 + 1)
|
|
|
657
665
|
```
|
|
658
666
|
|
|
659
667
|
The transform config takes no parameters (`{}`); intrinsics are read from the
|
|
660
|
-
`intrinsics`
|
|
668
|
+
`intrinsics` modality, stored either per sample or per scene/camera.
|
|
661
669
|
|
|
662
670
|
CLI runs write a source-backed depth dataset mirroring the input depth modality's layout
|
|
663
671
|
and writer metadata, with `meta.radial_depth` set to `true` in the emitted `index.json`.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import Counter
|
|
4
|
+
|
|
5
|
+
from euler_preprocess.common.logging import get_logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _make_modality(entry: str | dict):
|
|
9
|
+
from euler_loading import Modality
|
|
10
|
+
|
|
11
|
+
if isinstance(entry, str):
|
|
12
|
+
return Modality(entry)
|
|
13
|
+
return Modality(
|
|
14
|
+
entry["path"],
|
|
15
|
+
**{
|
|
16
|
+
key: entry[key]
|
|
17
|
+
for key in (
|
|
18
|
+
"split",
|
|
19
|
+
"metadata_scope",
|
|
20
|
+
"collapse_single",
|
|
21
|
+
"cache",
|
|
22
|
+
"applies_to",
|
|
23
|
+
"hierarchy_scope",
|
|
24
|
+
)
|
|
25
|
+
if key in entry
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _load_index_tree(name: str, modality) -> dict:
|
|
31
|
+
"""Load a modality's ds-crawler tree the same way ``MultiModalDataset`` does."""
|
|
32
|
+
from ds_crawler import index_dataset_from_path
|
|
33
|
+
from euler_loading._ds_crawler_utils import load_index_output
|
|
34
|
+
from euler_loading.dataset import _get_index_tree, _resolve_metadata_scope
|
|
35
|
+
|
|
36
|
+
modality = _resolve_metadata_scope(name, modality)
|
|
37
|
+
index = load_index_output(
|
|
38
|
+
modality.path,
|
|
39
|
+
split=modality.split,
|
|
40
|
+
metadata_scope=modality.metadata_scope,
|
|
41
|
+
index_dataset_from_path_fn=index_dataset_from_path,
|
|
42
|
+
)
|
|
43
|
+
return _get_index_tree(index)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_attachment(
|
|
47
|
+
name: str, seed: str, tree: dict, samples_per_level: Counter
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Check that hierarchical modality *name* applies to the samples by tree position."""
|
|
50
|
+
from euler_loading.indexing import collect_hierarchical_files, match_hierarchical_files
|
|
51
|
+
|
|
52
|
+
files_by_level = collect_hierarchical_files(tree)
|
|
53
|
+
attached = {
|
|
54
|
+
level: len(match_hierarchical_files(level, files_by_level))
|
|
55
|
+
for level in samples_per_level
|
|
56
|
+
}
|
|
57
|
+
covered = sum(count for level, count in samples_per_level.items() if attached[level])
|
|
58
|
+
if not covered:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Modality '{name}' shares no sample IDs with '{seed}' and none of its "
|
|
61
|
+
"files sit at or above the hierarchy level of any sample, so it can be "
|
|
62
|
+
"neither joined per sample nor matched hierarchically. Check that its "
|
|
63
|
+
f"ds-crawler IDs and hierarchy follow the same conventions as '{seed}'."
|
|
64
|
+
)
|
|
65
|
+
logger = get_logger()
|
|
66
|
+
total = sum(samples_per_level.values())
|
|
67
|
+
log = logger.info if covered == total else logger.warning
|
|
68
|
+
log(
|
|
69
|
+
"Modality '%s' is hierarchical (no sample IDs shared with '%s'); it applies "
|
|
70
|
+
"to %d/%d samples by hierarchy position.",
|
|
71
|
+
name,
|
|
72
|
+
seed,
|
|
73
|
+
covered,
|
|
74
|
+
total,
|
|
75
|
+
)
|
|
76
|
+
most = max(attached.values())
|
|
77
|
+
if most > 1:
|
|
78
|
+
logger.warning(
|
|
79
|
+
"Hierarchical modality '%s' attaches up to %d files to one sample. If it "
|
|
80
|
+
"holds per-sample data, its ds-crawler IDs do not match '%s'.",
|
|
81
|
+
name,
|
|
82
|
+
most,
|
|
83
|
+
seed,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _resolve_roles(
|
|
88
|
+
modalities: dict, per_sample: set[str]
|
|
89
|
+
) -> tuple[list[str], list[str]]:
|
|
90
|
+
"""Split modality names into regular and hierarchical ones.
|
|
91
|
+
|
|
92
|
+
The role follows from the ds-crawler indexes. A modality is regular
|
|
93
|
+
(joined per sample) when its qualified file IDs -- hierarchy path plus
|
|
94
|
+
file ID -- overlap those of the seed modality. Otherwise it is
|
|
95
|
+
hierarchical (matched by tree position, like per-scene intrinsics) and
|
|
96
|
+
must sit at or above the hierarchy level of at least one sample.
|
|
97
|
+
|
|
98
|
+
*per_sample* modalities are regular by contract and the first one is the
|
|
99
|
+
seed. Without one, the seed is the modality aligned with the most others,
|
|
100
|
+
then the one with the most files, then the first listed.
|
|
101
|
+
"""
|
|
102
|
+
from euler_loading.indexing import collect_files
|
|
103
|
+
|
|
104
|
+
names = list(modalities)
|
|
105
|
+
if len(names) < 2 or per_sample.issuperset(names):
|
|
106
|
+
return names, []
|
|
107
|
+
|
|
108
|
+
trees = {name: _load_index_tree(name, modalities[name]) for name in names}
|
|
109
|
+
# Qualified sample ID -> hierarchy path, keyed like euler-loading's lookups.
|
|
110
|
+
sample_ids = {}
|
|
111
|
+
for name, tree in trees.items():
|
|
112
|
+
sample_ids[name] = {
|
|
113
|
+
"/".join(r.hierarchy_path + (r.file_entry["id"],)): r.hierarchy_path
|
|
114
|
+
for r in collect_files(tree)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def aligned(name: str, other: str) -> bool:
|
|
118
|
+
return not sample_ids[name].keys().isdisjoint(sample_ids[other].keys())
|
|
119
|
+
|
|
120
|
+
anchors = [name for name in names if name in per_sample]
|
|
121
|
+
seed = anchors[0] if anchors else max(
|
|
122
|
+
names,
|
|
123
|
+
key=lambda name: (
|
|
124
|
+
sum(aligned(other, name) for other in names),
|
|
125
|
+
len(sample_ids[name]),
|
|
126
|
+
),
|
|
127
|
+
)
|
|
128
|
+
regular = [
|
|
129
|
+
name
|
|
130
|
+
for name in names
|
|
131
|
+
if name in per_sample or name == seed or aligned(name, seed)
|
|
132
|
+
]
|
|
133
|
+
hierarchical = [name for name in names if name not in regular]
|
|
134
|
+
get_logger().info(
|
|
135
|
+
"Modality roles inferred from ds-crawler indexes: per-sample %s; "
|
|
136
|
+
"hierarchical %s",
|
|
137
|
+
", ".join(regular),
|
|
138
|
+
", ".join(hierarchical) or "none",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Without common samples MultiModalDataset raises its own, clearer error.
|
|
142
|
+
common = set(sample_ids[seed]).intersection(*(sample_ids[name] for name in regular))
|
|
143
|
+
if common:
|
|
144
|
+
samples_per_level = Counter(sample_ids[seed][sample_id] for sample_id in common)
|
|
145
|
+
for name in hierarchical:
|
|
146
|
+
_check_attachment(name, seed, trees[name], samples_per_level)
|
|
147
|
+
return regular, hierarchical
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def build_dataset(
|
|
151
|
+
config: dict,
|
|
152
|
+
required_modalities: set[str],
|
|
153
|
+
required_hierarchical: set[str] | None = None,
|
|
154
|
+
):
|
|
155
|
+
"""Build a ``MultiModalDataset`` from a config dict.
|
|
156
|
+
|
|
157
|
+
Each modality entry is either a plain path string or a dict with
|
|
158
|
+
``path`` and optional ``split``. Loader resolution (which function to
|
|
159
|
+
call, which module to use) is handled by euler-loading via the
|
|
160
|
+
ds-crawler index at each path — point the config at a path whose
|
|
161
|
+
index declares the function you want (e.g. a ``sky_mask`` index for
|
|
162
|
+
boolean sky masks vs. a ``class_segmentation`` index for raw class
|
|
163
|
+
id maps).
|
|
164
|
+
|
|
165
|
+
Every modality can be listed under ``modalities``: whether it is joined
|
|
166
|
+
per sample or matched hierarchically (e.g. per-scene intrinsics) is
|
|
167
|
+
inferred from the ds-crawler indexes. Entries under the legacy
|
|
168
|
+
``hierarchical_modalities`` key are pooled with them and resolved the
|
|
169
|
+
same way. *required_modalities* are per-sample inputs and anchor that
|
|
170
|
+
inference; *required_hierarchical* names further required modalities,
|
|
171
|
+
whose role is inferred like any other.
|
|
172
|
+
"""
|
|
173
|
+
from euler_loading import MultiModalDataset, SamplePreprocessor
|
|
174
|
+
|
|
175
|
+
raw_modalities = config.get("modalities", {})
|
|
176
|
+
raw_hierarchical = config.get("hierarchical_modalities", {})
|
|
177
|
+
|
|
178
|
+
duplicated = raw_modalities.keys() & raw_hierarchical.keys()
|
|
179
|
+
if duplicated:
|
|
180
|
+
raise ValueError(
|
|
181
|
+
"Modalities listed under both 'modalities' and "
|
|
182
|
+
f"'hierarchical_modalities': {', '.join(sorted(duplicated))}"
|
|
183
|
+
)
|
|
184
|
+
configured = {**raw_modalities, **raw_hierarchical}
|
|
185
|
+
|
|
186
|
+
required = set(required_modalities) | set(required_hierarchical or ())
|
|
187
|
+
missing = required - configured.keys()
|
|
188
|
+
if missing:
|
|
189
|
+
raise ValueError(
|
|
190
|
+
f"Missing required modalities in config: {', '.join(sorted(missing))}. "
|
|
191
|
+
f"'modalities' must contain at least: {', '.join(sorted(required))}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
modalities = {name: _make_modality(entry) for name, entry in configured.items()}
|
|
195
|
+
regular, hierarchical = _resolve_roles(modalities, set(required_modalities))
|
|
196
|
+
logger = get_logger()
|
|
197
|
+
for name in regular:
|
|
198
|
+
if name in raw_hierarchical:
|
|
199
|
+
logger.warning(
|
|
200
|
+
"Modality '%s' is listed under 'hierarchical_modalities' but holds "
|
|
201
|
+
"per-sample data; loading it per sample.",
|
|
202
|
+
name,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return MultiModalDataset(
|
|
206
|
+
modalities={name: modalities[name] for name in regular},
|
|
207
|
+
hierarchical_modalities={name: modalities[name] for name in hierarchical}
|
|
208
|
+
or None,
|
|
209
|
+
transforms=[SamplePreprocessor.from_config(config["preprocessing"])]
|
|
210
|
+
if config.get("preprocessing")
|
|
211
|
+
else None,
|
|
212
|
+
)
|
|
@@ -13,8 +13,9 @@ except ImportError:
|
|
|
13
13
|
def extract_intrinsics(sample: dict) -> np.ndarray | None:
|
|
14
14
|
"""Extract the 3x3 intrinsics matrix from a sample dict.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
Per-sample intrinsics are stored directly as ``sample["intrinsics"]``.
|
|
17
|
+
Hierarchical modalities are stored as ``sample[name][file_id]``; for
|
|
18
|
+
intrinsics the convention is ``sample["intrinsics"]["intrinsics"]``.
|
|
18
19
|
"""
|
|
19
20
|
intr_dict = sample.get("intrinsics")
|
|
20
21
|
if intr_dict is None:
|
|
@@ -12,6 +12,15 @@ class Transform(ABC):
|
|
|
12
12
|
Subclasses declare the modalities they need via class variables and
|
|
13
13
|
implement :meth:`run` to process samples.
|
|
14
14
|
|
|
15
|
+
Required modalities:
|
|
16
|
+
:attr:`REQUIRED_MODALITIES` are per-sample inputs (RGB, depth, masks);
|
|
17
|
+
they are always joined per sample and anchor the role inference of
|
|
18
|
+
every other modality. :attr:`REQUIRED_HIERARCHICAL_MODALITIES` are
|
|
19
|
+
further required inputs, such as camera intrinsics, that a dataset may
|
|
20
|
+
store per sample or once per hierarchy level; their role is inferred
|
|
21
|
+
from the ds-crawler indexes (see
|
|
22
|
+
:func:`~euler_preprocess.common.dataset.build_dataset`).
|
|
23
|
+
|
|
15
24
|
Output slots:
|
|
16
25
|
Most transforms produce a single output (the *primary* slot, declared
|
|
17
26
|
via :attr:`OUTPUT_SLOT`). Transforms that produce additional auxiliary
|
|
@@ -286,8 +286,9 @@ class FogTransform(Transform):
|
|
|
286
286
|
"depth": np.ndarray (H, W), float in meters
|
|
287
287
|
"semantic_segmentation": np.ndarray (H, W), boolean sky mask
|
|
288
288
|
"id": str
|
|
289
|
-
"intrinsics":
|
|
290
|
-
|
|
289
|
+
"intrinsics": (3, 3) camera intrinsics matrix *K* (per-sample
|
|
290
|
+
modality), or a dict with an ``"intrinsics"`` key
|
|
291
|
+
mapping to *K* (hierarchical modality).
|
|
291
292
|
When present, planar (z-buffer) depth is converted to
|
|
292
293
|
radial (Euclidean) depth before fog is applied.
|
|
293
294
|
|
|
@@ -15,8 +15,8 @@ from euler_preprocess.common.transform import Transform
|
|
|
15
15
|
class RadialTransform(Transform):
|
|
16
16
|
"""Convert planar (z-buffer) depth to radial (Euclidean) depth.
|
|
17
17
|
|
|
18
|
-
Requires camera intrinsics (
|
|
19
|
-
compute the per-pixel ray direction.
|
|
18
|
+
Requires camera intrinsics (modality ``intrinsics``, stored per sample or
|
|
19
|
+
per hierarchy level) to compute the per-pixel ray direction.
|
|
20
20
|
|
|
21
21
|
Config JSON keys:
|
|
22
22
|
``device`` (str): ``"cpu"`` (default). GPU support reserved for future use.
|
|
@@ -61,7 +61,7 @@ class RadialTransform(Transform):
|
|
|
61
61
|
if K is None:
|
|
62
62
|
raise ValueError(
|
|
63
63
|
f"Sample '{sample.get('id', '?')}' has no intrinsics. "
|
|
64
|
-
"RadialTransform requires the 'intrinsics'
|
|
64
|
+
"RadialTransform requires the 'intrinsics' modality."
|
|
65
65
|
)
|
|
66
66
|
|
|
67
67
|
radial = planar_to_radial_depth(depth, K)
|
|
@@ -27,7 +27,15 @@ def export_spatial(config):
|
|
|
27
27
|
dataset_config = {
|
|
28
28
|
key: value for key, value in config.items() if key != "preprocessing"
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
# Bound fields with a spatial layout are per-sample images; the role of the
|
|
31
|
+
# others (e.g. intrinsics) is inferred from their ds-crawler indexes.
|
|
32
|
+
per_sample = {
|
|
33
|
+
name
|
|
34
|
+
for options in variants.values()
|
|
35
|
+
for name, binding in options["bindings"]["fields"].items()
|
|
36
|
+
if "H" in binding["profile"]["layout"]
|
|
37
|
+
}
|
|
38
|
+
dataset = build_dataset(dataset_config, per_sample)
|
|
31
39
|
plans = {}
|
|
32
40
|
for variant, options in variants.items():
|
|
33
41
|
bindings = deepcopy(options["bindings"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-preprocess
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.16.0
|
|
4
4
|
Summary: Physics-based preprocessing (fog, etc.) for RGB+depth datasets
|
|
5
5
|
Author-email: Daniel Rothenpieler <rothenpielerdaniel@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -94,9 +94,7 @@ loader.
|
|
|
94
94
|
"modalities": {
|
|
95
95
|
"rgb": {"path": "/path/to/rgb", "split": "train"},
|
|
96
96
|
"depth": "/path/to/depth",
|
|
97
|
-
"semantic_segmentation": "/path/to/classSegmentation"
|
|
98
|
-
},
|
|
99
|
-
"hierarchical_modalities": {
|
|
97
|
+
"semantic_segmentation": "/path/to/classSegmentation",
|
|
100
98
|
"intrinsics": {"path": "/path/to/intrinsics"}
|
|
101
99
|
}
|
|
102
100
|
}
|
|
@@ -109,17 +107,27 @@ loader.
|
|
|
109
107
|
| `output_slot` | Optional slot selector when `pipeline.output_targets` has several entries. Defaults to `rgb` for `fog` and `depth` for `sky-depth` / `radial`. |
|
|
110
108
|
| `sample` | Optional 0-based dataset index. Only `dataset[sample]` is transformed — useful for small benchmark slices of large datasets. |
|
|
111
109
|
| `samples` | Optional multi-sample selector: a list of indices (`[0, 10, 20]`) or a slice object (`{"start": 0, "stop": 1000, "step": 2, "count": 100}`). `stop` is exclusive, `count` caps the result. Mutually exclusive with `sample`. |
|
|
112
|
-
| `modalities` |
|
|
113
|
-
| `hierarchical_modalities` |
|
|
110
|
+
| `modalities` | All input modalities. Each value is a path string or an object with `path` and optional `split`. Whether a modality is per-sample or hierarchical is inferred (see below). |
|
|
111
|
+
| `hierarchical_modalities` | Legacy, optional. Same format; entries are pooled with `modalities` and resolved the same way. |
|
|
114
112
|
| `pipeline` | Optional runtime routing block compatible with `euler-inference`. |
|
|
115
113
|
|
|
116
114
|
**Required modalities per transform:**
|
|
117
115
|
|
|
118
|
-
| Transform |
|
|
116
|
+
| Transform | Required | Optional |
|
|
119
117
|
|---|---|---|
|
|
120
|
-
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics
|
|
118
|
+
| `fog` | `rgb`, `depth`, `semantic_segmentation` | `intrinsics`; used for radial depth conversion and camera-profile optics |
|
|
121
119
|
| `sky-depth` | `depth`, `semantic_segmentation` | — |
|
|
122
|
-
| `radial` | `depth
|
|
120
|
+
| `radial` | `depth`, `intrinsics` | — |
|
|
121
|
+
|
|
122
|
+
**Per-sample vs. hierarchical modalities.** You don't declare a modality's role:
|
|
123
|
+
it is inferred from the ds-crawler indexes. A modality whose qualified IDs
|
|
124
|
+
(hierarchy path plus file ID) match the transform's per-sample inputs is joined per
|
|
125
|
+
sample, e.g. Synscapes' per-image `meta/<id>.json` intrinsics. A modality whose
|
|
126
|
+
files share no sample IDs but sit at or above the samples' hierarchy level is
|
|
127
|
+
matched hierarchically and shared by every sample beneath it, e.g. one intrinsics
|
|
128
|
+
file per scene or camera. A modality that is neither raises an error, and the
|
|
129
|
+
inferred roles are logged. Listing a per-sample modality under
|
|
130
|
+
`hierarchical_modalities` logs a warning and loads it per sample.
|
|
123
131
|
|
|
124
132
|
When a modality directory contains ds-crawler split files
|
|
125
133
|
(`.ds_crawler/split_<name>.json`), set `split` on that modality to select a subset.
|
|
@@ -677,7 +685,7 @@ d_radial(u, v) = d_planar(u, v) * sqrt(((u - cx)/fx)^2 + ((v - cy)/fy)^2 + 1)
|
|
|
677
685
|
```
|
|
678
686
|
|
|
679
687
|
The transform config takes no parameters (`{}`); intrinsics are read from the
|
|
680
|
-
`intrinsics`
|
|
688
|
+
`intrinsics` modality, stored either per sample or per scene/camera.
|
|
681
689
|
|
|
682
690
|
CLI runs write a source-backed depth dataset mirroring the input depth modality's layout
|
|
683
691
|
and writer metadata, with `meta.radial_depth` set to `true` in the emitted `index.json`.
|
|
@@ -52,6 +52,7 @@ tests/test_fog_aux_outputs.py
|
|
|
52
52
|
tests/test_fog_aware_auto_exposure.py
|
|
53
53
|
tests/test_fog_cpu_gpu_parity.py
|
|
54
54
|
tests/test_fog_integration.py
|
|
55
|
+
tests/test_modality_roles.py
|
|
55
56
|
tests/test_radial.py
|
|
56
57
|
tests/test_real_drive_sim_scenario_profiles.py
|
|
57
58
|
tests/test_scene_illumination.py
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Tests for inferring per-sample vs. hierarchical modality roles."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from ds_crawler import DatasetWriter
|
|
13
|
+
from euler_loading import Modality, MultiModalDataset
|
|
14
|
+
from euler_loading.loaders.cpu.generic import write_intrinsics
|
|
15
|
+
from euler_loading.loaders.cpu.generic_dense_depth import write_depth
|
|
16
|
+
|
|
17
|
+
from euler_preprocess.cli import _run_transform
|
|
18
|
+
from euler_preprocess.common.dataset import build_dataset
|
|
19
|
+
from euler_preprocess.common.intrinsics import extract_intrinsics, planar_to_radial_depth
|
|
20
|
+
from euler_preprocess.common.normalize import normalize_depth
|
|
21
|
+
from euler_preprocess.radial.transform import RadialTransform
|
|
22
|
+
|
|
23
|
+
FRAMES = ("Scene01/Camera_0/0000", "Scene01/Camera_0/0001", "Scene02/Camera_0/0000")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _K(fx: float) -> np.ndarray:
|
|
27
|
+
return np.array([[fx, 0.0, 3.0], [0.0, fx, 2.0], [0.0, 0.0, 1.0]], dtype=np.float32)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _write_depth(root: Path, full_ids=FRAMES) -> str:
|
|
31
|
+
writer = DatasetWriter(
|
|
32
|
+
root,
|
|
33
|
+
name="synthetic_depth",
|
|
34
|
+
type="depth",
|
|
35
|
+
separator=None,
|
|
36
|
+
meta={"radial_depth": False, "scale_to_meters": 1.0, "range": [0, 1000]},
|
|
37
|
+
euler_loading={"loader": "generic_dense_depth", "function": "depth"},
|
|
38
|
+
)
|
|
39
|
+
for full_id in full_ids:
|
|
40
|
+
path = writer.get_path(full_id, full_id.rsplit("/", 1)[-1] + ".npy")
|
|
41
|
+
write_depth(str(path), np.full((4, 6), 25.0, dtype=np.float32))
|
|
42
|
+
writer.save_index()
|
|
43
|
+
return str(root)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _write_intrinsics(root: Path, matrices: dict[str, np.ndarray]) -> str:
|
|
47
|
+
writer = DatasetWriter(
|
|
48
|
+
root,
|
|
49
|
+
name="synthetic_intrinsics",
|
|
50
|
+
type="intrinsics",
|
|
51
|
+
separator=None,
|
|
52
|
+
euler_loading={"loader": "generic", "function": "intrinsics"},
|
|
53
|
+
)
|
|
54
|
+
for full_id, K in matrices.items():
|
|
55
|
+
write_intrinsics(str(writer.get_path(full_id, f"{K[0, 0]:.0f}.npy")), K)
|
|
56
|
+
writer.save_index()
|
|
57
|
+
return str(root)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _per_sample_intrinsics(root: Path) -> str:
|
|
61
|
+
return _write_intrinsics(root, {fid: _K(500.0 + i) for i, fid in enumerate(FRAMES)})
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _per_scene_intrinsics(root: Path) -> str:
|
|
65
|
+
return _write_intrinsics(
|
|
66
|
+
root, {"Scene01/intrinsics": _K(600.0), "Scene02/intrinsics": _K(700.0)}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _roles(dataset) -> tuple[set[str], set[str]]:
|
|
71
|
+
return set(dataset.modality_paths()), set(dataset.hierarchical_modality_paths())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _fx_by_full_id(dataset) -> dict[str, float]:
|
|
75
|
+
return {
|
|
76
|
+
dataset[i]["full_id"]: float(extract_intrinsics(dataset[i])[0, 0])
|
|
77
|
+
for i in range(len(dataset))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.parametrize("key", ["modalities", "hierarchical_modalities"])
|
|
82
|
+
def test_per_sample_intrinsics_are_joined_per_sample(tmp_path, key):
|
|
83
|
+
config = {"modalities": {"depth": _write_depth(tmp_path / "depth")}}
|
|
84
|
+
config.setdefault(key, {})["intrinsics"] = _per_sample_intrinsics(
|
|
85
|
+
tmp_path / "intrinsics"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
dataset = build_dataset(
|
|
89
|
+
config,
|
|
90
|
+
RadialTransform.REQUIRED_MODALITIES,
|
|
91
|
+
RadialTransform.REQUIRED_HIERARCHICAL_MODALITIES,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
assert _roles(dataset) == ({"depth", "intrinsics"}, set())
|
|
95
|
+
assert _fx_by_full_id(dataset) == {
|
|
96
|
+
"/Scene01/Camera_0/0000": 500.0,
|
|
97
|
+
"/Scene01/Camera_0/0001": 501.0,
|
|
98
|
+
"/Scene02/Camera_0/0000": 502.0,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@pytest.mark.parametrize("key", ["modalities", "hierarchical_modalities"])
|
|
103
|
+
def test_per_scene_intrinsics_are_matched_hierarchically(tmp_path, key):
|
|
104
|
+
config = {"modalities": {"depth": _write_depth(tmp_path / "depth")}}
|
|
105
|
+
config.setdefault(key, {})["intrinsics"] = _per_scene_intrinsics(
|
|
106
|
+
tmp_path / "intrinsics"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
dataset = build_dataset(
|
|
110
|
+
config,
|
|
111
|
+
RadialTransform.REQUIRED_MODALITIES,
|
|
112
|
+
RadialTransform.REQUIRED_HIERARCHICAL_MODALITIES,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
assert _roles(dataset) == ({"depth"}, {"intrinsics"})
|
|
116
|
+
assert _fx_by_full_id(dataset) == {
|
|
117
|
+
"/Scene01/Camera_0/0000": 600.0,
|
|
118
|
+
"/Scene01/Camera_0/0001": 600.0,
|
|
119
|
+
"/Scene02/Camera_0/0000": 700.0,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_seed_without_required_modalities_is_the_sample_modality(tmp_path):
|
|
124
|
+
# The calibration sits at the same level as the frames and is listed first.
|
|
125
|
+
calibration = _write_intrinsics(
|
|
126
|
+
tmp_path / "intrinsics", {"Scene01/Camera_0/calibration": _K(800.0)}
|
|
127
|
+
)
|
|
128
|
+
depth = _write_depth(tmp_path / "depth", FRAMES[:2])
|
|
129
|
+
|
|
130
|
+
dataset = build_dataset(
|
|
131
|
+
{"modalities": {"intrinsics": calibration, "depth": depth}}, set()
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
assert _roles(dataset) == ({"depth"}, {"intrinsics"})
|
|
135
|
+
assert len(dataset) == 2
|
|
136
|
+
assert set(dataset[0]["intrinsics"]) == {"calibration"}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_modality_neither_per_sample_nor_attached_raises(tmp_path):
|
|
140
|
+
config = {
|
|
141
|
+
"modalities": {
|
|
142
|
+
"depth": _write_depth(tmp_path / "depth"),
|
|
143
|
+
"intrinsics": _write_intrinsics(
|
|
144
|
+
tmp_path / "intrinsics", {"Scene99/intrinsics": _K(600.0)}
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
with pytest.raises(ValueError, match="neither joined per sample nor matched"):
|
|
150
|
+
build_dataset(config, {"depth"})
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_modality_listed_under_both_keys_raises(tmp_path):
|
|
154
|
+
intrinsics = _per_scene_intrinsics(tmp_path / "intrinsics")
|
|
155
|
+
config = {
|
|
156
|
+
"modalities": {"depth": _write_depth(tmp_path / "depth"), "intrinsics": intrinsics},
|
|
157
|
+
"hierarchical_modalities": {"intrinsics": intrinsics},
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
with pytest.raises(ValueError, match="both 'modalities' and 'hierarchical_modalities'"):
|
|
161
|
+
build_dataset(config, {"depth"})
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.mark.parametrize("layout", [_per_sample_intrinsics, _per_scene_intrinsics])
|
|
165
|
+
def test_radial_cli_infers_intrinsics_role(tmp_path, layout):
|
|
166
|
+
(tmp_path / "radial.json").write_text("{}", encoding="utf-8")
|
|
167
|
+
config_path = tmp_path / "dataset.json"
|
|
168
|
+
config_path.write_text(
|
|
169
|
+
json.dumps(
|
|
170
|
+
{
|
|
171
|
+
"transform_config_path": "radial.json",
|
|
172
|
+
"output_path": str(tmp_path / "radial_depth"),
|
|
173
|
+
"modalities": {
|
|
174
|
+
"depth": _write_depth(tmp_path / "depth"),
|
|
175
|
+
"intrinsics": layout(tmp_path / "intrinsics"),
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
encoding="utf-8",
|
|
180
|
+
)
|
|
181
|
+
source = build_dataset(json.loads(config_path.read_text()), {"depth"})
|
|
182
|
+
|
|
183
|
+
assert _run_transform(argparse.Namespace(config=str(config_path)), RadialTransform) == 0
|
|
184
|
+
|
|
185
|
+
output = MultiModalDataset({"depth": Modality(str(tmp_path / "radial_depth"))})
|
|
186
|
+
assert len(output) == len(source) == len(FRAMES)
|
|
187
|
+
for i in range(len(source)):
|
|
188
|
+
expected = planar_to_radial_depth(
|
|
189
|
+
normalize_depth(source[i]["depth"]), extract_intrinsics(source[i])
|
|
190
|
+
)
|
|
191
|
+
assert output[i]["full_id"] == source[i]["full_id"]
|
|
192
|
+
np.testing.assert_allclose(normalize_depth(output[i]["depth"]), expected, rtol=1e-6)
|
|
@@ -77,6 +77,17 @@ def test_producer_captures_and_reloads_five_outputs(tmp_path, zip_output):
|
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def test_producer_infers_hierarchical_intrinsics_listed_as_modality(tmp_path):
|
|
81
|
+
config, ds, plan, _data, _original = config_for(tmp_path)
|
|
82
|
+
config["modalities"].update(config.pop("hierarchical_modalities"))
|
|
83
|
+
paths = export_spatial(config)
|
|
84
|
+
ds.enable_transform_capture({"default": plan})
|
|
85
|
+
expected = ds[1]
|
|
86
|
+
for field, path in paths.items():
|
|
87
|
+
sample = MultiModalDataset({field: Modality(path, metadata_scope="saved")})[0]
|
|
88
|
+
np.testing.assert_array_equal(sample[field], expected[field])
|
|
89
|
+
|
|
90
|
+
|
|
80
91
|
def test_common_builder_consumes_preprocessing(tmp_path):
|
|
81
92
|
config, ds, _, _, _ = config_for(tmp_path)
|
|
82
93
|
configured = build_dataset(config, set(config["modalities"]))
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def _make_modality(entry: str | dict):
|
|
5
|
-
from euler_loading import Modality
|
|
6
|
-
|
|
7
|
-
if isinstance(entry, str):
|
|
8
|
-
return Modality(entry)
|
|
9
|
-
return Modality(
|
|
10
|
-
entry["path"],
|
|
11
|
-
**{
|
|
12
|
-
key: entry[key]
|
|
13
|
-
for key in (
|
|
14
|
-
"split",
|
|
15
|
-
"metadata_scope",
|
|
16
|
-
"collapse_single",
|
|
17
|
-
"cache",
|
|
18
|
-
"applies_to",
|
|
19
|
-
"hierarchy_scope",
|
|
20
|
-
)
|
|
21
|
-
if key in entry
|
|
22
|
-
},
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def build_dataset(
|
|
27
|
-
config: dict,
|
|
28
|
-
required_modalities: set[str],
|
|
29
|
-
required_hierarchical: set[str] | None = None,
|
|
30
|
-
):
|
|
31
|
-
"""Build a ``MultiModalDataset`` from a config dict.
|
|
32
|
-
|
|
33
|
-
Each modality entry is either a plain path string or a dict with
|
|
34
|
-
``path`` and optional ``split``. Loader resolution (which function to
|
|
35
|
-
call, which module to use) is handled by euler-loading via the
|
|
36
|
-
ds-crawler index at each path — point the config at a path whose
|
|
37
|
-
index declares the function you want (e.g. a ``sky_mask`` index for
|
|
38
|
-
boolean sky masks vs. a ``class_segmentation`` index for raw class
|
|
39
|
-
id maps).
|
|
40
|
-
"""
|
|
41
|
-
from euler_loading import MultiModalDataset, SamplePreprocessor
|
|
42
|
-
|
|
43
|
-
raw_modalities = config.get("modalities", {})
|
|
44
|
-
raw_hierarchical = config.get("hierarchical_modalities", {})
|
|
45
|
-
|
|
46
|
-
missing = required_modalities - raw_modalities.keys()
|
|
47
|
-
if missing:
|
|
48
|
-
raise ValueError(
|
|
49
|
-
f"Missing required modalities in config: {', '.join(sorted(missing))}. "
|
|
50
|
-
f"'modalities' must contain at least: {', '.join(sorted(required_modalities))}"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
if required_hierarchical:
|
|
54
|
-
missing_h = required_hierarchical - raw_hierarchical.keys()
|
|
55
|
-
if missing_h:
|
|
56
|
-
raise ValueError(
|
|
57
|
-
f"Missing required hierarchical modalities in config: "
|
|
58
|
-
f"{', '.join(sorted(missing_h))}. 'hierarchical_modalities' must "
|
|
59
|
-
f"contain at least: {', '.join(sorted(required_hierarchical))}"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
modalities = {name: _make_modality(entry) for name, entry in raw_modalities.items()}
|
|
63
|
-
hierarchical_modalities = {
|
|
64
|
-
name: _make_modality(entry) for name, entry in raw_hierarchical.items()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return MultiModalDataset(
|
|
68
|
-
modalities=modalities,
|
|
69
|
-
hierarchical_modalities=hierarchical_modalities or None,
|
|
70
|
-
transforms=[SamplePreprocessor.from_config(config["preprocessing"])]
|
|
71
|
-
if config.get("preprocessing")
|
|
72
|
-
else None,
|
|
73
|
-
)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/airlight_from_sky.py
RENAMED
|
File without changes
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/atmospheric_light.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/dcp_airlight_torch.py
RENAMED
|
File without changes
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess/fog/dcp_heuristic_airlight.py
RENAMED
|
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
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/euler_preprocess.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_dense_gloomy_daylight_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{euler_preprocess-3.15.0 → euler_preprocess-3.16.0}/tests/test_real_drive_sim_scenario_profiles.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|