euler-loading 2.18.0__tar.gz → 2.19.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.18.0 → euler_loading-2.19.0}/PKG-INFO +1 -1
- {euler_loading-2.18.0 → euler_loading-2.19.0}/README.md +18 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/_writing.py +20 -4
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/dataset.py +11 -1
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/cpu/generic.py +62 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/gpu/generic.py +62 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/package-lock.json +1 -1
- {euler_loading-2.18.0 → euler_loading-2.19.0}/pyproject.toml +1 -1
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/test_loaders.py +41 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/test_writing.py +65 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/.github/workflows/workflow.yml +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/.gitignore +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/docs/loader-attributes.md +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/__init__.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/_dataset_contract.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/_ds_crawler_utils.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/_metadata.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/_resolution.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/indexing.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/__init__.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/_annotations.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/_princeton_dense.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/_writer_utils.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/contracts.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/cpu/__init__.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/cpu/muses.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/cpu/princeton_dense.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/generate/__init__.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/generate/__main__.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/generate/loaders.json +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/generic.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/gpu/__init__.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/gpu/muses.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/gpu/princeton_dense.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/muses.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/princeton_dense.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/real_drive_sim.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/loaders/vkitti2.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/euler_loading/preprocessing.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/example.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/gen_loaders.sh +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/sample_rds.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/__init__.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/conftest.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/example_rds_calib.json +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/test_dataset.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/test_id_schema.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/test_indexing.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/test_preprocessing.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/tests/test_real_dataset.py +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/vkitti_cpu_example_output.json +0 -0
- {euler_loading-2.18.0 → euler_loading-2.19.0}/vkitti_gpu_example_output.json +0 -0
|
@@ -190,6 +190,24 @@ when doing so is unambiguous:
|
|
|
190
190
|
|
|
191
191
|
This is an additive layout. Existing roots with `.ds_crawler/index.json` continue to load unchanged, and a scoped modality falls back to that legacy location when the scoped artifacts are not present.
|
|
192
192
|
|
|
193
|
+
Output writers can create the same layout natively. Pass a scope when creating
|
|
194
|
+
each modality writer; ds-crawler writes the scoped artifacts and maintains
|
|
195
|
+
`.ds_crawler/scopes.json`:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
rgb_writer = dataset.create_output_writer(
|
|
199
|
+
"rgb", output_root, metadata_scope="rgb"
|
|
200
|
+
)
|
|
201
|
+
depth_writer = dataset.create_output_writer(
|
|
202
|
+
"sparse_depth", output_root, metadata_scope="sparse_depth"
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The standalone `create_dataset_writer_from_index(...)` helper accepts the same
|
|
207
|
+
`metadata_scope` argument. For ZIP output, one `ZipDatasetWriter` owns and
|
|
208
|
+
finalizes one archive, so do not open multiple ZIP writers concurrently for the
|
|
209
|
+
same path.
|
|
210
|
+
|
|
193
211
|
### `MultiModalDataset.modality_paths()`
|
|
194
212
|
|
|
195
213
|
Returns a dict mapping each regular modality name to `{"path": ..., "origin_path": ...}` and includes `split` and `metadata_scope` when configured.
|
|
@@ -28,6 +28,8 @@ OutputDestination = str | os.PathLike[str] | DatasetWriter | ZipDatasetWriter
|
|
|
28
28
|
|
|
29
29
|
def _wrap_writer_save_index(
|
|
30
30
|
writer: DatasetWriter | ZipDatasetWriter,
|
|
31
|
+
*,
|
|
32
|
+
metadata_scope: str | None = None,
|
|
31
33
|
) -> DatasetWriter | ZipDatasetWriter:
|
|
32
34
|
original_save_index = writer.save_index
|
|
33
35
|
|
|
@@ -72,7 +74,12 @@ def _wrap_writer_save_index(
|
|
|
72
74
|
path = original_save_index(filename=filename)
|
|
73
75
|
|
|
74
76
|
if isinstance(writer, ZipDatasetWriter):
|
|
75
|
-
|
|
77
|
+
metadata_prefix = (
|
|
78
|
+
f".ds_crawler/{metadata_scope}"
|
|
79
|
+
if metadata_scope is not None
|
|
80
|
+
else ".ds_crawler"
|
|
81
|
+
)
|
|
82
|
+
entry_name = f"{metadata_prefix}/{filename}"
|
|
76
83
|
archive_path = Path(writer.root)
|
|
77
84
|
with zipfile.ZipFile(archive_path, "r") as source_zip:
|
|
78
85
|
output_index = json.loads(source_zip.read(entry_name).decode("utf-8"))
|
|
@@ -113,8 +120,15 @@ def create_dataset_writer_from_index(
|
|
|
113
120
|
index_output: Mapping[str, Any],
|
|
114
121
|
root: str | os.PathLike[str],
|
|
115
122
|
zip: bool = False,
|
|
123
|
+
metadata_scope: str | None = None,
|
|
116
124
|
) -> DatasetWriter | ZipDatasetWriter:
|
|
117
|
-
"""Create a ds-crawler writer that mirrors an existing index's metadata.
|
|
125
|
+
"""Create a ds-crawler writer that mirrors an existing index's metadata.
|
|
126
|
+
|
|
127
|
+
``metadata_scope`` stores the generated artifacts below
|
|
128
|
+
``.ds_crawler/<metadata_scope>/`` and lets ds-crawler maintain the shared
|
|
129
|
+
``.ds_crawler/scopes.json`` manifest. This is useful when several logical
|
|
130
|
+
modalities share one physical output root or archive.
|
|
131
|
+
"""
|
|
118
132
|
indexing = index_output.get("indexing")
|
|
119
133
|
hierarchy = indexing.get("hierarchy") if isinstance(indexing, Mapping) else None
|
|
120
134
|
id_cfg = indexing.get("id") if isinstance(indexing, Mapping) else None
|
|
@@ -133,8 +147,9 @@ def create_dataset_writer_from_index(
|
|
|
133
147
|
root,
|
|
134
148
|
head=contract.to_mapping(),
|
|
135
149
|
separator=separator,
|
|
150
|
+
metadata_scope=metadata_scope,
|
|
136
151
|
)
|
|
137
|
-
return _wrap_writer_save_index(writer)
|
|
152
|
+
return _wrap_writer_save_index(writer, metadata_scope=metadata_scope)
|
|
138
153
|
|
|
139
154
|
name = as_non_empty_str(index_output.get("name"))
|
|
140
155
|
type_name = as_non_empty_str(index_output.get("type"))
|
|
@@ -160,9 +175,10 @@ def create_dataset_writer_from_index(
|
|
|
160
175
|
type=type_name,
|
|
161
176
|
euler_train=dict(euler_train) if isinstance(euler_train, Mapping) else None,
|
|
162
177
|
separator=separator,
|
|
178
|
+
metadata_scope=metadata_scope,
|
|
163
179
|
**properties,
|
|
164
180
|
)
|
|
165
|
-
return _wrap_writer_save_index(writer)
|
|
181
|
+
return _wrap_writer_save_index(writer, metadata_scope=metadata_scope)
|
|
166
182
|
|
|
167
183
|
|
|
168
184
|
def _resolve_output_destination(
|
|
@@ -969,13 +969,23 @@ class MultiModalDataset(_BaseDataset):
|
|
|
969
969
|
root: str | os.PathLike[str],
|
|
970
970
|
*,
|
|
971
971
|
zip: bool = False,
|
|
972
|
+
metadata_scope: str | None = None,
|
|
972
973
|
) -> DatasetWriter | ZipDatasetWriter:
|
|
973
|
-
"""Create a ds-crawler writer preconfigured from a modality's index.
|
|
974
|
+
"""Create a ds-crawler writer preconfigured from a modality's index.
|
|
975
|
+
|
|
976
|
+
Args:
|
|
977
|
+
modality_name: Modality whose index metadata should be mirrored.
|
|
978
|
+
root: Output directory or, when ``zip=True``, output archive.
|
|
979
|
+
zip: Create a :class:`ds_crawler.ZipDatasetWriter`.
|
|
980
|
+
metadata_scope: Optional namespace below ``.ds_crawler``. Scoped
|
|
981
|
+
writers also update ``.ds_crawler/scopes.json``.
|
|
982
|
+
"""
|
|
974
983
|
index_output = self.get_modality_index(modality_name)
|
|
975
984
|
return create_dataset_writer_from_index(
|
|
976
985
|
index_output=index_output,
|
|
977
986
|
root=root,
|
|
978
987
|
zip=zip,
|
|
988
|
+
metadata_scope=metadata_scope,
|
|
979
989
|
)
|
|
980
990
|
|
|
981
991
|
def get_writer(self, modality_name: str) -> Callable[..., Any]:
|
|
@@ -11,6 +11,8 @@ Supported file extensions:
|
|
|
11
11
|
Return types
|
|
12
12
|
------------
|
|
13
13
|
- **map_2d** / **scattering_coefficient** -- ``np.ndarray`` of shape ``(H, W)`` float32.
|
|
14
|
+
- **semantic_segmentation** / **instance_segmentation** -- integer
|
|
15
|
+
``np.ndarray`` of shape ``(H, W)``.
|
|
14
16
|
- **map_3d** / **atmospheric_light** -- ``np.ndarray`` of shape ``(H, W, C)`` float32.
|
|
15
17
|
- **points_3d** -- ``np.ndarray`` of shape ``(3, H, W)`` float32.
|
|
16
18
|
- **spherical_map** -- ``np.ndarray`` of shape ``(H, W, C)`` float32.
|
|
@@ -71,6 +73,20 @@ def _load_numpy(path: Union[str, BinaryIO]) -> np.ndarray:
|
|
|
71
73
|
return arr.astype(np.float32)
|
|
72
74
|
|
|
73
75
|
|
|
76
|
+
def _load_label_map(path: Union[str, BinaryIO], dtype: np.dtype) -> np.ndarray:
|
|
77
|
+
"""Load a standardized HW integer label map without converting to float."""
|
|
78
|
+
ext = os.path.splitext(_get_name(path))[1].lower()
|
|
79
|
+
if ext == _NPZ_EXTENSION:
|
|
80
|
+
with np.load(path) as npz:
|
|
81
|
+
arr = next(iter(npz.values()))
|
|
82
|
+
else:
|
|
83
|
+
arr = np.load(path)
|
|
84
|
+
arr = np.asarray(arr)
|
|
85
|
+
if arr.ndim != 2:
|
|
86
|
+
raise ValueError(f"segmentation map must have shape (H, W), got {arr.shape}")
|
|
87
|
+
return arr.astype(dtype, copy=False)
|
|
88
|
+
|
|
89
|
+
|
|
74
90
|
def _write_numpy(path: Union[str, BinaryIO], value: Any) -> None:
|
|
75
91
|
"""Write an array to ``.npy`` or ``.npz`` based on extension."""
|
|
76
92
|
ensure_parent(path)
|
|
@@ -116,6 +132,28 @@ def map_2d(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, at
|
|
|
116
132
|
return _load_numpy(path)
|
|
117
133
|
|
|
118
134
|
|
|
135
|
+
@modality_meta(
|
|
136
|
+
modality_type="semantic_segmentation",
|
|
137
|
+
dtype="uint8",
|
|
138
|
+
shape="HW",
|
|
139
|
+
file_formats=[".npy", ".npz"],
|
|
140
|
+
)
|
|
141
|
+
def semantic_segmentation(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, attributes: dict[str, Any] | None = None) -> np.ndarray:
|
|
142
|
+
"""Load an HW uint8 class-id map; 255 is the standardized void id."""
|
|
143
|
+
return _load_label_map(path, np.dtype(np.uint8))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@modality_meta(
|
|
147
|
+
modality_type="instance_segmentation",
|
|
148
|
+
dtype="uint16",
|
|
149
|
+
shape="HW",
|
|
150
|
+
file_formats=[".npy", ".npz"],
|
|
151
|
+
)
|
|
152
|
+
def instance_segmentation(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, attributes: dict[str, Any] | None = None) -> np.ndarray:
|
|
153
|
+
"""Load an HW uint16 instance-id map; 0 denotes stuff or void."""
|
|
154
|
+
return _load_label_map(path, np.dtype(np.uint16))
|
|
155
|
+
|
|
156
|
+
|
|
119
157
|
@modality_meta(
|
|
120
158
|
modality_type="map_3d",
|
|
121
159
|
dtype="float32",
|
|
@@ -244,6 +282,30 @@ def write_map_2d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] |
|
|
|
244
282
|
_write_numpy(path, arr)
|
|
245
283
|
|
|
246
284
|
|
|
285
|
+
def _write_segmentation(path: Union[str, BinaryIO], value: Any, dtype: np.dtype, name: str) -> None:
|
|
286
|
+
arr = to_hw(value, name=name).astype(dtype, copy=False)
|
|
287
|
+
ensure_parent(path)
|
|
288
|
+
ext = os.path.splitext(_get_name(path))[1].lower()
|
|
289
|
+
if ext == _NPY_EXTENSION:
|
|
290
|
+
np.save(path, arr)
|
|
291
|
+
elif ext == _NPZ_EXTENSION:
|
|
292
|
+
np.savez_compressed(path, data=arr)
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError(f"Unsupported output extension: {ext}")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@mark_stream_supported
|
|
298
|
+
def write_semantic_segmentation(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
299
|
+
"""Write the standardized HW uint8 semantic map (255 means void)."""
|
|
300
|
+
_write_segmentation(path, value, np.dtype(np.uint8), "semantic_segmentation")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@mark_stream_supported
|
|
304
|
+
def write_instance_segmentation(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
305
|
+
"""Write the standardized HW uint16 instance map (0 means stuff/void)."""
|
|
306
|
+
_write_segmentation(path, value, np.dtype(np.uint16), "instance_segmentation")
|
|
307
|
+
|
|
308
|
+
|
|
247
309
|
@mark_stream_supported
|
|
248
310
|
def write_map_3d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
249
311
|
"""Write a 3D map to NumPy formats based on extension.
|
|
@@ -11,6 +11,8 @@ Supported file extensions:
|
|
|
11
11
|
Return types
|
|
12
12
|
------------
|
|
13
13
|
- **map_2d** / **scattering_coefficient** -- ``torch.FloatTensor`` of shape ``(H, W)``.
|
|
14
|
+
- **semantic_segmentation** / **instance_segmentation** -- integer
|
|
15
|
+
``torch.Tensor`` of shape ``(H, W)``.
|
|
14
16
|
- **map_3d** / **atmospheric_light** -- ``torch.FloatTensor`` of shape ``(C, H, W)``.
|
|
15
17
|
- **points_3d** -- ``torch.FloatTensor`` of shape ``(3, H, W)``.
|
|
16
18
|
- **spherical_map** -- ``torch.FloatTensor`` of shape ``(C, H, W)``.
|
|
@@ -72,6 +74,20 @@ def _load_numpy(path: Union[str, BinaryIO]) -> np.ndarray:
|
|
|
72
74
|
return arr.astype(np.float32)
|
|
73
75
|
|
|
74
76
|
|
|
77
|
+
def _load_label_map(path: Union[str, BinaryIO], dtype: np.dtype) -> np.ndarray:
|
|
78
|
+
"""Load a standardized HW integer label map without converting to float."""
|
|
79
|
+
ext = os.path.splitext(_get_name(path))[1].lower()
|
|
80
|
+
if ext == _NPZ_EXTENSION:
|
|
81
|
+
with np.load(path) as npz:
|
|
82
|
+
arr = next(iter(npz.values()))
|
|
83
|
+
else:
|
|
84
|
+
arr = np.load(path)
|
|
85
|
+
arr = np.asarray(arr)
|
|
86
|
+
if arr.ndim != 2:
|
|
87
|
+
raise ValueError(f"segmentation map must have shape (H, W), got {arr.shape}")
|
|
88
|
+
return arr.astype(dtype, copy=False)
|
|
89
|
+
|
|
90
|
+
|
|
75
91
|
def _write_numpy(path: Union[str, BinaryIO], value: Any) -> None:
|
|
76
92
|
"""Write an array to ``.npy`` or ``.npz`` based on extension."""
|
|
77
93
|
ensure_parent(path)
|
|
@@ -118,6 +134,28 @@ def map_2d(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, at
|
|
|
118
134
|
return torch.from_numpy(arr).contiguous()
|
|
119
135
|
|
|
120
136
|
|
|
137
|
+
@modality_meta(
|
|
138
|
+
modality_type="semantic_segmentation",
|
|
139
|
+
dtype="uint8",
|
|
140
|
+
shape="HW",
|
|
141
|
+
file_formats=[".npy", ".npz"],
|
|
142
|
+
)
|
|
143
|
+
def semantic_segmentation(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, attributes: dict[str, Any] | None = None) -> torch.Tensor:
|
|
144
|
+
"""Load an HW uint8 class-id map; 255 is the standardized void id."""
|
|
145
|
+
return torch.from_numpy(_load_label_map(path, np.dtype(np.uint8))).contiguous()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@modality_meta(
|
|
149
|
+
modality_type="instance_segmentation",
|
|
150
|
+
dtype="uint16",
|
|
151
|
+
shape="HW",
|
|
152
|
+
file_formats=[".npy", ".npz"],
|
|
153
|
+
)
|
|
154
|
+
def instance_segmentation(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, attributes: dict[str, Any] | None = None) -> torch.Tensor:
|
|
155
|
+
"""Load an HW uint16 instance-id map; 0 denotes stuff or void."""
|
|
156
|
+
return torch.from_numpy(_load_label_map(path, np.dtype(np.uint16))).contiguous()
|
|
157
|
+
|
|
158
|
+
|
|
121
159
|
@modality_meta(
|
|
122
160
|
modality_type="map_3d",
|
|
123
161
|
dtype="float32",
|
|
@@ -244,6 +282,30 @@ def write_map_2d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] |
|
|
|
244
282
|
_write_numpy(path, arr)
|
|
245
283
|
|
|
246
284
|
|
|
285
|
+
def _write_segmentation(path: Union[str, BinaryIO], value: Any, dtype: np.dtype, name: str) -> None:
|
|
286
|
+
arr = to_hw(value, name=name).astype(dtype, copy=False)
|
|
287
|
+
ensure_parent(path)
|
|
288
|
+
ext = os.path.splitext(_get_name(path))[1].lower()
|
|
289
|
+
if ext == _NPY_EXTENSION:
|
|
290
|
+
np.save(path, arr)
|
|
291
|
+
elif ext == _NPZ_EXTENSION:
|
|
292
|
+
np.savez_compressed(path, data=arr)
|
|
293
|
+
else:
|
|
294
|
+
raise ValueError(f"Unsupported output extension: {ext}")
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@mark_stream_supported
|
|
298
|
+
def write_semantic_segmentation(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
299
|
+
"""Write the standardized HW uint8 semantic map (255 means void)."""
|
|
300
|
+
_write_segmentation(path, value, np.dtype(np.uint8), "semantic_segmentation")
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@mark_stream_supported
|
|
304
|
+
def write_instance_segmentation(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
305
|
+
"""Write the standardized HW uint16 instance map (0 means stuff/void)."""
|
|
306
|
+
_write_segmentation(path, value, np.dtype(np.uint16), "instance_segmentation")
|
|
307
|
+
|
|
308
|
+
|
|
247
309
|
@mark_stream_supported
|
|
248
310
|
def write_map_3d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
249
311
|
"""Write a 3D map to NumPy formats based on extension.
|
|
@@ -1179,6 +1179,8 @@ class TestRDSWriters:
|
|
|
1179
1179
|
GENERIC_LOADER_NAMES = [
|
|
1180
1180
|
"map_2d",
|
|
1181
1181
|
"map_3d",
|
|
1182
|
+
"semantic_segmentation",
|
|
1183
|
+
"instance_segmentation",
|
|
1182
1184
|
"points_3d",
|
|
1183
1185
|
"scattering_coefficient",
|
|
1184
1186
|
"atmospheric_light",
|
|
@@ -1187,6 +1189,8 @@ GENERIC_LOADER_NAMES = [
|
|
|
1187
1189
|
GENERIC_WRITER_NAMES = [
|
|
1188
1190
|
"write_map_2d",
|
|
1189
1191
|
"write_map_3d",
|
|
1192
|
+
"write_semantic_segmentation",
|
|
1193
|
+
"write_instance_segmentation",
|
|
1190
1194
|
"write_points_3d",
|
|
1191
1195
|
"write_scattering_coefficient",
|
|
1192
1196
|
"write_atmospheric_light",
|
|
@@ -1345,6 +1349,43 @@ class TestGenericBackwardCompat:
|
|
|
1345
1349
|
assert torch.equal(top, gpu)
|
|
1346
1350
|
|
|
1347
1351
|
|
|
1352
|
+
class TestGenericSegmentation:
|
|
1353
|
+
"""Generic segmentation codecs preserve the standardized CUPS format."""
|
|
1354
|
+
|
|
1355
|
+
@pytest.mark.parametrize(
|
|
1356
|
+
("function", "writer", "dtype", "torch_dtype", "values"),
|
|
1357
|
+
[
|
|
1358
|
+
("semantic_segmentation", "write_semantic_segmentation", np.uint8, torch.uint8, [[0, 18], [255, 7]]),
|
|
1359
|
+
("instance_segmentation", "write_instance_segmentation", np.uint16, torch.uint16, [[0, 1], [257, 65535]]),
|
|
1360
|
+
],
|
|
1361
|
+
)
|
|
1362
|
+
def test_cpu_gpu_npy_roundtrip(self, tmp_path, function, writer, dtype, torch_dtype, values):
|
|
1363
|
+
expected = np.asarray(values, dtype=dtype)
|
|
1364
|
+
path = tmp_path / f"{function}.npy"
|
|
1365
|
+
getattr(cpu_generic, writer)(str(path), expected)
|
|
1366
|
+
|
|
1367
|
+
cpu_value = getattr(cpu_generic, function)(str(path))
|
|
1368
|
+
gpu_value = getattr(gpu_generic, function)(str(path))
|
|
1369
|
+
assert cpu_value.dtype == dtype
|
|
1370
|
+
assert np.array_equal(cpu_value, expected)
|
|
1371
|
+
assert gpu_value.dtype == torch_dtype
|
|
1372
|
+
assert torch.equal(gpu_value, torch.from_numpy(expected))
|
|
1373
|
+
|
|
1374
|
+
def test_writer_accepts_singleton_channel(self, tmp_path):
|
|
1375
|
+
value = np.array([[[0, 1], [2, 255]]], dtype=np.uint8)
|
|
1376
|
+
path = tmp_path / "semantic.npz"
|
|
1377
|
+
cpu_generic.write_semantic_segmentation(str(path), value)
|
|
1378
|
+
loaded = cpu_generic.semantic_segmentation(str(path))
|
|
1379
|
+
assert loaded.shape == (2, 2)
|
|
1380
|
+
assert np.array_equal(loaded, value[0])
|
|
1381
|
+
|
|
1382
|
+
def test_loader_rejects_non_hw_storage(self, tmp_path):
|
|
1383
|
+
path = tmp_path / "semantic.npy"
|
|
1384
|
+
np.save(path, np.zeros((1, 2, 3), dtype=np.uint8))
|
|
1385
|
+
with pytest.raises(ValueError, match=r"must have shape \(H, W\)"):
|
|
1386
|
+
cpu_generic.semantic_segmentation(str(path))
|
|
1387
|
+
|
|
1388
|
+
|
|
1348
1389
|
# ---------------------------------------------------------------------------
|
|
1349
1390
|
# Generic map_2d / map_3d loader tests
|
|
1350
1391
|
# ---------------------------------------------------------------------------
|
|
@@ -240,6 +240,71 @@ class TestWriteSample:
|
|
|
240
240
|
output_index = json.load(io.TextIOWrapper(entry, encoding="utf-8"))
|
|
241
241
|
assert output_index["type"] == "txt"
|
|
242
242
|
|
|
243
|
+
def test_output_writer_writes_scoped_metadata_and_manifest(self, tmp_path):
|
|
244
|
+
def writer(path: str, value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
245
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
246
|
+
f.write(str(value))
|
|
247
|
+
|
|
248
|
+
index = _flat_index("txt", ["f001"])
|
|
249
|
+
with patch(
|
|
250
|
+
"euler_loading.dataset.index_dataset_from_path",
|
|
251
|
+
return_value=index,
|
|
252
|
+
):
|
|
253
|
+
ds = MultiModalDataset(
|
|
254
|
+
modalities={
|
|
255
|
+
"depth": Modality(
|
|
256
|
+
"/data/depth", loader=dummy_loader, writer=writer
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
output_writer = ds.create_output_writer(
|
|
262
|
+
"depth", tmp_path / "out", metadata_scope="depth"
|
|
263
|
+
)
|
|
264
|
+
ds.write_sample(0, {"depth": "prediction"}, output_writer)
|
|
265
|
+
output_writer.save_index()
|
|
266
|
+
|
|
267
|
+
scoped_index = tmp_path / "out" / ".ds_crawler" / "depth" / "output.json"
|
|
268
|
+
manifest_path = tmp_path / "out" / ".ds_crawler" / "scopes.json"
|
|
269
|
+
assert json.loads(scoped_index.read_text())["type"] == "txt"
|
|
270
|
+
manifest = json.loads(manifest_path.read_text())
|
|
271
|
+
assert manifest["scopes"]["depth"]["metadata_dir"] == ".ds_crawler/depth"
|
|
272
|
+
assert "output.json" in manifest["scopes"]["depth"]["files"]
|
|
273
|
+
|
|
274
|
+
def test_zip_output_writer_writes_scoped_metadata_and_manifest(self, tmp_path):
|
|
275
|
+
def writer(path: str, value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
276
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
277
|
+
f.write(str(value))
|
|
278
|
+
|
|
279
|
+
index = _flat_index("txt", ["f001"])
|
|
280
|
+
with patch(
|
|
281
|
+
"euler_loading.dataset.index_dataset_from_path",
|
|
282
|
+
return_value=index,
|
|
283
|
+
):
|
|
284
|
+
ds = MultiModalDataset(
|
|
285
|
+
modalities={
|
|
286
|
+
"depth": Modality(
|
|
287
|
+
"/data/depth", loader=dummy_loader, writer=writer
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
output_writer = ds.create_output_writer(
|
|
293
|
+
"depth", tmp_path / "out.zip", zip=True, metadata_scope="depth"
|
|
294
|
+
)
|
|
295
|
+
ds.write_sample(0, {"depth": "prediction"}, output_writer)
|
|
296
|
+
output_writer.save_index()
|
|
297
|
+
|
|
298
|
+
with zipfile.ZipFile(tmp_path / "out.zip") as zf:
|
|
299
|
+
scoped_index = json.loads(
|
|
300
|
+
zf.read(".ds_crawler/depth/output.json").decode("utf-8")
|
|
301
|
+
)
|
|
302
|
+
manifest = json.loads(
|
|
303
|
+
zf.read(".ds_crawler/scopes.json").decode("utf-8")
|
|
304
|
+
)
|
|
305
|
+
assert scoped_index["type"] == "txt"
|
|
306
|
+
assert manifest["scopes"]["depth"]["metadata_dir"] == ".ds_crawler/depth"
|
|
307
|
+
|
|
243
308
|
def test_write_sample_records_attributes_on_dataset_writer_entry(self, tmp_path):
|
|
244
309
|
"""write_sample(attributes={modality: {...}}) lands on the output entry."""
|
|
245
310
|
def writer(path: str, value: Any, meta: dict[str, Any] | None = None) -> None:
|
|
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
|
{euler_loading-2.18.0 → euler_loading-2.19.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
|
|
File without changes
|
{euler_loading-2.18.0 → euler_loading-2.19.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|