euler-loading 2.7.0__tar.gz → 2.8.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-2.7.0 → euler_loading-2.8.0}/PKG-INFO +1 -1
- {euler_loading-2.7.0 → euler_loading-2.8.0}/README.md +20 -83
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/_writing.py +4 -3
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/dataset.py +235 -454
- {euler_loading-2.7.0 → euler_loading-2.8.0}/package-lock.json +1 -1
- {euler_loading-2.7.0 → euler_loading-2.8.0}/pyproject.toml +1 -1
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/test_dataset.py +58 -478
- {euler_loading-2.7.0 → euler_loading-2.8.0}/.github/workflows/workflow.yml +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/.gitignore +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/docs/loader-attributes.md +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/__init__.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/_dataset_contract.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/_ds_crawler_utils.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/_metadata.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/_resolution.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/indexing.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/__init__.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/_annotations.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/_writer_utils.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/contracts.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/cpu/__init__.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/cpu/generic.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/generate/__init__.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/generate/__main__.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/generate/loaders.json +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/generic.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/gpu/__init__.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/gpu/generic.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/real_drive_sim.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/loaders/vkitti2.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/euler_loading/preprocessing.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/example.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/sample_rds.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/__init__.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/conftest.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/example_rds_calib.json +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/test_id_schema.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/test_indexing.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/test_loaders.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/test_preprocessing.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/test_real_dataset.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/tests/test_writing.py +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/vkitti_cpu_example_output.json +0 -0
- {euler_loading-2.7.0 → euler_loading-2.8.0}/vkitti_gpu_example_output.json +0 -0
|
@@ -4,7 +4,7 @@ Multi-modal PyTorch `Dataset` that synchronises files across arbitrary dataset m
|
|
|
4
4
|
|
|
5
5
|
Each modality points at a directory (or `.zip` archive) that carries its own `ds-crawler.config` (or cached `output.json`).
|
|
6
6
|
ds-crawler indexes the directory tree, discovers files, and exposes hierarchical metadata (path properties, calibration files, …).
|
|
7
|
-
euler-loading then **intersects file IDs** across all modalities so that every sample contains exactly one file per modality. Additional hierarchical data (e.g. per-scene calibration files) can be loaded via `hierarchical_modalities`. For augmentation-style datasets — many augmented files per ground-truth sample — `
|
|
7
|
+
euler-loading then **intersects file IDs** across all modalities so that every sample contains exactly one file per modality. Additional hierarchical data (e.g. per-scene calibration files) can be loaded via `hierarchical_modalities`. For augmentation-style datasets — many augmented files per ground-truth sample — `euler_layout` metadata lets `MultiModalDataset.from_layout(...)` load shared targets as collapsed hierarchical modalities.
|
|
8
8
|
How a file is actually **loaded** (image, depth map, point cloud, …) is configurable per modality — either supply a `Callable` or let euler-loading resolve a built-in loader automatically from the ds-crawler config.
|
|
9
9
|
Writer functions can be resolved the same way, so inference outputs can be written back in dataset-native formats.
|
|
10
10
|
|
|
@@ -65,8 +65,8 @@ Frozen dataclass describing one data modality.
|
|
|
65
65
|
| `hierarchy_scope` | `str \| None` | Optional scope label for hierarchical modalities (e.g. `scene_camera`). |
|
|
66
66
|
| `applies_to` | `list[str] \| None` | Optional list of regular modality names a hierarchical modality applies to. |
|
|
67
67
|
| `split` | `str \| None` | Optional inline split name. Loads `.ds_crawler/split_<name>.json` from the modality root (directory or zip) and overlays it on the normal ds-crawler metadata. |
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
68
|
+
| `cache` | `bool \| None` | Opt-in/out for in-memory caching of decoded values (only meaningful for hierarchical modalities; regular modalities are never cached). `True` keeps every distinct loaded file in a process-lifetime dict. `False` re-reads on every access. `None` (default): hierarchical → `True` because small shared calibration files benefit. |
|
|
69
|
+
| `collapse_single` | `bool` | For hierarchical modalities only. When `True`, a sample that matches exactly one hierarchical file returns that loaded value directly instead of `{file_id: value}`. This is useful for shared GT tensors used by multiple augmentations of the same source sample. |
|
|
70
70
|
| `metadata` | `dict[str, Any]` | Optional arbitrary metadata. Keys under `metadata["euler_loading"]` are treated as euler-loading defaults. |
|
|
71
71
|
|
|
72
72
|
The loader is the **only** place where domain-specific I/O happens.
|
|
@@ -137,30 +137,23 @@ Returns a dict mapping each regular modality name to `{"path": ..., "origin_path
|
|
|
137
137
|
|
|
138
138
|
Returns a dict mapping each hierarchical modality name to `{"path": ..., "origin_path": ...}` and includes `split` when configured.
|
|
139
139
|
|
|
140
|
-
### `MultiModalDataset.keyed_modality_paths()`
|
|
141
|
-
|
|
142
|
-
Returns a dict mapping each keyed modality name to `{"path": ..., "origin_path": ...}` plus `keyed_by_modality` (the resolved anchor) and `keyed_by_key_name` (the resolved or auto-detected key name). Includes `split` when configured. Useful for verifying which prefix the auto-detection picked.
|
|
143
|
-
|
|
144
140
|
### `MultiModalDataset.get_modality_metadata(modality_name)`
|
|
145
141
|
|
|
146
142
|
Returns the ds-crawler metadata dict for the given modality.
|
|
147
143
|
|
|
148
|
-
### `MultiModalDataset(modalities, hierarchical_modalities=None, transforms=None
|
|
144
|
+
### `MultiModalDataset(modalities, hierarchical_modalities=None, transforms=None)`
|
|
149
145
|
|
|
150
146
|
PyTorch `Dataset`. On construction it:
|
|
151
147
|
|
|
152
|
-
1. Runs `ds_crawler.index_dataset_from_path()` for every modality (regular
|
|
148
|
+
1. Runs `ds_crawler.index_dataset_from_path()` for every modality (regular and hierarchical).
|
|
153
149
|
2. Computes the **sorted intersection** of file IDs across all regular modalities.
|
|
154
|
-
3.
|
|
155
|
-
4. Logs warnings for unmatched files; raises `ValueError` when the intersection is empty or all samples are dropped by keyed-join validation.
|
|
150
|
+
3. Logs warnings for unmatched files; raises `ValueError` when the intersection is empty.
|
|
156
151
|
|
|
157
152
|
| Parameter | Type | Description |
|
|
158
153
|
|-----------|------|-------------|
|
|
159
154
|
| `modalities` | `dict[str, Modality]` | At least one entry required. Keys become the sample dict keys. These modalities participate in ID intersection. |
|
|
160
155
|
| `hierarchical_modalities` | `dict[str, Modality] \| None` | Optional modalities whose files live at intermediate hierarchy levels (e.g. per-scene intrinsics). These do **not** participate in ID intersection. Each sample will contain a dict `{file_id: loaded_result}` with all files at or above the sample's hierarchy level. Results are cached so shared files are parsed only once. |
|
|
161
|
-
| `keyed_modalities` | `dict[str, Modality] \| None` | Optional modalities joined to a regular sample by the value of the regular sample's deepest hierarchy key. See [Keyed modalities](#keyed-modalities) below. Contributes a single loaded value per sample (not a dict). Caching is **off by default** for keyed modalities (per-sample tensors can be large); set `Modality(..., cache=True)` to opt in when the keyed dataset is small enough to fit in memory. |
|
|
162
156
|
| `transforms` | `list[Callable[[dict], dict]] \| None` | Applied in order after loading. Each receives and returns the full sample dict. |
|
|
163
|
-
| `strict_keyed` | `bool` | When `True`, missing or mis-decoded keyed-modality joins raise immediately at construction instead of warning + dropping the affected samples. Default `False`. |
|
|
164
157
|
|
|
165
158
|
#### Sample dict
|
|
166
159
|
|
|
@@ -175,8 +168,6 @@ PyTorch `Dataset`. On construction it:
|
|
|
175
168
|
...
|
|
176
169
|
},
|
|
177
170
|
...
|
|
178
|
-
"<keyed_modality_name>": <loader result>, # one entry per keyed modality (single value)
|
|
179
|
-
...
|
|
180
171
|
"id": str, # file ID (leaf only, shared across modalities)
|
|
181
172
|
"full_id": str, # full hierarchical path including file ID (e.g. "/scene/camera/frame")
|
|
182
173
|
"meta": { # per-modality ds-crawler file entries
|
|
@@ -184,7 +175,7 @@ PyTorch `Dataset`. On construction it:
|
|
|
184
175
|
...
|
|
185
176
|
},
|
|
186
177
|
"attributes": { # per-modality top-level surface for file_entry["attributes"]
|
|
187
|
-
"<modality_name>": {...}, # for regular
|
|
178
|
+
"<modality_name>": {...}, # for regular modalities (single dict)
|
|
188
179
|
"<hierarchical_modality_name>": { # for hierarchical modalities (one dict per matched file)
|
|
189
180
|
"<file_id>": {...},
|
|
190
181
|
...
|
|
@@ -194,7 +185,7 @@ PyTorch `Dataset`. On construction it:
|
|
|
194
185
|
}
|
|
195
186
|
```
|
|
196
187
|
|
|
197
|
-
Hierarchical modality results are cached by default
|
|
188
|
+
Hierarchical modality results are cached by default. This can be flipped via `Modality(..., cache=True|False)` — see the [`cache` row in the `Modality` table](#modalitypath--loadernone-metadatanone).
|
|
198
189
|
|
|
199
190
|
### `FileRecord`
|
|
200
191
|
|
|
@@ -334,13 +325,9 @@ Files across modalities are matched by these IDs, so **the directory structure m
|
|
|
334
325
|
|
|
335
326
|
Calibration files or other per-scene/per-sequence metadata can be loaded via `hierarchical_modalities`. These files are matched to samples based on their position in the hierarchy — all files at or above a sample's hierarchy level are included and cached for efficiency.
|
|
336
327
|
|
|
337
|
-
##
|
|
338
|
-
|
|
339
|
-
A **keyed modality** joins each regular sample to a single file in another dataset by reading the *value* of the regular sample's deepest hierarchy key. This is the right tool when one modality augments samples of another at a different hierarchy depth — typically: many augmented files per ground-truth sample.
|
|
328
|
+
## Layout-aware augmentation loading
|
|
340
329
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
Layout where the augmented modality nests files under an extra `file_id:<id>` level, while the GT keeps the file id as the filename stem:
|
|
330
|
+
Augmentation datasets often contain many files for the same source sample, while a target modality such as GT depth should be reused for every augmentation of that source. The recommended layout is to index the source sample as a hierarchy level and the augmentation variant as the file ID:
|
|
344
331
|
|
|
345
332
|
```
|
|
346
333
|
augmented_rgb/
|
|
@@ -349,82 +336,32 @@ augmented_rgb/
|
|
|
349
336
|
scene_000000/CS_FRONT/file_id:000000000000000026/mor_10m.png
|
|
350
337
|
...
|
|
351
338
|
gt_depth/
|
|
352
|
-
scene_000000/CS_FRONT/000000000000000025.png
|
|
353
|
-
scene_000000/CS_FRONT/000000000000000026.png
|
|
339
|
+
scene_000000/CS_FRONT/file_id:000000000000000025/depth.png
|
|
340
|
+
scene_000000/CS_FRONT/file_id:000000000000000026/depth.png
|
|
354
341
|
...
|
|
355
342
|
```
|
|
356
343
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
### Wiring
|
|
344
|
+
Both modality indexes should include an `euler_layout` addon. The augmented RGB layout declares a `sample_axis` such as `file_id` at `location="hierarchy"` and a `variant_axis` at `location="file_id"`. The GT depth layout declares the same `sample_axis` and no variant axis.
|
|
360
345
|
|
|
361
346
|
```python
|
|
362
347
|
from euler_loading import Modality, MultiModalDataset
|
|
363
348
|
|
|
364
|
-
dataset = MultiModalDataset(
|
|
365
|
-
|
|
349
|
+
dataset = MultiModalDataset.from_layout(
|
|
350
|
+
{
|
|
366
351
|
"rgb_aug": Modality("/data/augmented_rgb", loader=load_rgb),
|
|
367
|
-
},
|
|
368
|
-
keyed_modalities={
|
|
369
352
|
"depth": Modality("/data/gt_depth", loader=load_depth),
|
|
370
353
|
},
|
|
354
|
+
primary="rgb_aug",
|
|
371
355
|
)
|
|
372
356
|
|
|
373
357
|
sample = dataset[0]
|
|
374
|
-
# sample["rgb_aug"] – the per-
|
|
375
|
-
# sample["depth"] – the GT depth for this file_id
|
|
358
|
+
# sample["rgb_aug"] – the per-augmentation RGB
|
|
359
|
+
# sample["depth"] – the GT depth for this file_id, shared across variants
|
|
376
360
|
# sample["full_id"] – e.g. "/scene_000000/CS_FRONT/file_id:000…025/mor_10m"
|
|
377
|
-
# sample["id"] – the augmentation's leaf id
|
|
361
|
+
# sample["id"] – the augmentation's leaf id, e.g. "mor_10m"
|
|
378
362
|
```
|
|
379
363
|
|
|
380
|
-
`
|
|
381
|
-
|
|
382
|
-
- there are multiple regular modalities (anchor must be picked: `keyed_by={"modality": "rgb_aug", "key_name": "file_id"}`); or
|
|
383
|
-
- the anchor's deepest hierarchy keys mix multiple prefixes (e.g. some `file_id:…` and some `frame:…`).
|
|
384
|
-
|
|
385
|
-
### Sample shape
|
|
386
|
-
|
|
387
|
-
A keyed modality contributes a **single loaded value** per sample, unlike hierarchical modalities which return `{file_id: loader_result}`. The join returns exactly one record by construction (one GT per augmented sample's file id at the parent hierarchy prefix), so a dict would be misleading.
|
|
388
|
-
|
|
389
|
-
### How the join works
|
|
390
|
-
|
|
391
|
-
For each common id in the regular modalities, euler-loading:
|
|
392
|
-
|
|
393
|
-
1. Looks up the anchor record's `hierarchy_path`, e.g. `(scene_000000, CS_FRONT, file_id:000…025)`.
|
|
394
|
-
2. Splits the deepest key on the regular modality's `indexing.hierarchy.separator` → `("file_id", "000…025")`.
|
|
395
|
-
3. Verifies the key name matches the configured (or auto-detected) `key_name`.
|
|
396
|
-
4. Looks up the keyed modality's record at hierarchy `(scene_000000, CS_FRONT)` with `id == "000…025"`.
|
|
397
|
-
|
|
398
|
-
### Caching
|
|
399
|
-
|
|
400
|
-
Caching for keyed modalities is **off by default**, which differs from hierarchical modalities (default on). The reason is workload shape: hierarchical files are usually tiny calibration / metadata blobs that you'd happily keep in memory forever, whereas a keyed modality typically holds the same kind of full per-sample tensor as a regular modality. Caching every decoded GT depth tensor in a 500 GB dataset would OOM the process.
|
|
401
|
-
|
|
402
|
-
Override per modality:
|
|
403
|
-
|
|
404
|
-
```python
|
|
405
|
-
keyed_modalities={
|
|
406
|
-
# Small GT (a few hundred MB total): worth caching to avoid re-reads
|
|
407
|
-
# across augmentations of the same file_id.
|
|
408
|
-
"depth": Modality("/data/gt_depth", loader=load_depth, cache=True),
|
|
409
|
-
# Large GT (multi-hundred-GB total): keep it off, re-read each access.
|
|
410
|
-
"lidar": Modality("/data/gt_lidar", loader=load_lidar), # cache=False default
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
When `cache=True`, the GT for `file_id:000…025` is decoded once and reused across every augmentation that points at it. When `cache=False` (the default), every sample access re-reads and re-decodes the file.
|
|
415
|
-
|
|
416
|
-
### Validation and missing joins
|
|
417
|
-
|
|
418
|
-
Construction-time validation runs the decoding for every common id. Samples whose decode or lookup fails are dropped from the dataset with a per-modality warning summarising the count. Pass `strict_keyed=True` to raise instead — useful in pipelines where dropped samples should be a hard error. Common error cases (with precise messages):
|
|
419
|
-
|
|
420
|
-
- key-name mismatch (deepest key starts with a different prefix);
|
|
421
|
-
- regular modality with no `indexing.hierarchy.separator`;
|
|
422
|
-
- ambiguous auto-detection (multiple distinct deepest-key prefixes in the anchor);
|
|
423
|
-
- regular modality with no hierarchy at all.
|
|
424
|
-
|
|
425
|
-
### Writing keyed-modality outputs
|
|
426
|
-
|
|
427
|
-
`MultiModalDataset.write_sample(...)` accepts keyed modality names alongside regular ones. The destination path is derived from the keyed record (the GT's relative path), so prediction outputs land in the GT-shape layout — *not* under a synthetic `file_id:` subdirectory.
|
|
364
|
+
`from_layout(...)` plans the GT modality as `hierarchical_modalities={"depth": Modality(..., collapse_single=True)}`. The hierarchical match uses the regular sample's hierarchy path, so source samples remain disambiguated by their parent hierarchy as well as by `file_id`. If a shared modality declares the same `sample_axis` but its index does not expose that axis as hierarchy, construction raises and the dataset should be re-indexed with the source sample axis in hierarchy.
|
|
428
365
|
|
|
429
366
|
## DenseDepthLoader protocol
|
|
430
367
|
|
|
@@ -55,10 +55,11 @@ def _wrap_writer_save_index(
|
|
|
55
55
|
result["meta"] = dict(meta)
|
|
56
56
|
|
|
57
57
|
if isinstance(addons, Mapping):
|
|
58
|
-
for addon_name in (
|
|
59
|
-
|
|
58
|
+
for addon_name, addon in addons.items():
|
|
59
|
+
if not str(addon_name).startswith("euler_"):
|
|
60
|
+
continue
|
|
60
61
|
if isinstance(addon, Mapping):
|
|
61
|
-
result[addon_name] = dict(addon)
|
|
62
|
+
result[str(addon_name)] = dict(addon)
|
|
62
63
|
|
|
63
64
|
index_tree = output.get("index")
|
|
64
65
|
if isinstance(index_tree, Mapping):
|