euler-loading 2.6.0__tar.gz → 2.7.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.6.0 → euler_loading-2.7.0}/PKG-INFO +1 -1
- {euler_loading-2.6.0 → euler_loading-2.7.0}/README.md +118 -7
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/dataset.py +490 -12
- {euler_loading-2.6.0 → euler_loading-2.7.0}/pyproject.toml +1 -1
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/test_dataset.py +562 -1
- {euler_loading-2.6.0 → euler_loading-2.7.0}/.github/workflows/workflow.yml +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/.gitignore +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/docs/loader-attributes.md +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/__init__.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/_dataset_contract.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/_ds_crawler_utils.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/_metadata.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/_resolution.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/_writing.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/indexing.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/__init__.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/_annotations.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/_writer_utils.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/contracts.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/cpu/__init__.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/cpu/generic.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/generate/__init__.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/generate/__main__.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/generate/loaders.json +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/generic.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/gpu/__init__.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/gpu/generic.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/real_drive_sim.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/loaders/vkitti2.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/euler_loading/preprocessing.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/example.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/package-lock.json +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/sample_rds.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/__init__.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/conftest.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/example_rds_calib.json +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/test_id_schema.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/test_indexing.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/test_loaders.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/test_preprocessing.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/test_real_dataset.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/tests/test_writing.py +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.0}/vkitti_cpu_example_output.json +0 -0
- {euler_loading-2.6.0 → euler_loading-2.7.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`.
|
|
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 — `keyed_modalities` joins each augmented sample to its single GT file via the value encoded in the augmented sample's deepest hierarchy key.
|
|
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,6 +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
|
+
| `keyed_by` | `Mapping[str, str] \| None` | Optional join configuration used only when this modality is passed under `MultiModalDataset(keyed_modalities=...)`. Recognised keys: `key_name` (named-group prefix at the regular modality's deepest hierarchy key, e.g. `"file_id"`; auto-detected from the anchor's data when omitted) and `modality` (anchor regular-modality name; auto-inferred when there's exactly one regular). Both keys are optional — the kwarg can be left unset entirely. |
|
|
69
|
+
| `cache` | `bool \| None` | Opt-in/out for in-memory caching of decoded values (only meaningful for hierarchical and keyed 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` (small calibration files benefit), keyed → `False` (safe for the augmentation case where the keyed modality holds full per-sample tensors and unbounded caching would OOM). |
|
|
68
70
|
| `metadata` | `dict[str, Any]` | Optional arbitrary metadata. Keys under `metadata["euler_loading"]` are treated as euler-loading defaults. |
|
|
69
71
|
|
|
70
72
|
The loader is the **only** place where domain-specific I/O happens.
|
|
@@ -135,23 +137,30 @@ Returns a dict mapping each regular modality name to `{"path": ..., "origin_path
|
|
|
135
137
|
|
|
136
138
|
Returns a dict mapping each hierarchical modality name to `{"path": ..., "origin_path": ...}` and includes `split` when configured.
|
|
137
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
|
+
|
|
138
144
|
### `MultiModalDataset.get_modality_metadata(modality_name)`
|
|
139
145
|
|
|
140
146
|
Returns the ds-crawler metadata dict for the given modality.
|
|
141
147
|
|
|
142
|
-
### `MultiModalDataset(modalities, hierarchical_modalities=None, transforms=None)`
|
|
148
|
+
### `MultiModalDataset(modalities, hierarchical_modalities=None, transforms=None, keyed_modalities=None, strict_keyed=False)`
|
|
143
149
|
|
|
144
150
|
PyTorch `Dataset`. On construction it:
|
|
145
151
|
|
|
146
|
-
1. Runs `ds_crawler.index_dataset_from_path()` for every modality (regular and
|
|
152
|
+
1. Runs `ds_crawler.index_dataset_from_path()` for every modality (regular, hierarchical, and keyed).
|
|
147
153
|
2. Computes the **sorted intersection** of file IDs across all regular modalities.
|
|
148
|
-
3.
|
|
154
|
+
3. Validates keyed-modality joins for every common id; drops samples whose join misses (or raises when `strict_keyed=True`).
|
|
155
|
+
4. Logs warnings for unmatched files; raises `ValueError` when the intersection is empty or all samples are dropped by keyed-join validation.
|
|
149
156
|
|
|
150
157
|
| Parameter | Type | Description |
|
|
151
158
|
|-----------|------|-------------|
|
|
152
159
|
| `modalities` | `dict[str, Modality]` | At least one entry required. Keys become the sample dict keys. These modalities participate in ID intersection. |
|
|
153
160
|
| `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. |
|
|
154
162
|
| `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`. |
|
|
155
164
|
|
|
156
165
|
#### Sample dict
|
|
157
166
|
|
|
@@ -166,16 +175,26 @@ PyTorch `Dataset`. On construction it:
|
|
|
166
175
|
...
|
|
167
176
|
},
|
|
168
177
|
...
|
|
178
|
+
"<keyed_modality_name>": <loader result>, # one entry per keyed modality (single value)
|
|
179
|
+
...
|
|
169
180
|
"id": str, # file ID (leaf only, shared across modalities)
|
|
170
181
|
"full_id": str, # full hierarchical path including file ID (e.g. "/scene/camera/frame")
|
|
171
|
-
"meta": { # per-modality ds-crawler file entries
|
|
172
|
-
"<modality_name>": {"id": ..., "path": ..., "path_properties": ..., "basename_properties": ...},
|
|
182
|
+
"meta": { # per-modality ds-crawler file entries
|
|
183
|
+
"<modality_name>": {"id": ..., "path": ..., "path_properties": ..., "basename_properties": ..., "attributes": ...},
|
|
184
|
+
...
|
|
185
|
+
},
|
|
186
|
+
"attributes": { # per-modality top-level surface for file_entry["attributes"]
|
|
187
|
+
"<modality_name>": {...}, # for regular and keyed modalities (single dict)
|
|
188
|
+
"<hierarchical_modality_name>": { # for hierarchical modalities (one dict per matched file)
|
|
189
|
+
"<file_id>": {...},
|
|
190
|
+
...
|
|
191
|
+
},
|
|
173
192
|
...
|
|
174
193
|
},
|
|
175
194
|
}
|
|
176
195
|
```
|
|
177
196
|
|
|
178
|
-
Hierarchical modality results are cached
|
|
197
|
+
Hierarchical modality results are cached by default; keyed modality results are not. Both can be flipped via `Modality(..., cache=True|False)` — see the [`cache` row in the `Modality` table](#modalitypath--loadernone-metadatanone) and [Keyed modalities](#keyed-modalities) for details on when each default is the right choice.
|
|
179
198
|
|
|
180
199
|
### `FileRecord`
|
|
181
200
|
|
|
@@ -315,6 +334,98 @@ Files across modalities are matched by these IDs, so **the directory structure m
|
|
|
315
334
|
|
|
316
335
|
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.
|
|
317
336
|
|
|
337
|
+
## Keyed modalities
|
|
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.
|
|
340
|
+
|
|
341
|
+
### When to use it
|
|
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:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
augmented_rgb/
|
|
347
|
+
scene_000000/CS_FRONT/file_id:000000000000000025/mor_10m.png
|
|
348
|
+
scene_000000/CS_FRONT/file_id:000000000000000025/mor_20m.png
|
|
349
|
+
scene_000000/CS_FRONT/file_id:000000000000000026/mor_10m.png
|
|
350
|
+
...
|
|
351
|
+
gt_depth/
|
|
352
|
+
scene_000000/CS_FRONT/000000000000000025.png
|
|
353
|
+
scene_000000/CS_FRONT/000000000000000026.png
|
|
354
|
+
...
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
ds-crawler indexes both layouts unchanged; the augmented modality's `indexing.hierarchy.separator` must be `":"` (or any single separator) so the deepest key `"file_id:000…025"` decodes into `("file_id", "000…025")`.
|
|
358
|
+
|
|
359
|
+
### Wiring
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
from euler_loading import Modality, MultiModalDataset
|
|
363
|
+
|
|
364
|
+
dataset = MultiModalDataset(
|
|
365
|
+
modalities={
|
|
366
|
+
"rgb_aug": Modality("/data/augmented_rgb", loader=load_rgb),
|
|
367
|
+
},
|
|
368
|
+
keyed_modalities={
|
|
369
|
+
"depth": Modality("/data/gt_depth", loader=load_depth),
|
|
370
|
+
},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
sample = dataset[0]
|
|
374
|
+
# sample["rgb_aug"] – the per-aug RGB
|
|
375
|
+
# sample["depth"] – the GT depth for this file_id (single value, shared across augs)
|
|
376
|
+
# sample["full_id"] – e.g. "/scene_000000/CS_FRONT/file_id:000…025/mor_10m"
|
|
377
|
+
# sample["id"] – the augmentation's leaf id (e.g. "mor_10m")
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
`keyed_by` is optional — both `key_name` (the named-group prefix at the regular modality's deepest hierarchy key) and `modality` (the anchor regular modality) are auto-detected when unambiguous. Set them explicitly when:
|
|
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.
|
|
428
|
+
|
|
318
429
|
## DenseDepthLoader protocol
|
|
319
430
|
|
|
320
431
|
`euler_loading.DenseDepthLoader` is a `runtime_checkable` Protocol defining the loader contract for dense-depth datasets. A conforming module must expose:
|