euler-loading 1.2.0__tar.gz → 2.0.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_loading-1.2.0 → euler_loading-2.0.0}/PKG-INFO +2 -1
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/__init__.py +1 -0
- euler_loading-2.0.0/euler_loading/_dataset_contract.py +91 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/_ds_crawler_utils.py +27 -19
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/_metadata.py +17 -7
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/_resolution.py +12 -2
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/_writing.py +8 -16
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/dataset.py +46 -9
- {euler_loading-1.2.0 → euler_loading-2.0.0}/package-lock.json +1 -1
- {euler_loading-1.2.0 → euler_loading-2.0.0}/pyproject.toml +2 -1
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/test_dataset.py +68 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/.github/workflows/workflow.yml +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/.gitignore +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/README.md +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/indexing.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/__init__.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/_annotations.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/_writer_utils.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/contracts.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/cpu/__init__.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/cpu/generic.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/generate/__init__.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/generate/__main__.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/generate/loaders.json +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/generic.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/gpu/__init__.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/gpu/generic.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/real_drive_sim.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/vkitti2.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/example.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/sample_rds.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/__init__.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/conftest.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/example_rds_calib.json +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/test_indexing.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/test_loaders.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/test_real_dataset.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/tests/test_writing.py +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/vkitti_cpu_example_output.json +0 -0
- {euler_loading-1.2.0 → euler_loading-2.0.0}/vkitti_gpu_example_output.json +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-loading
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Multi-modal PyTorch dataloader using ds-crawler indices
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Requires-Dist: ds-crawler
|
|
7
|
+
Requires-Dist: euler-dataset-contract
|
|
7
8
|
Requires-Dist: numpy
|
|
8
9
|
Requires-Dist: pillow
|
|
9
10
|
Provides-Extra: dev
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""euler-loading: Multi-modal PyTorch dataloader using ds-crawler indices."""
|
|
2
2
|
|
|
3
|
+
from . import _dataset_contract # noqa: F401
|
|
3
4
|
from ._resolution import resolve_loader_module, resolve_writer_module
|
|
4
5
|
from ._writing import create_dataset_writer_from_index
|
|
5
6
|
from .dataset import Modality, MultiModalDataset
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from euler_dataset_contract import (
|
|
6
|
+
register_addon_validator,
|
|
7
|
+
validate_addon_version,
|
|
8
|
+
validate_slot,
|
|
9
|
+
validate_string_list,
|
|
10
|
+
validate_token,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_ALLOWED_USED_AS = {"condition", "input", "output", "target"}
|
|
15
|
+
_ALLOWED_KEYS = {
|
|
16
|
+
"version",
|
|
17
|
+
"used_as",
|
|
18
|
+
"slot",
|
|
19
|
+
"hierarchy_scope",
|
|
20
|
+
"applies_to",
|
|
21
|
+
"task",
|
|
22
|
+
"loader",
|
|
23
|
+
"function",
|
|
24
|
+
"writer_function",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_euler_loading_addon(value: Any, context: str) -> None:
|
|
29
|
+
if not isinstance(value, dict):
|
|
30
|
+
raise ValueError(f"{context} must be an object")
|
|
31
|
+
|
|
32
|
+
unknown = sorted(set(value.keys()) - _ALLOWED_KEYS)
|
|
33
|
+
if unknown:
|
|
34
|
+
joined = ", ".join(unknown)
|
|
35
|
+
raise ValueError(f"Unknown {context} key(s): {joined}")
|
|
36
|
+
|
|
37
|
+
validate_addon_version(value.get("version"), f"{context}.version")
|
|
38
|
+
|
|
39
|
+
used_as = value.get("used_as")
|
|
40
|
+
if used_as is not None:
|
|
41
|
+
if not isinstance(used_as, str) or not used_as:
|
|
42
|
+
raise ValueError(f"{context}.used_as must be a non-empty string")
|
|
43
|
+
if used_as not in _ALLOWED_USED_AS:
|
|
44
|
+
allowed = ", ".join(sorted(_ALLOWED_USED_AS))
|
|
45
|
+
raise ValueError(
|
|
46
|
+
f"{context}.used_as must be one of {{{allowed}}}, got {used_as!r}"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
slot = value.get("slot")
|
|
50
|
+
if slot is not None:
|
|
51
|
+
validate_slot(slot, f"{context}.slot")
|
|
52
|
+
|
|
53
|
+
task = value.get("task")
|
|
54
|
+
if task is not None:
|
|
55
|
+
validate_token(task, f"{context}.task")
|
|
56
|
+
|
|
57
|
+
loader = value.get("loader")
|
|
58
|
+
function = value.get("function")
|
|
59
|
+
if loader is not None:
|
|
60
|
+
validate_token(loader, f"{context}.loader")
|
|
61
|
+
if function is not None:
|
|
62
|
+
validate_token(function, f"{context}.function")
|
|
63
|
+
if (loader is None) != (function is None):
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"{context}.loader and {context}.function must either both be set "
|
|
66
|
+
"or both be omitted"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
writer_function = value.get("writer_function")
|
|
70
|
+
if writer_function is not None:
|
|
71
|
+
validate_token(writer_function, f"{context}.writer_function")
|
|
72
|
+
|
|
73
|
+
hierarchy_scope = value.get("hierarchy_scope")
|
|
74
|
+
applies_to = value.get("applies_to")
|
|
75
|
+
if hierarchy_scope is not None or applies_to is not None:
|
|
76
|
+
if used_as != "condition":
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"{context}.hierarchy_scope and {context}.applies_to are only "
|
|
79
|
+
"allowed when used_as is 'condition'"
|
|
80
|
+
)
|
|
81
|
+
if hierarchy_scope is not None:
|
|
82
|
+
validate_token(hierarchy_scope, f"{context}.hierarchy_scope")
|
|
83
|
+
if applies_to is not None:
|
|
84
|
+
validate_string_list(
|
|
85
|
+
applies_to,
|
|
86
|
+
f"{context}.applies_to",
|
|
87
|
+
allow_wildcard=True,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
register_addon_validator("euler_loading", validate_euler_loading_addon, overwrite=True)
|
|
@@ -5,6 +5,7 @@ from collections.abc import Mapping
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from ds_crawler import get_dataset_contract
|
|
8
9
|
from ds_crawler.zip_utils import read_metadata_json
|
|
9
10
|
|
|
10
11
|
try:
|
|
@@ -13,19 +14,6 @@ except ImportError:
|
|
|
13
14
|
_load_dataset_split = None
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
DS_CRAWLER_STRUCTURAL_KEYS = frozenset({
|
|
17
|
-
"name",
|
|
18
|
-
"type",
|
|
19
|
-
"id_regex",
|
|
20
|
-
"id_regex_join_char",
|
|
21
|
-
"euler_train",
|
|
22
|
-
"dataset",
|
|
23
|
-
"hierarchy_regex",
|
|
24
|
-
"named_capture_group_value_separator",
|
|
25
|
-
"sampled",
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
|
|
29
17
|
def as_non_empty_str(value: Any) -> str | None:
|
|
30
18
|
if value is None:
|
|
31
19
|
return None
|
|
@@ -61,16 +49,35 @@ def first_non_empty_list(*candidates: list[str] | None) -> list[str]:
|
|
|
61
49
|
|
|
62
50
|
|
|
63
51
|
def extract_ds_crawler_properties(index_output: Mapping[str, Any]) -> dict[str, Any]:
|
|
64
|
-
return
|
|
65
|
-
str(key): value
|
|
66
|
-
for key, value in index_output.items()
|
|
67
|
-
if key not in DS_CRAWLER_STRUCTURAL_KEYS
|
|
68
|
-
}
|
|
52
|
+
return get_dataset_contract(dict(index_output)).to_properties_dict()
|
|
69
53
|
|
|
70
54
|
|
|
71
55
|
_SPLIT_NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*$")
|
|
72
56
|
|
|
73
57
|
|
|
58
|
+
def parse_path_with_split(path: str) -> tuple[str, str | None]:
|
|
59
|
+
"""Extract an optional inline split suffix from a colon-separated path.
|
|
60
|
+
|
|
61
|
+
Supports paths like ``/data/ds.zip:train`` where the part after the
|
|
62
|
+
last colon is treated as the split name.
|
|
63
|
+
|
|
64
|
+
Returns a ``(path, split)`` tuple. When no valid split suffix is
|
|
65
|
+
found, *split* is ``None`` and *path* is returned unchanged.
|
|
66
|
+
"""
|
|
67
|
+
colon_pos = path.rfind(":")
|
|
68
|
+
# No colon, or colon at position 1 is a Windows drive letter (e.g. "C:\\")
|
|
69
|
+
if colon_pos <= 1:
|
|
70
|
+
return path, None
|
|
71
|
+
|
|
72
|
+
candidate_split = path[colon_pos + 1:]
|
|
73
|
+
candidate_path = path[:colon_pos]
|
|
74
|
+
|
|
75
|
+
if not candidate_split or not _SPLIT_NAME_PATTERN.match(candidate_split):
|
|
76
|
+
return path, None
|
|
77
|
+
|
|
78
|
+
return candidate_path, candidate_split
|
|
79
|
+
|
|
80
|
+
|
|
74
81
|
def validate_split_name(split_name: str) -> str:
|
|
75
82
|
"""Validate and normalize an inline split name."""
|
|
76
83
|
if not isinstance(split_name, str):
|
|
@@ -141,5 +148,6 @@ def load_index_output(
|
|
|
141
148
|
)
|
|
142
149
|
|
|
143
150
|
result = dict(base_output)
|
|
144
|
-
result["
|
|
151
|
+
result["index"] = split_dataset
|
|
152
|
+
result.pop("dataset", None)
|
|
145
153
|
return result
|
|
@@ -4,6 +4,8 @@ import re
|
|
|
4
4
|
from collections.abc import Mapping
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Callable
|
|
6
6
|
|
|
7
|
+
from ds_crawler import get_dataset_contract
|
|
8
|
+
|
|
7
9
|
from ._ds_crawler_utils import (
|
|
8
10
|
as_non_empty_str,
|
|
9
11
|
as_string_list,
|
|
@@ -44,6 +46,7 @@ def _build_runlog_entry(
|
|
|
44
46
|
as_non_empty_str(
|
|
45
47
|
_resolve_euler_loading_property(euler_loading_properties, "modality_type")
|
|
46
48
|
),
|
|
49
|
+
as_non_empty_str(descriptor.get("modality_key")),
|
|
47
50
|
as_non_empty_str(descriptor.get("modality_type")),
|
|
48
51
|
_infer_modality_type(name=name, path=modality.path),
|
|
49
52
|
)
|
|
@@ -146,12 +149,19 @@ def _read_ds_crawler_descriptor(
|
|
|
146
149
|
|
|
147
150
|
if isinstance(index_output, Mapping):
|
|
148
151
|
properties.update(extract_ds_crawler_properties(index_output))
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if
|
|
154
|
-
descriptor["
|
|
152
|
+
try:
|
|
153
|
+
contract = get_dataset_contract(dict(index_output))
|
|
154
|
+
except Exception:
|
|
155
|
+
contract = None
|
|
156
|
+
if contract is not None:
|
|
157
|
+
descriptor["modality_key"] = contract.modality_key
|
|
158
|
+
indexing = index_output.get("indexing")
|
|
159
|
+
if isinstance(indexing, Mapping):
|
|
160
|
+
hierarchy = indexing.get("hierarchy")
|
|
161
|
+
if isinstance(hierarchy, Mapping):
|
|
162
|
+
hierarchy_regex = as_non_empty_str(hierarchy.get("regex"))
|
|
163
|
+
if hierarchy_regex is not None:
|
|
164
|
+
descriptor["hierarchy_regex"] = hierarchy_regex
|
|
155
165
|
|
|
156
166
|
try:
|
|
157
167
|
cfg = load_dataset_config_fn({"path": path})
|
|
@@ -164,7 +174,7 @@ def _read_ds_crawler_descriptor(
|
|
|
164
174
|
|
|
165
175
|
cfg_type = as_non_empty_str(getattr(cfg, "type", None))
|
|
166
176
|
if cfg_type is not None:
|
|
167
|
-
descriptor["
|
|
177
|
+
descriptor["modality_key"] = cfg_type
|
|
168
178
|
|
|
169
179
|
cfg_hierarchy_regex = as_non_empty_str(getattr(cfg, "hierarchy_regex", None))
|
|
170
180
|
if cfg_hierarchy_regex is not None:
|
|
@@ -6,6 +6,8 @@ from collections.abc import Mapping
|
|
|
6
6
|
from types import ModuleType
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Callable
|
|
8
8
|
|
|
9
|
+
from ds_crawler import get_dataset_contract
|
|
10
|
+
|
|
9
11
|
from ._ds_crawler_utils import as_non_empty_str
|
|
10
12
|
|
|
11
13
|
if TYPE_CHECKING:
|
|
@@ -61,7 +63,11 @@ def _resolve_loader(
|
|
|
61
63
|
if modality.loader is not None:
|
|
62
64
|
return modality.loader
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
try:
|
|
67
|
+
contract = get_dataset_contract(index)
|
|
68
|
+
euler_loading_meta = contract.get_addon("euler_loading")
|
|
69
|
+
except Exception:
|
|
70
|
+
euler_loading_meta = None
|
|
65
71
|
if not isinstance(euler_loading_meta, Mapping):
|
|
66
72
|
raise ValueError(
|
|
67
73
|
f"Modality {modality_name!r}: no explicit loader provided and the "
|
|
@@ -126,7 +132,11 @@ def _resolve_writer(
|
|
|
126
132
|
if modality.writer is not None:
|
|
127
133
|
return modality.writer
|
|
128
134
|
|
|
129
|
-
|
|
135
|
+
try:
|
|
136
|
+
contract = get_dataset_contract(index)
|
|
137
|
+
euler_loading_meta = contract.get_addon("euler_loading")
|
|
138
|
+
except Exception:
|
|
139
|
+
euler_loading_meta = None
|
|
130
140
|
if not isinstance(euler_loading_meta, Mapping):
|
|
131
141
|
return None
|
|
132
142
|
|
|
@@ -7,11 +7,10 @@ from collections.abc import Mapping
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any, Callable
|
|
9
9
|
|
|
10
|
-
from ds_crawler import DatasetWriter, ZipDatasetWriter
|
|
10
|
+
from ds_crawler import DatasetWriter, ZipDatasetWriter, get_dataset_contract
|
|
11
11
|
|
|
12
12
|
from ._ds_crawler_utils import (
|
|
13
13
|
as_non_empty_str,
|
|
14
|
-
extract_ds_crawler_properties,
|
|
15
14
|
first_non_empty,
|
|
16
15
|
)
|
|
17
16
|
from .loaders._writer_utils import supports_stream_target
|
|
@@ -30,27 +29,20 @@ def create_dataset_writer_from_index(
|
|
|
30
29
|
zip: bool = False,
|
|
31
30
|
) -> DatasetWriter | ZipDatasetWriter:
|
|
32
31
|
"""Create a ds-crawler writer that mirrors an existing index's metadata."""
|
|
33
|
-
|
|
34
|
-
type_name = as_non_empty_str(index_output.get("type")) or "other"
|
|
35
|
-
euler_train = index_output.get("euler_train")
|
|
36
|
-
if not isinstance(euler_train, Mapping):
|
|
37
|
-
raise ValueError(
|
|
38
|
-
"index_output must contain an 'euler_train' mapping to build a writer."
|
|
39
|
-
)
|
|
32
|
+
contract = get_dataset_contract(dict(index_output))
|
|
40
33
|
|
|
34
|
+
indexing = index_output.get("indexing")
|
|
35
|
+
hierarchy = indexing.get("hierarchy") if isinstance(indexing, Mapping) else None
|
|
36
|
+
id_cfg = indexing.get("id") if isinstance(indexing, Mapping) else None
|
|
41
37
|
separator = first_non_empty(
|
|
42
|
-
as_non_empty_str(
|
|
43
|
-
as_non_empty_str(
|
|
38
|
+
as_non_empty_str(hierarchy.get("separator")) if isinstance(hierarchy, Mapping) else None,
|
|
39
|
+
as_non_empty_str(id_cfg.get("join_char")) if isinstance(id_cfg, Mapping) else None,
|
|
44
40
|
)
|
|
45
|
-
properties = extract_ds_crawler_properties(index_output)
|
|
46
41
|
writer_cls = ZipDatasetWriter if zip else DatasetWriter
|
|
47
42
|
return writer_cls(
|
|
48
43
|
root,
|
|
49
|
-
|
|
50
|
-
type=type_name,
|
|
51
|
-
euler_train=dict(euler_train),
|
|
44
|
+
head=contract.to_mapping(),
|
|
52
45
|
separator=separator,
|
|
53
|
-
**properties,
|
|
54
46
|
)
|
|
55
47
|
|
|
56
48
|
|
|
@@ -18,12 +18,13 @@ except ImportError:
|
|
|
18
18
|
from ds_crawler import (
|
|
19
19
|
DatasetWriter,
|
|
20
20
|
ZipDatasetWriter,
|
|
21
|
+
get_dataset_contract,
|
|
21
22
|
index_dataset_from_path,
|
|
22
23
|
load_dataset_config,
|
|
23
24
|
)
|
|
24
25
|
from ds_crawler.zip_utils import get_zip_root_prefix, is_zip_path
|
|
25
26
|
|
|
26
|
-
from ._ds_crawler_utils import load_index_output
|
|
27
|
+
from ._ds_crawler_utils import load_index_output, parse_path_with_split
|
|
27
28
|
from ._metadata import _build_runlog_entry, _get_ds_crawler_descriptor
|
|
28
29
|
from ._resolution import (
|
|
29
30
|
_resolve_loader,
|
|
@@ -66,7 +67,9 @@ class Modality:
|
|
|
66
67
|
Attributes:
|
|
67
68
|
path: Absolute path to the dataset root directory for this modality.
|
|
68
69
|
Must contain a ``ds-crawler.config`` file (or a cached
|
|
69
|
-
``output.json`` from a prior indexing run).
|
|
70
|
+
``output.json`` from a prior indexing run). A colon-separated
|
|
71
|
+
split suffix is also accepted (e.g. ``/data/ds.zip:train``);
|
|
72
|
+
the suffix is extracted and used as the ``split`` parameter.
|
|
70
73
|
origin_path: Optional original path string before any copying or symlinking for i.e. slurm staging.
|
|
71
74
|
This is not used by euler-loading itself but can be useful for experiment
|
|
72
75
|
logging to retain references to the original dataset location.
|
|
@@ -113,6 +116,18 @@ class Modality:
|
|
|
113
116
|
split: str | None = None
|
|
114
117
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
115
118
|
|
|
119
|
+
def __post_init__(self) -> None:
|
|
120
|
+
parsed_path, path_split = parse_path_with_split(self.path)
|
|
121
|
+
if path_split is not None:
|
|
122
|
+
if self.split is not None:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"Modality path {self.path!r} contains an inline split "
|
|
125
|
+
f"({path_split!r}) but an explicit split={self.split!r} "
|
|
126
|
+
f"was also provided. Use one or the other, not both."
|
|
127
|
+
)
|
|
128
|
+
object.__setattr__(self, "path", parsed_path)
|
|
129
|
+
object.__setattr__(self, "split", path_split)
|
|
130
|
+
|
|
116
131
|
|
|
117
132
|
class MultiModalDataset(_BaseDataset):
|
|
118
133
|
"""PyTorch Dataset that loads synchronized multi-modal samples.
|
|
@@ -305,7 +320,7 @@ class MultiModalDataset(_BaseDataset):
|
|
|
305
320
|
self._resolved_writers[name] = _resolve_writer(
|
|
306
321
|
modality_name=name, modality=modality, index=index,
|
|
307
322
|
)
|
|
308
|
-
records = collect_files(index["
|
|
323
|
+
records = collect_files(index["index"])
|
|
309
324
|
lookup = {
|
|
310
325
|
"/".join(rec.hierarchy_path + (rec.file_entry["id"],)): rec
|
|
311
326
|
for rec in records
|
|
@@ -337,7 +352,7 @@ class MultiModalDataset(_BaseDataset):
|
|
|
337
352
|
self._resolved_writers[name] = _resolve_writer(
|
|
338
353
|
modality_name=name, modality=modality, index=index,
|
|
339
354
|
)
|
|
340
|
-
files_by_level = collect_hierarchical_files(index["
|
|
355
|
+
files_by_level = collect_hierarchical_files(index["index"])
|
|
341
356
|
self._hierarchical_lookups[name] = files_by_level
|
|
342
357
|
total_files = sum(len(f) for f in files_by_level.values())
|
|
343
358
|
logger.info(
|
|
@@ -393,7 +408,11 @@ class MultiModalDataset(_BaseDataset):
|
|
|
393
408
|
|
|
394
409
|
def get_modality_metadata(self, modality_name: str) -> dict[str, Any]:
|
|
395
410
|
"""Return the metadata dict for a given modality name."""
|
|
396
|
-
|
|
411
|
+
index = self._index_outputs.get(modality_name, {})
|
|
412
|
+
try:
|
|
413
|
+
return get_dataset_contract(index).meta or {}
|
|
414
|
+
except Exception:
|
|
415
|
+
return {}
|
|
397
416
|
|
|
398
417
|
def get_modality_index(self, modality_name: str) -> dict[str, Any]:
|
|
399
418
|
"""Return the cached ds-crawler index for a modality."""
|
|
@@ -491,7 +510,12 @@ class MultiModalDataset(_BaseDataset):
|
|
|
491
510
|
output_root=output_root, modality_name=modality_name
|
|
492
511
|
)
|
|
493
512
|
record = self._lookups[modality_name][sample_id]
|
|
494
|
-
|
|
513
|
+
try:
|
|
514
|
+
modality_meta = get_dataset_contract(
|
|
515
|
+
self._index_outputs[modality_name]
|
|
516
|
+
).meta
|
|
517
|
+
except Exception:
|
|
518
|
+
modality_meta = None
|
|
495
519
|
basename = Path(record.file_entry["path"]).name
|
|
496
520
|
relative_path = record.file_entry["path"]
|
|
497
521
|
full_id = _build_writer_full_id(
|
|
@@ -531,7 +555,10 @@ class MultiModalDataset(_BaseDataset):
|
|
|
531
555
|
first_modality: str | None = None
|
|
532
556
|
|
|
533
557
|
for modality_name, index in self._index_outputs.items():
|
|
534
|
-
|
|
558
|
+
try:
|
|
559
|
+
name = get_dataset_contract(index).name
|
|
560
|
+
except Exception:
|
|
561
|
+
name = None
|
|
535
562
|
if name is None:
|
|
536
563
|
continue
|
|
537
564
|
if first_name is None:
|
|
@@ -590,7 +617,12 @@ class MultiModalDataset(_BaseDataset):
|
|
|
590
617
|
|
|
591
618
|
for name, modality in self._modalities.items():
|
|
592
619
|
record = self._lookups[name][sample_id]
|
|
593
|
-
|
|
620
|
+
try:
|
|
621
|
+
modality_meta = get_dataset_contract(
|
|
622
|
+
self._index_outputs[name]
|
|
623
|
+
).meta
|
|
624
|
+
except Exception:
|
|
625
|
+
modality_meta = None
|
|
594
626
|
|
|
595
627
|
if name in self._zip_modalities:
|
|
596
628
|
file_or_path = self._open_from_zip(
|
|
@@ -611,7 +643,12 @@ class MultiModalDataset(_BaseDataset):
|
|
|
611
643
|
for name, modality in self._hierarchical_modalities.items():
|
|
612
644
|
files_by_level = self._hierarchical_lookups[name]
|
|
613
645
|
matched = match_hierarchical_files(hierarchy_path, files_by_level)
|
|
614
|
-
|
|
646
|
+
try:
|
|
647
|
+
modality_meta = get_dataset_contract(
|
|
648
|
+
self._hierarchical_index_outputs[name]
|
|
649
|
+
).meta
|
|
650
|
+
except Exception:
|
|
651
|
+
modality_meta = None
|
|
615
652
|
loaded: dict[str, Any] = {}
|
|
616
653
|
for entry in matched:
|
|
617
654
|
cache_key = f"{modality.path}/{entry['path']}"
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "euler-loading"
|
|
3
|
-
version = "
|
|
3
|
+
version = "2.0.0"
|
|
4
4
|
description = "Multi-modal PyTorch dataloader using ds-crawler indices"
|
|
5
5
|
requires-python = ">=3.9"
|
|
6
6
|
dependencies = [
|
|
7
7
|
"numpy",
|
|
8
8
|
"Pillow",
|
|
9
9
|
"ds-crawler",
|
|
10
|
+
"euler-dataset-contract",
|
|
10
11
|
]
|
|
11
12
|
|
|
12
13
|
[project.optional-dependencies]
|
|
@@ -301,6 +301,74 @@ class TestInlineSplitLoading:
|
|
|
301
301
|
assert buf.read() == b"fake-png-002"
|
|
302
302
|
|
|
303
303
|
|
|
304
|
+
class TestPathWithColonSplit:
|
|
305
|
+
"""Colon-separated path:split syntax."""
|
|
306
|
+
|
|
307
|
+
def test_split_extracted_from_path(self):
|
|
308
|
+
mod = Modality("/data/ds:train")
|
|
309
|
+
assert mod.path == "/data/ds"
|
|
310
|
+
assert mod.split == "train"
|
|
311
|
+
|
|
312
|
+
def test_split_extracted_from_zip_path(self):
|
|
313
|
+
mod = Modality("/data/ds.zip:val")
|
|
314
|
+
assert mod.path == "/data/ds.zip"
|
|
315
|
+
assert mod.split == "val"
|
|
316
|
+
|
|
317
|
+
def test_no_colon_leaves_split_none(self):
|
|
318
|
+
mod = Modality("/data/ds")
|
|
319
|
+
assert mod.path == "/data/ds"
|
|
320
|
+
assert mod.split is None
|
|
321
|
+
|
|
322
|
+
def test_explicit_split_without_colon_still_works(self):
|
|
323
|
+
mod = Modality("/data/ds", split="train")
|
|
324
|
+
assert mod.path == "/data/ds"
|
|
325
|
+
assert mod.split == "train"
|
|
326
|
+
|
|
327
|
+
def test_colon_and_explicit_split_raises(self):
|
|
328
|
+
with pytest.raises(ValueError, match="inline split"):
|
|
329
|
+
Modality("/data/ds:train", split="val")
|
|
330
|
+
|
|
331
|
+
def test_windows_drive_letter_not_treated_as_split(self):
|
|
332
|
+
mod = Modality("C:\\data\\ds")
|
|
333
|
+
assert mod.path == "C:\\data\\ds"
|
|
334
|
+
assert mod.split is None
|
|
335
|
+
|
|
336
|
+
def test_invalid_split_suffix_left_in_path(self):
|
|
337
|
+
# A colon followed by an invalid split name is left as-is
|
|
338
|
+
mod = Modality("/data/ds:bad/name")
|
|
339
|
+
assert mod.path == "/data/ds:bad/name"
|
|
340
|
+
assert mod.split is None
|
|
341
|
+
|
|
342
|
+
def test_colon_path_integration_with_dataset(self, tmp_path):
|
|
343
|
+
root = tmp_path / "rgb"
|
|
344
|
+
root.mkdir()
|
|
345
|
+
rgb_index = _flat_index("rgb", ["f001", "f002"])
|
|
346
|
+
rgb_split = _flat_index("rgb", ["f002"])["dataset"]
|
|
347
|
+
_write_inline_split(root, "train", rgb_split)
|
|
348
|
+
|
|
349
|
+
def mock_index(path, **kw):
|
|
350
|
+
return rgb_index
|
|
351
|
+
|
|
352
|
+
with patch(
|
|
353
|
+
"euler_loading.dataset.index_dataset_from_path",
|
|
354
|
+
side_effect=mock_index,
|
|
355
|
+
):
|
|
356
|
+
ds = MultiModalDataset(
|
|
357
|
+
modalities={
|
|
358
|
+
"rgb": Modality(
|
|
359
|
+
f"{root}:train", loader=dummy_loader
|
|
360
|
+
),
|
|
361
|
+
},
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
assert len(ds) == 1
|
|
365
|
+
assert ds[0]["id"] == "f002"
|
|
366
|
+
assert ds.modality_paths()["rgb"] == {
|
|
367
|
+
"path": str(root),
|
|
368
|
+
"origin_path": None,
|
|
369
|
+
"split": "train",
|
|
370
|
+
}
|
|
371
|
+
|
|
304
372
|
|
|
305
373
|
class TestTransforms:
|
|
306
374
|
"""Transform application and ordering."""
|
|
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_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/cpu/generic_dense_depth.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_loading-1.2.0 → euler_loading-2.0.0}/euler_loading/loaders/gpu/generic_dense_depth.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|