euler-loading 2.1.0__tar.gz → 2.2.1__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_loading-2.1.0 → euler_loading-2.2.1}/PKG-INFO +1 -1
- {euler_loading-2.1.0 → euler_loading-2.2.1}/README.md +38 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/__init__.py +16 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/_ds_crawler_utils.py +20 -9
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/_resolution.py +17 -10
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/_writing.py +122 -4
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/dataset.py +85 -25
- euler_loading-2.2.1/euler_loading/preprocessing.py +867 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/package-lock.json +1 -1
- {euler_loading-2.1.0 → euler_loading-2.2.1}/pyproject.toml +1 -1
- euler_loading-2.2.1/tests/test_preprocessing.py +234 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/.github/workflows/workflow.yml +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/.gitignore +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/_dataset_contract.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/_metadata.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/indexing.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/__init__.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/_annotations.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/_writer_utils.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/contracts.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/cpu/__init__.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/cpu/generic.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/cpu/vkitti2.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/generate/__init__.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/generate/__main__.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/generate/loaders.json +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/generic.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/gpu/__init__.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/gpu/generic.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/gpu/vkitti2.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/real_drive_sim.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/euler_loading/loaders/vkitti2.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/example.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/sample_rds.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/__init__.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/conftest.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/example_rds_calib.json +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/test_dataset.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/test_indexing.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/test_loaders.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/test_real_dataset.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/tests/test_writing.py +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/vkitti_cpu_example_output.json +0 -0
- {euler_loading-2.1.0 → euler_loading-2.2.1}/vkitti_gpu_example_output.json +0 -0
|
@@ -218,6 +218,44 @@ def mask_sky_in_depth(sample: dict) -> dict:
|
|
|
218
218
|
return sample
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
+
### Built-in spatial preprocessing
|
|
222
|
+
|
|
223
|
+
`euler_loading.SamplePreprocessor` applies shared spatial ops such as resize and crop
|
|
224
|
+
consistently across sample fields, including calibration-sensitive fields such as
|
|
225
|
+
camera intrinsics and ray maps.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from euler_loading import FieldSpec, SamplePreprocessor
|
|
229
|
+
|
|
230
|
+
preprocessor = SamplePreprocessor.from_config(
|
|
231
|
+
{
|
|
232
|
+
"resize": [384, 768],
|
|
233
|
+
"crop": {"size": [320, 640], "anchor": "center"},
|
|
234
|
+
"fields": {
|
|
235
|
+
"rgb": {"kind": "image"},
|
|
236
|
+
"depth": {"kind": "depth"},
|
|
237
|
+
"valid_mask": {"kind": "mask"},
|
|
238
|
+
"intrinsics": {"kind": "intrinsics", "reduce": "first"},
|
|
239
|
+
"ray_map": {"kind": "ray_map"},
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
dataset = MultiModalDataset(
|
|
245
|
+
modalities={...},
|
|
246
|
+
hierarchical_modalities={...},
|
|
247
|
+
transforms=[preprocessor],
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- `kind="image"` and `kind="depth"` default to bilinear interpolation.
|
|
252
|
+
- `kind="mask"` defaults to nearest-neighbour interpolation and preserves mask dtype.
|
|
253
|
+
- `kind="ray_map"` bilinearly resizes and renormalizes vectors to unit length.
|
|
254
|
+
- `kind="intrinsics"` rescales the camera matrix for resize and shifts the principal point for crop.
|
|
255
|
+
- `reduce="first"` is useful for hierarchical modalities such as one intrinsics file per scene/camera.
|
|
256
|
+
|
|
257
|
+
If you already set `modality_type` on your `Modality(...)` definitions, the preprocessor can bind to the dataset and reuse those hints automatically.
|
|
258
|
+
|
|
221
259
|
## Zip archive support
|
|
222
260
|
|
|
223
261
|
Modality paths can point to `.zip` files instead of directories. euler-loading detects zip paths automatically and reads files directly from the archive without extraction:
|
|
@@ -6,15 +6,31 @@ from ._writing import create_dataset_writer_from_index
|
|
|
6
6
|
from .dataset import Modality, MultiModalDataset
|
|
7
7
|
from .indexing import FileRecord
|
|
8
8
|
from .loaders.contracts import DenseDepthCodec, DenseDepthLoader, DenseDepthWriter
|
|
9
|
+
from .preprocessing import (
|
|
10
|
+
Crop,
|
|
11
|
+
FieldSpec,
|
|
12
|
+
Resize,
|
|
13
|
+
SamplePreprocessor,
|
|
14
|
+
crop_intrinsics,
|
|
15
|
+
infer_field_spec,
|
|
16
|
+
resize_intrinsics,
|
|
17
|
+
)
|
|
9
18
|
|
|
10
19
|
__all__ = [
|
|
11
20
|
"DenseDepthCodec",
|
|
12
21
|
"DenseDepthLoader",
|
|
13
22
|
"DenseDepthWriter",
|
|
14
23
|
"FileRecord",
|
|
24
|
+
"Crop",
|
|
25
|
+
"FieldSpec",
|
|
15
26
|
"Modality",
|
|
16
27
|
"MultiModalDataset",
|
|
28
|
+
"Resize",
|
|
29
|
+
"SamplePreprocessor",
|
|
17
30
|
"create_dataset_writer_from_index",
|
|
31
|
+
"crop_intrinsics",
|
|
32
|
+
"infer_field_spec",
|
|
18
33
|
"resolve_loader_module",
|
|
19
34
|
"resolve_writer_module",
|
|
35
|
+
"resize_intrinsics",
|
|
20
36
|
]
|
|
@@ -49,7 +49,13 @@ def first_non_empty_list(*candidates: list[str] | None) -> list[str]:
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def extract_ds_crawler_properties(index_output: Mapping[str, Any]) -> dict[str, Any]:
|
|
52
|
-
|
|
52
|
+
try:
|
|
53
|
+
return get_dataset_contract(dict(index_output)).to_properties_dict()
|
|
54
|
+
except Exception:
|
|
55
|
+
properties = index_output.get("properties")
|
|
56
|
+
if isinstance(properties, Mapping):
|
|
57
|
+
return dict(properties)
|
|
58
|
+
return {}
|
|
53
59
|
|
|
54
60
|
|
|
55
61
|
_SPLIT_NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*$")
|
|
@@ -126,13 +132,18 @@ def load_index_output(
|
|
|
126
132
|
|
|
127
133
|
normalized_split = validate_split_name(split)
|
|
128
134
|
if _load_dataset_split is not None:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
try:
|
|
136
|
+
return _load_dataset_split(
|
|
137
|
+
path,
|
|
138
|
+
normalized_split,
|
|
139
|
+
strict=strict,
|
|
140
|
+
save_index=save_index,
|
|
141
|
+
force_reindex=force_reindex,
|
|
142
|
+
)
|
|
143
|
+
except FileNotFoundError:
|
|
144
|
+
# Fallback for mocked tests or callers that provide synthetic index
|
|
145
|
+
# loaders without a full ds-crawler config on disk.
|
|
146
|
+
pass
|
|
136
147
|
|
|
137
148
|
base_output = index_dataset_from_path_fn(
|
|
138
149
|
path,
|
|
@@ -149,5 +160,5 @@ def load_index_output(
|
|
|
149
160
|
|
|
150
161
|
result = dict(base_output)
|
|
151
162
|
result["index"] = split_dataset
|
|
152
|
-
result
|
|
163
|
+
result["dataset"] = split_dataset
|
|
153
164
|
return result
|
|
@@ -25,6 +25,21 @@ _LOADER_MODULES: dict[str, str] = {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _get_euler_loading_meta(index: Mapping[str, Any]) -> Mapping[str, Any] | None:
|
|
29
|
+
try:
|
|
30
|
+
contract = get_dataset_contract(dict(index))
|
|
31
|
+
euler_loading_meta = contract.get_addon("euler_loading")
|
|
32
|
+
except Exception:
|
|
33
|
+
euler_loading_meta = None
|
|
34
|
+
if isinstance(euler_loading_meta, Mapping):
|
|
35
|
+
return euler_loading_meta
|
|
36
|
+
|
|
37
|
+
raw_meta = index.get("euler_loading")
|
|
38
|
+
if isinstance(raw_meta, Mapping):
|
|
39
|
+
return raw_meta
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
28
43
|
def resolve_loader_module(name: str) -> ModuleType:
|
|
29
44
|
"""Import and return the GPU loader module for *name*.
|
|
30
45
|
|
|
@@ -63,11 +78,7 @@ def _resolve_loader(
|
|
|
63
78
|
if modality.loader is not None:
|
|
64
79
|
return modality.loader
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
contract = get_dataset_contract(index)
|
|
68
|
-
euler_loading_meta = contract.get_addon("euler_loading")
|
|
69
|
-
except Exception:
|
|
70
|
-
euler_loading_meta = None
|
|
81
|
+
euler_loading_meta = _get_euler_loading_meta(index)
|
|
71
82
|
if not isinstance(euler_loading_meta, Mapping):
|
|
72
83
|
raise ValueError(
|
|
73
84
|
f"Modality {modality_name!r}: no explicit loader provided and the "
|
|
@@ -132,11 +143,7 @@ def _resolve_writer(
|
|
|
132
143
|
if modality.writer is not None:
|
|
133
144
|
return modality.writer
|
|
134
145
|
|
|
135
|
-
|
|
136
|
-
contract = get_dataset_contract(index)
|
|
137
|
-
euler_loading_meta = contract.get_addon("euler_loading")
|
|
138
|
-
except Exception:
|
|
139
|
-
euler_loading_meta = None
|
|
146
|
+
euler_loading_meta = _get_euler_loading_meta(index)
|
|
140
147
|
if not isinstance(euler_loading_meta, Mapping):
|
|
141
148
|
return None
|
|
142
149
|
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
5
6
|
import tempfile
|
|
7
|
+
import zipfile
|
|
6
8
|
from collections.abc import Mapping
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
from typing import Any, Callable
|
|
@@ -18,10 +20,93 @@ from .loaders._writer_utils import supports_stream_target
|
|
|
18
20
|
|
|
19
21
|
logger = logging.getLogger(__name__)
|
|
20
22
|
|
|
23
|
+
_DEFAULT_WRITER_INDEX_FILENAME = "output.json"
|
|
24
|
+
|
|
21
25
|
|
|
22
26
|
OutputDestination = str | os.PathLike[str] | DatasetWriter | ZipDatasetWriter
|
|
23
27
|
|
|
24
28
|
|
|
29
|
+
def _wrap_writer_save_index(
|
|
30
|
+
writer: DatasetWriter | ZipDatasetWriter,
|
|
31
|
+
) -> DatasetWriter | ZipDatasetWriter:
|
|
32
|
+
original_save_index = writer.save_index
|
|
33
|
+
|
|
34
|
+
def _build_legacy_fields() -> dict[str, Any]:
|
|
35
|
+
output = writer.build_output()
|
|
36
|
+
result: dict[str, Any] = {}
|
|
37
|
+
|
|
38
|
+
head = output.get("head")
|
|
39
|
+
if isinstance(head, Mapping):
|
|
40
|
+
dataset = head.get("dataset")
|
|
41
|
+
modality = head.get("modality")
|
|
42
|
+
addons = head.get("addons")
|
|
43
|
+
|
|
44
|
+
if isinstance(dataset, Mapping):
|
|
45
|
+
name = as_non_empty_str(dataset.get("name"))
|
|
46
|
+
if name is not None:
|
|
47
|
+
result["name"] = name
|
|
48
|
+
|
|
49
|
+
if isinstance(modality, Mapping):
|
|
50
|
+
modality_key = as_non_empty_str(modality.get("key"))
|
|
51
|
+
if modality_key is not None:
|
|
52
|
+
result["type"] = modality_key
|
|
53
|
+
meta = modality.get("meta")
|
|
54
|
+
if isinstance(meta, Mapping):
|
|
55
|
+
result["meta"] = dict(meta)
|
|
56
|
+
|
|
57
|
+
if isinstance(addons, Mapping):
|
|
58
|
+
for addon_name in ("euler_train", "euler_loading"):
|
|
59
|
+
addon = addons.get(addon_name)
|
|
60
|
+
if isinstance(addon, Mapping):
|
|
61
|
+
result[addon_name] = dict(addon)
|
|
62
|
+
|
|
63
|
+
index_tree = output.get("index")
|
|
64
|
+
if isinstance(index_tree, Mapping):
|
|
65
|
+
result["dataset"] = dict(index_tree)
|
|
66
|
+
|
|
67
|
+
return result
|
|
68
|
+
|
|
69
|
+
def save_index(filename: str = _DEFAULT_WRITER_INDEX_FILENAME):
|
|
70
|
+
legacy_fields = _build_legacy_fields()
|
|
71
|
+
path = original_save_index(filename=filename)
|
|
72
|
+
|
|
73
|
+
if isinstance(writer, ZipDatasetWriter):
|
|
74
|
+
entry_name = f".ds_crawler/{filename}"
|
|
75
|
+
archive_path = Path(writer.root)
|
|
76
|
+
with zipfile.ZipFile(archive_path, "r") as source_zip:
|
|
77
|
+
output_index = json.loads(source_zip.read(entry_name).decode("utf-8"))
|
|
78
|
+
output_index.update(legacy_fields)
|
|
79
|
+
|
|
80
|
+
replacement_name = archive_path.with_suffix(archive_path.suffix + ".tmp")
|
|
81
|
+
with zipfile.ZipFile(replacement_name, "w") as target_zip:
|
|
82
|
+
replaced = False
|
|
83
|
+
for info in source_zip.infolist():
|
|
84
|
+
data = source_zip.read(info.filename)
|
|
85
|
+
if info.filename == entry_name and not replaced:
|
|
86
|
+
data = json.dumps(output_index, indent=2).encode("utf-8")
|
|
87
|
+
replaced = True
|
|
88
|
+
target_zip.writestr(info, data)
|
|
89
|
+
|
|
90
|
+
if not replaced:
|
|
91
|
+
target_zip.writestr(
|
|
92
|
+
entry_name,
|
|
93
|
+
json.dumps(output_index, indent=2),
|
|
94
|
+
compress_type=zipfile.ZIP_DEFLATED,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
replacement_name.replace(archive_path)
|
|
98
|
+
return path
|
|
99
|
+
|
|
100
|
+
output_path = Path(path)
|
|
101
|
+
output_index = json.loads(output_path.read_text(encoding="utf-8"))
|
|
102
|
+
output_index.update(legacy_fields)
|
|
103
|
+
output_path.write_text(json.dumps(output_index, indent=2), encoding="utf-8")
|
|
104
|
+
return path
|
|
105
|
+
|
|
106
|
+
setattr(writer, "save_index", save_index)
|
|
107
|
+
return writer
|
|
108
|
+
|
|
109
|
+
|
|
25
110
|
def create_dataset_writer_from_index(
|
|
26
111
|
*,
|
|
27
112
|
index_output: Mapping[str, Any],
|
|
@@ -29,8 +114,6 @@ def create_dataset_writer_from_index(
|
|
|
29
114
|
zip: bool = False,
|
|
30
115
|
) -> DatasetWriter | ZipDatasetWriter:
|
|
31
116
|
"""Create a ds-crawler writer that mirrors an existing index's metadata."""
|
|
32
|
-
contract = get_dataset_contract(dict(index_output))
|
|
33
|
-
|
|
34
117
|
indexing = index_output.get("indexing")
|
|
35
118
|
hierarchy = indexing.get("hierarchy") if isinstance(indexing, Mapping) else None
|
|
36
119
|
id_cfg = indexing.get("id") if isinstance(indexing, Mapping) else None
|
|
@@ -39,11 +122,46 @@ def create_dataset_writer_from_index(
|
|
|
39
122
|
as_non_empty_str(id_cfg.get("join_char")) if isinstance(id_cfg, Mapping) else None,
|
|
40
123
|
)
|
|
41
124
|
writer_cls = ZipDatasetWriter if zip else DatasetWriter
|
|
42
|
-
|
|
125
|
+
try:
|
|
126
|
+
contract = get_dataset_contract(dict(index_output))
|
|
127
|
+
except Exception:
|
|
128
|
+
contract = None
|
|
129
|
+
|
|
130
|
+
if contract is not None:
|
|
131
|
+
writer = writer_cls(
|
|
132
|
+
root,
|
|
133
|
+
head=contract.to_mapping(),
|
|
134
|
+
separator=separator,
|
|
135
|
+
)
|
|
136
|
+
return _wrap_writer_save_index(writer)
|
|
137
|
+
|
|
138
|
+
name = as_non_empty_str(index_output.get("name"))
|
|
139
|
+
type_name = as_non_empty_str(index_output.get("type"))
|
|
140
|
+
if name is None or type_name is None:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
"Cannot create a dataset writer without ds-crawler head metadata. "
|
|
143
|
+
"Expected either a dataset contract or top-level 'name' and 'type'."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
euler_train = index_output.get("euler_train")
|
|
147
|
+
euler_loading = index_output.get("euler_loading")
|
|
148
|
+
meta = index_output.get("meta")
|
|
149
|
+
|
|
150
|
+
properties: dict[str, Any] = {}
|
|
151
|
+
if isinstance(euler_loading, Mapping):
|
|
152
|
+
properties["euler_loading"] = dict(euler_loading)
|
|
153
|
+
if isinstance(meta, Mapping):
|
|
154
|
+
properties["meta"] = dict(meta)
|
|
155
|
+
|
|
156
|
+
writer = writer_cls(
|
|
43
157
|
root,
|
|
44
|
-
|
|
158
|
+
name=name,
|
|
159
|
+
type=type_name,
|
|
160
|
+
euler_train=dict(euler_train) if isinstance(euler_train, Mapping) else None,
|
|
45
161
|
separator=separator,
|
|
162
|
+
**properties,
|
|
46
163
|
)
|
|
164
|
+
return _wrap_writer_save_index(writer)
|
|
47
165
|
|
|
48
166
|
|
|
49
167
|
def _resolve_output_destination(
|
|
@@ -59,6 +59,65 @@ __all__ = [
|
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
|
|
62
|
+
def _callable_name(value: Any) -> str:
|
|
63
|
+
module = getattr(value, "__module__", None)
|
|
64
|
+
qualname = getattr(value, "__qualname__", None)
|
|
65
|
+
if module and qualname:
|
|
66
|
+
return f"{module}.{qualname}"
|
|
67
|
+
name = getattr(value, "__name__", None)
|
|
68
|
+
if module and name:
|
|
69
|
+
return f"{module}.{name}"
|
|
70
|
+
cls = value.__class__
|
|
71
|
+
return f"{cls.__module__}.{cls.__qualname__}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _describe_transform(transform: Any) -> str:
|
|
75
|
+
describe = getattr(transform, "describe", None)
|
|
76
|
+
if callable(describe):
|
|
77
|
+
try:
|
|
78
|
+
return str(describe())
|
|
79
|
+
except Exception:
|
|
80
|
+
logger.debug(
|
|
81
|
+
"Failed to describe transform %s; falling back to callable name.",
|
|
82
|
+
_callable_name(transform),
|
|
83
|
+
exc_info=True,
|
|
84
|
+
)
|
|
85
|
+
return _callable_name(transform)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _get_index_tree(index_output: Mapping[str, Any]) -> dict[str, Any]:
|
|
89
|
+
"""Return the dataset tree from a ds-crawler output payload."""
|
|
90
|
+
index_tree = index_output.get("index")
|
|
91
|
+
if isinstance(index_tree, Mapping):
|
|
92
|
+
dataset_tree = index_tree.get("dataset")
|
|
93
|
+
if isinstance(dataset_tree, Mapping):
|
|
94
|
+
return dict(dataset_tree)
|
|
95
|
+
return dict(index_tree)
|
|
96
|
+
|
|
97
|
+
dataset_tree = index_output.get("dataset")
|
|
98
|
+
if isinstance(dataset_tree, Mapping):
|
|
99
|
+
return dict(dataset_tree)
|
|
100
|
+
|
|
101
|
+
raise KeyError(
|
|
102
|
+
"ds-crawler output must contain either an 'index' tree or a 'dataset' tree."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _get_index_meta(index_output: Mapping[str, Any]) -> dict[str, Any] | None:
|
|
107
|
+
"""Return modality meta from a ds-crawler output payload when available."""
|
|
108
|
+
try:
|
|
109
|
+
meta = get_dataset_contract(dict(index_output)).meta
|
|
110
|
+
except Exception:
|
|
111
|
+
meta = None
|
|
112
|
+
if isinstance(meta, Mapping):
|
|
113
|
+
return dict(meta)
|
|
114
|
+
|
|
115
|
+
raw_meta = index_output.get("meta")
|
|
116
|
+
if isinstance(raw_meta, Mapping):
|
|
117
|
+
return dict(raw_meta)
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
62
121
|
#TODO: might want to add slots=True in a 3.10+ only codebase
|
|
63
122
|
@dataclass(frozen=True)
|
|
64
123
|
class Modality:
|
|
@@ -320,7 +379,7 @@ class MultiModalDataset(_BaseDataset):
|
|
|
320
379
|
self._resolved_writers[name] = _resolve_writer(
|
|
321
380
|
modality_name=name, modality=modality, index=index,
|
|
322
381
|
)
|
|
323
|
-
records = collect_files(index
|
|
382
|
+
records = collect_files(_get_index_tree(index))
|
|
324
383
|
lookup = {
|
|
325
384
|
"/".join(rec.hierarchy_path + (rec.file_entry["id"],)): rec
|
|
326
385
|
for rec in records
|
|
@@ -352,7 +411,7 @@ class MultiModalDataset(_BaseDataset):
|
|
|
352
411
|
self._resolved_writers[name] = _resolve_writer(
|
|
353
412
|
modality_name=name, modality=modality, index=index,
|
|
354
413
|
)
|
|
355
|
-
files_by_level = collect_hierarchical_files(index
|
|
414
|
+
files_by_level = collect_hierarchical_files(_get_index_tree(index))
|
|
356
415
|
self._hierarchical_lookups[name] = files_by_level
|
|
357
416
|
total_files = sum(len(f) for f in files_by_level.values())
|
|
358
417
|
logger.info(
|
|
@@ -406,13 +465,28 @@ class MultiModalDataset(_BaseDataset):
|
|
|
406
465
|
# -- Caches ----------------------------------------------------------
|
|
407
466
|
self._hierarchical_cache: dict[str, Any] = {}
|
|
408
467
|
|
|
468
|
+
# -- Optional transform binding -------------------------------------
|
|
469
|
+
for transform in self._transforms:
|
|
470
|
+
bind = getattr(transform, "bind_to_dataset", None)
|
|
471
|
+
if callable(bind):
|
|
472
|
+
bind(self)
|
|
473
|
+
if self._transforms:
|
|
474
|
+
logger.info("Configured %d sample transform(s):", len(self._transforms))
|
|
475
|
+
for idx, transform in enumerate(self._transforms, start=1):
|
|
476
|
+
logger.info(
|
|
477
|
+
"Transform %d/%d: %s",
|
|
478
|
+
idx,
|
|
479
|
+
len(self._transforms),
|
|
480
|
+
_describe_transform(transform),
|
|
481
|
+
)
|
|
482
|
+
else:
|
|
483
|
+
logger.info("Configured 0 sample transforms.")
|
|
484
|
+
|
|
409
485
|
def get_modality_metadata(self, modality_name: str) -> dict[str, Any]:
|
|
410
486
|
"""Return the metadata dict for a given modality name."""
|
|
411
487
|
index = self._index_outputs.get(modality_name, {})
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
except Exception:
|
|
415
|
-
return {}
|
|
488
|
+
meta = _get_index_meta(index)
|
|
489
|
+
return meta or {}
|
|
416
490
|
|
|
417
491
|
def get_modality_index(self, modality_name: str) -> dict[str, Any]:
|
|
418
492
|
"""Return the cached ds-crawler index for a modality."""
|
|
@@ -510,12 +584,7 @@ class MultiModalDataset(_BaseDataset):
|
|
|
510
584
|
output_root=output_root, modality_name=modality_name
|
|
511
585
|
)
|
|
512
586
|
record = self._lookups[modality_name][sample_id]
|
|
513
|
-
|
|
514
|
-
modality_meta = get_dataset_contract(
|
|
515
|
-
self._index_outputs[modality_name]
|
|
516
|
-
).meta
|
|
517
|
-
except Exception:
|
|
518
|
-
modality_meta = None
|
|
587
|
+
modality_meta = _get_index_meta(self._index_outputs[modality_name])
|
|
519
588
|
basename = Path(record.file_entry["path"]).name
|
|
520
589
|
relative_path = record.file_entry["path"]
|
|
521
590
|
full_id = _build_writer_full_id(
|
|
@@ -558,7 +627,8 @@ class MultiModalDataset(_BaseDataset):
|
|
|
558
627
|
try:
|
|
559
628
|
name = get_dataset_contract(index).name
|
|
560
629
|
except Exception:
|
|
561
|
-
|
|
630
|
+
raw_name = index.get("name")
|
|
631
|
+
name = str(raw_name) if raw_name is not None else None
|
|
562
632
|
if name is None:
|
|
563
633
|
continue
|
|
564
634
|
if first_name is None:
|
|
@@ -617,12 +687,7 @@ class MultiModalDataset(_BaseDataset):
|
|
|
617
687
|
|
|
618
688
|
for name, modality in self._modalities.items():
|
|
619
689
|
record = self._lookups[name][sample_id]
|
|
620
|
-
|
|
621
|
-
modality_meta = get_dataset_contract(
|
|
622
|
-
self._index_outputs[name]
|
|
623
|
-
).meta
|
|
624
|
-
except Exception:
|
|
625
|
-
modality_meta = None
|
|
690
|
+
modality_meta = _get_index_meta(self._index_outputs[name])
|
|
626
691
|
|
|
627
692
|
if name in self._zip_modalities:
|
|
628
693
|
file_or_path = self._open_from_zip(
|
|
@@ -643,12 +708,7 @@ class MultiModalDataset(_BaseDataset):
|
|
|
643
708
|
for name, modality in self._hierarchical_modalities.items():
|
|
644
709
|
files_by_level = self._hierarchical_lookups[name]
|
|
645
710
|
matched = match_hierarchical_files(hierarchy_path, files_by_level)
|
|
646
|
-
|
|
647
|
-
modality_meta = get_dataset_contract(
|
|
648
|
-
self._hierarchical_index_outputs[name]
|
|
649
|
-
).meta
|
|
650
|
-
except Exception:
|
|
651
|
-
modality_meta = None
|
|
711
|
+
modality_meta = _get_index_meta(self._hierarchical_index_outputs[name])
|
|
652
712
|
loaded: dict[str, Any] = {}
|
|
653
713
|
for entry in matched:
|
|
654
714
|
cache_key = f"{modality.path}/{entry['path']}"
|