ds_crawler 2.7.2__tar.gz → 2.9.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.
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/PKG-INFO +1 -1
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/README.md +150 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/__init__.py +5 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/artifact_builder.py +47 -8
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/artifacts.py +15 -2
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/cli.py +52 -2
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/config.py +75 -7
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/migration.py +81 -13
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/operations.py +547 -69
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/parser.py +52 -9
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/schema.py +29 -6
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/validation.py +37 -7
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/writer.py +19 -18
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/zip_utils.py +280 -20
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/pyproject.toml +1 -1
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_artifact_builder.py +44 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_inline_splits.py +74 -0
- ds_crawler-2.9.0/tests/test_metadata_scopes.py +313 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_new_metadata.py +30 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_parser.py +33 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/.github/workflows/workflow.yml +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/.gitignore +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/config_example.json +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/_dataset_contract.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/_euler_modalities.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/_version.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/handlers/__init__.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/handlers/base.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/handlers/generic.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/handlers/zip_handler.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/layout.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/path_filters.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/ds_crawler/traversal.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/example-output-rds.json +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/examples/augmented_rgb_example.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/examples/config.json +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/examples/example.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/examples/example_output.json +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/examples/run.sh +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/examples/sample_realdrivesim.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/meta/__init__.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/meta/build_meta_schema.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/meta/schema.json +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/package-lock.json +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/split_rds.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/__init__.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/conftest.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/current_helpers.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_align_datasets.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_attributes_passthrough.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_config.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_copy_dataset.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_extract_datasets.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_handlers.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_integration.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_layout.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_meta_schema.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_split_dataset.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_validation.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_writer.py +0 -0
- {ds_crawler-2.7.2 → ds_crawler-2.9.0}/tests/test_zip.py +0 -0
|
@@ -43,6 +43,31 @@ The files have distinct roles:
|
|
|
43
43
|
| `.ds_crawler/index.json` | Materialized full dataset index. No duplicated head/config metadata. |
|
|
44
44
|
| `.ds_crawler/split_<name>.json` | Named split artifact with its own contract, provenance, and filtered `index` node. |
|
|
45
45
|
|
|
46
|
+
When several logical modalities share one physical root or zip, store each
|
|
47
|
+
metadata set under a scope:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
dataset_root/
|
|
51
|
+
├── .ds_crawler/
|
|
52
|
+
│ ├── scopes.json
|
|
53
|
+
│ ├── rgb/
|
|
54
|
+
│ │ ├── dataset-head.json
|
|
55
|
+
│ │ ├── ds-crawler.json
|
|
56
|
+
│ │ └── index.json
|
|
57
|
+
│ └── camera_extrinsics/
|
|
58
|
+
│ ├── dataset-head.json
|
|
59
|
+
│ ├── ds-crawler.json
|
|
60
|
+
│ └── index.json
|
|
61
|
+
├── calib.json
|
|
62
|
+
└── ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Pass `metadata_scope="rgb"` or `metadata_scope="camera_extrinsics"` to the
|
|
66
|
+
path-based APIs to read/write that scoped artifact set. Scoped writes update
|
|
67
|
+
`.ds_crawler/scopes.json`, a discoverability manifest listing known scopes and
|
|
68
|
+
their artifact filenames. Omitting `metadata_scope` preserves the legacy
|
|
69
|
+
unscoped layout exactly.
|
|
70
|
+
|
|
46
71
|
The in-memory objects returned by `index_dataset_from_path(...)` and
|
|
47
72
|
`load_dataset_split(...)` are hydrated outputs. They include `head` and
|
|
48
73
|
`indexing` for convenience, even though the on-disk `index.json` only stores
|
|
@@ -146,6 +171,21 @@ print(output["index"].keys())
|
|
|
146
171
|
- `.ds_crawler/ds-crawler.json`
|
|
147
172
|
- `.ds_crawler/index.json`
|
|
148
173
|
|
|
174
|
+
For a shared physical root, pass a metadata scope:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
rgb = index_dataset_from_path(
|
|
178
|
+
"/data/muses",
|
|
179
|
+
save_index=True,
|
|
180
|
+
metadata_scope="rgb",
|
|
181
|
+
)
|
|
182
|
+
extrinsics = index_dataset_from_path(
|
|
183
|
+
"/data/muses",
|
|
184
|
+
save_index=True,
|
|
185
|
+
metadata_scope="camera_extrinsics",
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
149
189
|
### 3. Align multiple modalities
|
|
150
190
|
|
|
151
191
|
```python
|
|
@@ -220,6 +260,58 @@ MultiModalDataset(
|
|
|
220
260
|
A runnable end-to-end example is in
|
|
221
261
|
[`examples/augmented_rgb_example.py`](examples/augmented_rgb_example.py).
|
|
222
262
|
|
|
263
|
+
### Recipe: root-level calibration shared by every sample
|
|
264
|
+
|
|
265
|
+
Some archives keep calibration at the dataset root, for example
|
|
266
|
+
`rgb.zip/calib.json`, while regular samples live below path-based hierarchy
|
|
267
|
+
folders. Model that calibration as a hierarchical modality with no
|
|
268
|
+
`indexing.hierarchy` block. ds-crawler then stores `calib.json` directly in
|
|
269
|
+
the root `index.files` list, i.e. at hierarchy path `()`. Consumers such as
|
|
270
|
+
`euler-loading` treat root-level hierarchical files as ancestors of every
|
|
271
|
+
sample, so the calibration applies globally.
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"indexing": {
|
|
276
|
+
"id": {"regex": "^(calib)\\.json$", "join_char": "+"},
|
|
277
|
+
"files": {
|
|
278
|
+
"extensions": [".json"],
|
|
279
|
+
"path_filters": {"include_regex": ["^calib\\.json$"]}
|
|
280
|
+
},
|
|
281
|
+
"constraints": {"flat_ids_unique": true}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
This is the same hierarchy model as per-scene or per-camera calibration: the
|
|
287
|
+
only difference is that the applicable hierarchy level is the root rather than
|
|
288
|
+
`scene:<id>` or `camera:<id>`. Do not add a `hierarchy.regex` for this case;
|
|
289
|
+
there is no path segment to extract.
|
|
290
|
+
|
|
291
|
+
Use `indexing.id.override` when the matched file's natural name is not the
|
|
292
|
+
logical calibration slot. For example, a file named `calib.json` or a UUID JSON
|
|
293
|
+
can be indexed as `intrinsics`, `extrinsics`, or `calibration`:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{
|
|
297
|
+
"indexing": {
|
|
298
|
+
"id": {
|
|
299
|
+
"regex": "^(calib)\\.json$",
|
|
300
|
+
"join_char": "+",
|
|
301
|
+
"override": "intrinsics"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
The override replaces the ID extracted by `id.regex` after the regex has
|
|
308
|
+
matched. This matters for hierarchical modalities because downstream consumers
|
|
309
|
+
use the file ID as the semantic key, and repeated IDs at different ancestor
|
|
310
|
+
levels intentionally mean "the deeper file overrides the shallower one". Do not
|
|
311
|
+
set `id.override` when multiple distinct files can exist at the same hierarchy
|
|
312
|
+
level; they would all receive the same ID and ds-crawler will treat all but one
|
|
313
|
+
as duplicates.
|
|
314
|
+
|
|
223
315
|
### 4. Create named split artifacts
|
|
224
316
|
|
|
225
317
|
```python
|
|
@@ -237,6 +329,19 @@ print(train_output["split"]["name"])
|
|
|
237
329
|
print(train_output["execution"]["split"])
|
|
238
330
|
```
|
|
239
331
|
|
|
332
|
+
Scoped split artifacts use the same namespace as the index:
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
create_dataset_splits(
|
|
336
|
+
"/data/muses",
|
|
337
|
+
split_names=["train", "val"],
|
|
338
|
+
ratios=[80, 20],
|
|
339
|
+
metadata_scope="rgb",
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
train_output = load_dataset_split("/data/muses", "train", metadata_scope="rgb")
|
|
343
|
+
```
|
|
344
|
+
|
|
240
345
|
To reuse an existing partition on a sibling dataset (same qualified file
|
|
241
346
|
IDs), copy the splits over instead of recomputing them:
|
|
242
347
|
|
|
@@ -290,6 +395,26 @@ Directory and `.zip` datasets are both supported. With multiple source paths,
|
|
|
290
395
|
the split rules are applied to the intersection of hierarchy-qualified IDs so
|
|
291
396
|
the generated split artifacts stay aligned across modalities.
|
|
292
397
|
|
|
398
|
+
To split from an explicit JSON mapping of split name to full qualified file
|
|
399
|
+
IDs, use `create_mapped_dataset_splits`:
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
from ds_crawler import create_mapped_dataset_splits
|
|
403
|
+
|
|
404
|
+
create_mapped_dataset_splits(
|
|
405
|
+
"/data/foggy_rgb",
|
|
406
|
+
{
|
|
407
|
+
"fog": ["camera_0~fog~day~0001", "camera_1~fog~day~0002"],
|
|
408
|
+
"clear": ["camera_0~clear~day~0003"],
|
|
409
|
+
},
|
|
410
|
+
)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
String IDs are split by `qualified_id_separator="~"` into
|
|
414
|
+
`(*hierarchy_keys, file_id)`. If a path segment itself contains `~`, pass that
|
|
415
|
+
ID as a JSON array of path segments instead. All requested IDs are validated
|
|
416
|
+
before writing, so an invalid mapping never leaves partial split artifacts.
|
|
417
|
+
|
|
293
418
|
### 5. Write generated outputs back to disk
|
|
294
419
|
|
|
295
420
|
```python
|
|
@@ -497,6 +622,9 @@ The main public entry points are re-exported from `ds_crawler`.
|
|
|
497
622
|
|
|
498
623
|
Indexing and config:
|
|
499
624
|
|
|
625
|
+
- `build_crawler_config(...)`
|
|
626
|
+
- `build_dataset_artifacts_from_files(...)`
|
|
627
|
+
- `build_dataset_head(...)`
|
|
500
628
|
- `index_dataset(...)`
|
|
501
629
|
- `index_dataset_from_files(...)`
|
|
502
630
|
- `index_dataset_from_path(...)`
|
|
@@ -505,14 +633,22 @@ Indexing and config:
|
|
|
505
633
|
- `validate_dataset(...)`
|
|
506
634
|
- `validate_output(...)`
|
|
507
635
|
|
|
636
|
+
`index_dataset(...)`, `index_dataset_from_path(...)`, `load_dataset_config(...)`,
|
|
637
|
+
and `validate_dataset(...)` accept `metadata_scope=...` for scoped metadata.
|
|
638
|
+
`build_dataset_artifacts_from_files(...)` also accepts `metadata_scope=...` and
|
|
639
|
+
returns scoped artifact names plus `scopes.json`.
|
|
640
|
+
|
|
508
641
|
Dataset metadata:
|
|
509
642
|
|
|
510
643
|
- `get_dataset_contract(source)`
|
|
511
644
|
- `get_dataset_properties(source)`
|
|
512
645
|
- `extract_dataset_properties(mapping)`
|
|
646
|
+
- `list_metadata_scopes(path)`
|
|
513
647
|
|
|
514
648
|
`get_dataset_contract(...)` returns a `DatasetHeadContract`. Use
|
|
515
649
|
`contract.get_namespace("euler_train")` to access addon payloads.
|
|
650
|
+
`get_dataset_contract(path, metadata_scope=...)` and
|
|
651
|
+
`get_dataset_properties(path, metadata_scope=...)` read scoped heads.
|
|
516
652
|
|
|
517
653
|
Traversal and filtering:
|
|
518
654
|
|
|
@@ -532,19 +668,33 @@ Operations:
|
|
|
532
668
|
- `create_dataset_splits(...)`
|
|
533
669
|
- `create_aligned_dataset_splits(...)`
|
|
534
670
|
- `create_hierarchy_dataset_splits(...)`
|
|
671
|
+
- `create_mapped_dataset_splits(...)`
|
|
535
672
|
- `list_dataset_splits(...)`
|
|
536
673
|
- `load_dataset_split(...)`
|
|
537
674
|
|
|
675
|
+
Split, copy, align, and extraction operations accept `metadata_scope=...`.
|
|
676
|
+
`copy_dataset(...)`, `copy_dataset_splits(...)`, and `split_dataset(...)` also
|
|
677
|
+
accept source/target scope overrides when the two sides use different scope
|
|
678
|
+
names. `split_datasets(...)` and `extract_datasets(...)` accept per-source or
|
|
679
|
+
per-config scope lists for multi-dataset workflows.
|
|
680
|
+
|
|
538
681
|
Writers:
|
|
539
682
|
|
|
540
683
|
- `DatasetWriter(...)`
|
|
541
684
|
- `ZipDatasetWriter(...)`
|
|
542
685
|
|
|
686
|
+
Both writers accept `metadata_scope=...` and write scoped artifacts plus the
|
|
687
|
+
`scopes.json` manifest.
|
|
688
|
+
|
|
543
689
|
Migration helpers:
|
|
544
690
|
|
|
545
691
|
- `migrate_dataset_metadata(...)`
|
|
546
692
|
- `migrate_dataset_zip(...)`
|
|
547
693
|
- `migrate_dataset_zips_in_folder(...)`
|
|
694
|
+
- `migrate_inline_splits(...)`
|
|
695
|
+
|
|
696
|
+
Migration helpers and `ds-crawler migrate` also accept `metadata_scope=...`
|
|
697
|
+
or `--metadata-scope` for scoped legacy metadata conversion.
|
|
548
698
|
|
|
549
699
|
## ZIP support
|
|
550
700
|
|
|
@@ -32,6 +32,7 @@ from .operations import (
|
|
|
32
32
|
create_aligned_dataset_splits,
|
|
33
33
|
create_dataset_splits,
|
|
34
34
|
create_hierarchy_dataset_splits,
|
|
35
|
+
create_mapped_dataset_splits,
|
|
35
36
|
extract_datasets,
|
|
36
37
|
HierarchySplitClause,
|
|
37
38
|
HierarchySplitRule,
|
|
@@ -46,6 +47,7 @@ from .migration import (
|
|
|
46
47
|
migrate_dataset_zips_in_folder,
|
|
47
48
|
)
|
|
48
49
|
from .writer import DatasetWriter, ZipDatasetWriter
|
|
50
|
+
from .zip_utils import list_metadata_scopes, validate_metadata_scope
|
|
49
51
|
from .layout import (
|
|
50
52
|
EULER_LAYOUT_ADDON,
|
|
51
53
|
EULER_LAYOUT_VERSION,
|
|
@@ -74,6 +76,7 @@ __all__ = [
|
|
|
74
76
|
"create_aligned_dataset_splits",
|
|
75
77
|
"create_dataset_splits",
|
|
76
78
|
"create_hierarchy_dataset_splits",
|
|
79
|
+
"create_mapped_dataset_splits",
|
|
77
80
|
"extract_datasets",
|
|
78
81
|
"HierarchySplitClause",
|
|
79
82
|
"HierarchySplitRule",
|
|
@@ -86,6 +89,7 @@ __all__ = [
|
|
|
86
89
|
"index_dataset_from_files",
|
|
87
90
|
"index_dataset_from_path",
|
|
88
91
|
"list_dataset_splits",
|
|
92
|
+
"list_metadata_scopes",
|
|
89
93
|
"load_dataset_config",
|
|
90
94
|
"load_dataset_split",
|
|
91
95
|
"migrate_dataset_metadata",
|
|
@@ -96,6 +100,7 @@ __all__ = [
|
|
|
96
100
|
"split_qualified_ids",
|
|
97
101
|
"validate_crawler_config",
|
|
98
102
|
"validate_dataset",
|
|
103
|
+
"validate_metadata_scope",
|
|
99
104
|
"validate_layout_addon",
|
|
100
105
|
"validate_output",
|
|
101
106
|
"extract_dataset_properties",
|
|
@@ -15,7 +15,14 @@ from .artifacts import build_index_artifact
|
|
|
15
15
|
from .config import CONFIG_FILENAME, CRAWLER_CONFIG_KIND, CRAWLER_CONFIG_VERSION, DatasetConfig
|
|
16
16
|
from .parser import index_dataset_from_files
|
|
17
17
|
from .traversal import get_files
|
|
18
|
-
from .zip_utils import
|
|
18
|
+
from .zip_utils import (
|
|
19
|
+
DATASET_HEAD_FILENAME,
|
|
20
|
+
OUTPUT_FILENAME,
|
|
21
|
+
SCOPES_FILENAME,
|
|
22
|
+
_scopes_manifest_with,
|
|
23
|
+
get_metadata_filename,
|
|
24
|
+
validate_metadata_scope,
|
|
25
|
+
)
|
|
19
26
|
|
|
20
27
|
|
|
21
28
|
def _require_mapping(value: Any, context: str) -> dict[str, Any]:
|
|
@@ -125,6 +132,7 @@ def build_crawler_config(
|
|
|
125
132
|
source_path: str = ".",
|
|
126
133
|
head_file: str = DATASET_HEAD_FILENAME,
|
|
127
134
|
prebuilt_index_file: str | None = None,
|
|
135
|
+
metadata_scope: str | None = None,
|
|
128
136
|
) -> dict[str, Any]:
|
|
129
137
|
"""Build and validate a canonical ``ds-crawler.json`` mapping."""
|
|
130
138
|
indexing = _require_mapping(indexing, "indexing")
|
|
@@ -150,6 +158,8 @@ def build_crawler_config(
|
|
|
150
158
|
}
|
|
151
159
|
if prebuilt_index_file is not None:
|
|
152
160
|
config["source"]["prebuilt_index_file"] = prebuilt_index_file
|
|
161
|
+
if metadata_scope is not None:
|
|
162
|
+
config["metadata_scope"] = validate_metadata_scope(metadata_scope)
|
|
153
163
|
|
|
154
164
|
runtime_config = dict(config)
|
|
155
165
|
runtime_config["head"] = deepcopy(head)
|
|
@@ -172,10 +182,20 @@ def build_dataset_artifacts_from_files(
|
|
|
172
182
|
strict: bool = False,
|
|
173
183
|
sample: int | None = None,
|
|
174
184
|
match_index: dict[str, Any] | None = None,
|
|
185
|
+
metadata_scope: str | None = None,
|
|
175
186
|
) -> dict[str, Any]:
|
|
176
187
|
"""Build canonical dataset artifacts from a parameter set and file list."""
|
|
188
|
+
normalized_scope = (
|
|
189
|
+
validate_metadata_scope(metadata_scope)
|
|
190
|
+
if metadata_scope is not None
|
|
191
|
+
else None
|
|
192
|
+
)
|
|
177
193
|
head = build_dataset_head(dataset=dataset, modality=modality, addons=addons)
|
|
178
|
-
config = build_crawler_config(
|
|
194
|
+
config = build_crawler_config(
|
|
195
|
+
head=head,
|
|
196
|
+
indexing=indexing,
|
|
197
|
+
metadata_scope=normalized_scope,
|
|
198
|
+
)
|
|
179
199
|
|
|
180
200
|
runtime_config = dict(config)
|
|
181
201
|
runtime_config["head"] = deepcopy(head)
|
|
@@ -193,14 +213,33 @@ def build_dataset_artifacts_from_files(
|
|
|
193
213
|
CONFIG_FILENAME: config,
|
|
194
214
|
OUTPUT_FILENAME: build_index_artifact(output),
|
|
195
215
|
}
|
|
216
|
+
if normalized_scope is not None:
|
|
217
|
+
unscoped_artifact_names = list(artifacts)
|
|
218
|
+
artifacts = {
|
|
219
|
+
get_metadata_filename(
|
|
220
|
+
filename,
|
|
221
|
+
metadata_scope=normalized_scope,
|
|
222
|
+
): payload
|
|
223
|
+
for filename, payload in artifacts.items()
|
|
224
|
+
}
|
|
225
|
+
artifacts[SCOPES_FILENAME] = _scopes_manifest_with(
|
|
226
|
+
None,
|
|
227
|
+
metadata_scope=normalized_scope,
|
|
228
|
+
filenames=unscoped_artifact_names,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
summary = {
|
|
232
|
+
"dataset_id": output["head"]["dataset"]["id"],
|
|
233
|
+
"dataset_name": output["head"]["dataset"]["name"],
|
|
234
|
+
"modality_key": output["head"]["modality"]["key"],
|
|
235
|
+
"file_count": len(get_files(output)),
|
|
236
|
+
}
|
|
237
|
+
if normalized_scope is not None:
|
|
238
|
+
summary["metadata_scope"] = normalized_scope
|
|
239
|
+
|
|
196
240
|
return {
|
|
197
241
|
"artifacts": artifacts,
|
|
198
|
-
"summary":
|
|
199
|
-
"dataset_id": output["head"]["dataset"]["id"],
|
|
200
|
-
"dataset_name": output["head"]["dataset"]["name"],
|
|
201
|
-
"modality_key": output["head"]["modality"]["key"],
|
|
202
|
-
"file_count": len(get_files(output)),
|
|
203
|
-
},
|
|
242
|
+
"summary": summary,
|
|
204
243
|
}
|
|
205
244
|
|
|
206
245
|
|
|
@@ -173,13 +173,21 @@ def load_saved_output(
|
|
|
173
173
|
dataset_path: str | Path,
|
|
174
174
|
*,
|
|
175
175
|
filename: str = OUTPUT_FILENAME,
|
|
176
|
+
metadata_scope: str | None = None,
|
|
176
177
|
) -> dict[str, Any] | None:
|
|
177
178
|
"""Load and hydrate a persisted dataset index artifact from disk."""
|
|
178
179
|
dataset_root = Path(dataset_path)
|
|
179
|
-
index_artifact = read_metadata_json(
|
|
180
|
+
index_artifact = read_metadata_json(
|
|
181
|
+
dataset_root,
|
|
182
|
+
filename,
|
|
183
|
+
metadata_scope=metadata_scope,
|
|
184
|
+
)
|
|
180
185
|
if index_artifact is None:
|
|
181
186
|
return None
|
|
182
|
-
ds_config = load_dataset_config(
|
|
187
|
+
ds_config = load_dataset_config(
|
|
188
|
+
{"path": str(dataset_root)},
|
|
189
|
+
metadata_scope=metadata_scope,
|
|
190
|
+
)
|
|
183
191
|
return hydrate_index_artifact(index_artifact, ds_config)
|
|
184
192
|
|
|
185
193
|
|
|
@@ -196,6 +204,7 @@ def load_prebuilt_output(ds_config: DatasetConfig) -> dict[str, Any]:
|
|
|
196
204
|
index_artifact = read_metadata_json(
|
|
197
205
|
Path(ds_config.dataset_root),
|
|
198
206
|
prebuilt_path.name,
|
|
207
|
+
metadata_scope=ds_config.metadata_scope,
|
|
199
208
|
)
|
|
200
209
|
if index_artifact is None:
|
|
201
210
|
raise FileNotFoundError(
|
|
@@ -210,6 +219,7 @@ def save_output_artifacts(
|
|
|
210
219
|
output: dict[str, Any],
|
|
211
220
|
*,
|
|
212
221
|
filename: str = OUTPUT_FILENAME,
|
|
222
|
+
metadata_scope: str | None = None,
|
|
213
223
|
) -> Path:
|
|
214
224
|
"""Persist head/config/index artifacts for a dataset output."""
|
|
215
225
|
dataset_root = Path(dataset_path)
|
|
@@ -227,8 +237,11 @@ def save_output_artifacts(
|
|
|
227
237
|
CONFIG_FILENAME: build_crawler_config_for_output(output),
|
|
228
238
|
filename: build_index_artifact(output),
|
|
229
239
|
},
|
|
240
|
+
metadata_scope=metadata_scope,
|
|
230
241
|
)
|
|
231
242
|
|
|
232
243
|
if is_zip_path(dataset_root):
|
|
233
244
|
return dataset_root
|
|
245
|
+
if metadata_scope is not None:
|
|
246
|
+
return dataset_root / METADATA_DIR / metadata_scope / filename
|
|
234
247
|
return dataset_root / METADATA_DIR / filename
|
|
@@ -85,13 +85,27 @@ def _run_index(argv: list[str]) -> int:
|
|
|
85
85
|
metavar="PATH",
|
|
86
86
|
help="Path to an index.json whose file IDs are used as a filter.",
|
|
87
87
|
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--metadata-scope",
|
|
90
|
+
type=str,
|
|
91
|
+
default=None,
|
|
92
|
+
metavar="SCOPE",
|
|
93
|
+
help=(
|
|
94
|
+
"Read/write ds-crawler artifacts below .ds_crawler/SCOPE/ "
|
|
95
|
+
"instead of the root .ds_crawler directory."
|
|
96
|
+
),
|
|
97
|
+
)
|
|
88
98
|
|
|
89
99
|
args = parser.parse_args(argv)
|
|
90
100
|
|
|
91
101
|
setup_logging(args.verbose)
|
|
92
102
|
|
|
93
103
|
try:
|
|
94
|
-
config = Config.from_file(
|
|
104
|
+
config = Config.from_file(
|
|
105
|
+
args.config,
|
|
106
|
+
workdir=args.workdir,
|
|
107
|
+
metadata_scope=args.metadata_scope,
|
|
108
|
+
)
|
|
95
109
|
except FileNotFoundError as e:
|
|
96
110
|
print(f"Error: {e}", file=sys.stderr)
|
|
97
111
|
return 1
|
|
@@ -122,7 +136,9 @@ def _run_index(argv: list[str]) -> int:
|
|
|
122
136
|
parser_instance.write_output(args.output)
|
|
123
137
|
logger.info(f"Output written to: {args.output}")
|
|
124
138
|
else:
|
|
125
|
-
output_paths = parser_instance.write_outputs_per_dataset(
|
|
139
|
+
output_paths = parser_instance.write_outputs_per_dataset(
|
|
140
|
+
metadata_scope=args.metadata_scope,
|
|
141
|
+
)
|
|
126
142
|
for path in output_paths:
|
|
127
143
|
logger.info(f"Output written to: {path}")
|
|
128
144
|
except Exception as e:
|
|
@@ -165,6 +181,13 @@ def _run_migrate(argv: list[str]) -> int:
|
|
|
165
181
|
action="store_true",
|
|
166
182
|
help="When used with --scan-zips, only scan the provided directory itself.",
|
|
167
183
|
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--metadata-scope",
|
|
186
|
+
help=(
|
|
187
|
+
"Read/write legacy migration artifacts below "
|
|
188
|
+
".ds_crawler/SCOPE/."
|
|
189
|
+
),
|
|
190
|
+
)
|
|
168
191
|
parser.add_argument(
|
|
169
192
|
"-v",
|
|
170
193
|
"--verbose",
|
|
@@ -188,6 +211,7 @@ def _run_migrate(argv: list[str]) -> int:
|
|
|
188
211
|
result = migrate_inline_splits(
|
|
189
212
|
path,
|
|
190
213
|
logger=logger,
|
|
214
|
+
metadata_scope=args.metadata_scope,
|
|
191
215
|
)
|
|
192
216
|
logger.info(
|
|
193
217
|
"Migrated inline splits %s (splits=%d)",
|
|
@@ -202,6 +226,7 @@ def _run_migrate(argv: list[str]) -> int:
|
|
|
202
226
|
recursive=not args.top_level_only,
|
|
203
227
|
write_output=not args.no_index,
|
|
204
228
|
logger=logger,
|
|
229
|
+
metadata_scope=args.metadata_scope,
|
|
205
230
|
)
|
|
206
231
|
if result["failed"]:
|
|
207
232
|
failed = True
|
|
@@ -222,6 +247,7 @@ def _run_migrate(argv: list[str]) -> int:
|
|
|
222
247
|
path,
|
|
223
248
|
write_output=not args.no_index,
|
|
224
249
|
logger=logger,
|
|
250
|
+
metadata_scope=args.metadata_scope,
|
|
225
251
|
)
|
|
226
252
|
except Exception as exc:
|
|
227
253
|
failed = True
|
|
@@ -274,6 +300,27 @@ def _run_copy_splits(argv: list[str]) -> int:
|
|
|
274
300
|
action="store_true",
|
|
275
301
|
help="Replace existing splits on the target if they share a name.",
|
|
276
302
|
)
|
|
303
|
+
parser.add_argument(
|
|
304
|
+
"--metadata-scope",
|
|
305
|
+
type=str,
|
|
306
|
+
default=None,
|
|
307
|
+
metavar="SCOPE",
|
|
308
|
+
help="Use the same scoped .ds_crawler/SCOPE metadata for source and target.",
|
|
309
|
+
)
|
|
310
|
+
parser.add_argument(
|
|
311
|
+
"--source-metadata-scope",
|
|
312
|
+
type=str,
|
|
313
|
+
default=None,
|
|
314
|
+
metavar="SCOPE",
|
|
315
|
+
help="Read source splits from .ds_crawler/SCOPE/.",
|
|
316
|
+
)
|
|
317
|
+
parser.add_argument(
|
|
318
|
+
"--target-metadata-scope",
|
|
319
|
+
type=str,
|
|
320
|
+
default=None,
|
|
321
|
+
metavar="SCOPE",
|
|
322
|
+
help="Write target splits to .ds_crawler/SCOPE/.",
|
|
323
|
+
)
|
|
277
324
|
parser.add_argument(
|
|
278
325
|
"-v",
|
|
279
326
|
"--verbose",
|
|
@@ -291,6 +338,9 @@ def _run_copy_splits(argv: list[str]) -> int:
|
|
|
291
338
|
args.target,
|
|
292
339
|
split_names=args.splits,
|
|
293
340
|
override=args.override,
|
|
341
|
+
metadata_scope=args.metadata_scope,
|
|
342
|
+
source_metadata_scope=args.source_metadata_scope,
|
|
343
|
+
target_metadata_scope=args.target_metadata_scope,
|
|
294
344
|
)
|
|
295
345
|
except (FileNotFoundError, ValueError) as exc:
|
|
296
346
|
print(f"Error: {exc}", file=sys.stderr)
|