euler-loading 2.12.0__tar.gz → 2.14.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.12.0 → euler_loading-2.14.0}/PKG-INFO +1 -1
- {euler_loading-2.12.0 → euler_loading-2.14.0}/README.md +11 -2
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/_resolution.py +2 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/__init__.py +4 -0
- euler_loading-2.14.0/euler_loading/loaders/_princeton_dense.py +314 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/cpu/__init__.py +2 -0
- euler_loading-2.14.0/euler_loading/loaders/cpu/princeton_dense.py +141 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/generate/loaders.json +112 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/gpu/__init__.py +2 -0
- euler_loading-2.14.0/euler_loading/loaders/gpu/princeton_dense.py +142 -0
- euler_loading-2.14.0/euler_loading/loaders/princeton_dense.py +12 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/package-lock.json +1 -1
- {euler_loading-2.12.0 → euler_loading-2.14.0}/pyproject.toml +1 -1
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/test_loaders.py +212 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/.github/workflows/workflow.yml +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/.gitignore +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/docs/loader-attributes.md +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/__init__.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/_dataset_contract.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/_ds_crawler_utils.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/_metadata.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/_writing.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/dataset.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/indexing.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/_annotations.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/_writer_utils.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/contracts.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/cpu/generic.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/cpu/muses.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/generate/__init__.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/generate/__main__.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/generic.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/gpu/generic.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/gpu/muses.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/muses.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/real_drive_sim.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/loaders/vkitti2.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/euler_loading/preprocessing.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/example.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/sample_rds.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/__init__.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/conftest.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/example_rds_calib.json +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/test_dataset.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/test_id_schema.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/test_indexing.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/test_preprocessing.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/test_real_dataset.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/tests/test_writing.py +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/vkitti_cpu_example_output.json +0 -0
- {euler_loading-2.12.0 → euler_loading-2.14.0}/vkitti_gpu_example_output.json +0 -0
|
@@ -381,7 +381,7 @@ When `Modality.loader` is `None`, euler-loading resolves the loader from the ds-
|
|
|
381
381
|
}
|
|
382
382
|
```
|
|
383
383
|
|
|
384
|
-
`loader` is the module name (`vkitti2`, `real_drive_sim`, `muses`, or `generic_dense_depth`) and `function` is the function within that module. The GPU variant is used by default.
|
|
384
|
+
`loader` is the module name (`vkitti2`, `real_drive_sim`, `muses`, `princeton_dense`, or `generic_dense_depth`) and `function` is the function within that module. The GPU variant is used by default.
|
|
385
385
|
|
|
386
386
|
Writer resolution uses the same module and function metadata:
|
|
387
387
|
|
|
@@ -530,6 +530,15 @@ All built-in loaders accept both filesystem paths (`str`) and in-memory buffers
|
|
|
530
530
|
| `read_intrinsics` | RGB camera intrinsics from MUSES `calib.json` as a `(3, 3)` matrix; defaults to `sensor="rgb"` |
|
|
531
531
|
| `read_extrinsics` | Camera extrinsics from MUSES `calib.json` as a `(4, 4)` matrix; defaults to `transform="lidar2rgb"` for sparse-depth reprojection into RGB pixels |
|
|
532
532
|
|
|
533
|
+
### Princeton DENSE / SeeingThroughFog (`euler_loading.loaders.princeton_dense`)
|
|
534
|
+
|
|
535
|
+
| Function | Description |
|
|
536
|
+
|----------|-------------|
|
|
537
|
+
| `rgb` | Left/right stereo 12-bit Bayer TIFF as RGB float32, normalised to [0, 1] |
|
|
538
|
+
| `sparse_depth` | Lidar `.bin` point cloud as `(N, 5)` float32: `x, y, z, intensity, ring` |
|
|
539
|
+
| `read_intrinsics` | Left stereo camera intrinsics `K` from `calib_cam_stereo_left.json` as a `(3, 3)` matrix |
|
|
540
|
+
| `read_extrinsics` | Transform from HDL64 lidar frame `lidar_hdl64_s3_roof` to left camera optical frame `cam_stereo_left_optical` from `calib_tf_tree_full.json` as a `(4, 4)` matrix |
|
|
541
|
+
|
|
533
542
|
### Generic Dense Depth (`euler_loading.loaders.gpu.generic_dense_depth`)
|
|
534
543
|
|
|
535
544
|
A format-agnostic loader that infers the loading strategy from the file extension. Useful for datasets that don't have a dedicated loader module.
|
|
@@ -541,7 +550,7 @@ A format-agnostic loader that infers the loading strategy from the file extensio
|
|
|
541
550
|
| `sky_mask` | Binary mask by comparing pixels against `meta["sky_mask"]` (`[R, G, B]`). Requires `meta` |
|
|
542
551
|
| `read_intrinsics` | Returns `meta["intrinsics"]` as a `(3, 3)` tensor. Ignores path; requires `meta` |
|
|
543
552
|
|
|
544
|
-
CPU variants of all loaders live under `euler_loading.loaders.cpu.{vkitti2,real_drive_sim,muses,generic_dense_depth}`.
|
|
553
|
+
CPU variants of all loaders live under `euler_loading.loaders.cpu.{vkitti2,real_drive_sim,muses,princeton_dense,generic_dense_depth}`.
|
|
545
554
|
|
|
546
555
|
### Flattening hierarchical modalities
|
|
547
556
|
|
|
@@ -24,6 +24,8 @@ _LOADER_MODULES: dict[str, str] = {
|
|
|
24
24
|
"generic_dense_depth": "euler_loading.loaders.gpu.generic_dense_depth",
|
|
25
25
|
"generic": "euler_loading.loaders.gpu.generic",
|
|
26
26
|
"muses": "euler_loading.loaders.gpu.muses",
|
|
27
|
+
"princeton_dense": "euler_loading.loaders.gpu.princeton_dense",
|
|
28
|
+
"seeing_through_fog": "euler_loading.loaders.gpu.princeton_dense",
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
|
|
@@ -27,4 +27,8 @@ Available submodules:
|
|
|
27
27
|
- :mod:`euler_loading.loaders.cpu.real_drive_sim` — Real Drive Sim (numpy)
|
|
28
28
|
- :mod:`euler_loading.loaders.gpu.muses` — MUSES (torch)
|
|
29
29
|
- :mod:`euler_loading.loaders.cpu.muses` — MUSES (numpy)
|
|
30
|
+
- :mod:`euler_loading.loaders.gpu.princeton_dense` — Princeton DENSE /
|
|
31
|
+
SeeingThroughFog (torch)
|
|
32
|
+
- :mod:`euler_loading.loaders.cpu.princeton_dense` — Princeton DENSE /
|
|
33
|
+
SeeingThroughFog (numpy)
|
|
30
34
|
"""
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""Shared helpers for Princeton DENSE / SeeingThroughFog loaders."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from collections import deque
|
|
8
|
+
from collections.abc import Mapping
|
|
9
|
+
from typing import Any, BinaryIO, Union
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from PIL import Image
|
|
13
|
+
|
|
14
|
+
_DEFAULT_BAYER_PATTERN = "GBRG"
|
|
15
|
+
_DEFAULT_RAW_MAX_VALUE = 4095.0
|
|
16
|
+
DEFAULT_CAMERA_FRAME = "cam_stereo_left_optical"
|
|
17
|
+
DEFAULT_LIDAR_FRAME = "lidar_hdl64_s3_roof"
|
|
18
|
+
LIDAR_COLUMNS = ["x", "y", "z", "intensity", "ring"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _value_from_context(
|
|
22
|
+
meta: Mapping[str, Any] | None,
|
|
23
|
+
attributes: Mapping[str, Any] | None,
|
|
24
|
+
keys: tuple[str, ...],
|
|
25
|
+
) -> Any:
|
|
26
|
+
for source in (attributes, meta):
|
|
27
|
+
if not isinstance(source, Mapping):
|
|
28
|
+
continue
|
|
29
|
+
for key in keys:
|
|
30
|
+
if key in source:
|
|
31
|
+
return source[key]
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _rgb_scale(
|
|
36
|
+
raw: np.ndarray,
|
|
37
|
+
meta: Mapping[str, Any] | None,
|
|
38
|
+
attributes: Mapping[str, Any] | None,
|
|
39
|
+
) -> float:
|
|
40
|
+
value = _value_from_context(
|
|
41
|
+
meta,
|
|
42
|
+
attributes,
|
|
43
|
+
("rgb_max_value", "raw_max_value", "white_level"),
|
|
44
|
+
)
|
|
45
|
+
if value is not None:
|
|
46
|
+
return float(value)
|
|
47
|
+
|
|
48
|
+
if np.issubdtype(raw.dtype, np.integer):
|
|
49
|
+
if raw.dtype == np.uint8:
|
|
50
|
+
return 255.0
|
|
51
|
+
if raw.size and float(raw.max()) <= _DEFAULT_RAW_MAX_VALUE:
|
|
52
|
+
return _DEFAULT_RAW_MAX_VALUE
|
|
53
|
+
return float(np.iinfo(raw.dtype).max)
|
|
54
|
+
|
|
55
|
+
return 1.0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _bayer_pattern(
|
|
59
|
+
meta: Mapping[str, Any] | None,
|
|
60
|
+
attributes: Mapping[str, Any] | None,
|
|
61
|
+
) -> str:
|
|
62
|
+
value = _value_from_context(meta, attributes, ("bayer_pattern", "cfa_pattern"))
|
|
63
|
+
if value is None:
|
|
64
|
+
return _DEFAULT_BAYER_PATTERN
|
|
65
|
+
pattern = str(value).upper()
|
|
66
|
+
if len(pattern) != 4 or any(channel not in "RGB" for channel in pattern):
|
|
67
|
+
raise ValueError("bayer_pattern must be a 4-character pattern using R, G, and B")
|
|
68
|
+
return pattern
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _interpolate_channel(values: np.ndarray, mask: np.ndarray) -> np.ndarray:
|
|
72
|
+
values_pad = np.pad(values, 1, mode="edge")
|
|
73
|
+
mask_pad = np.pad(mask.astype(np.float32), 1, mode="edge")
|
|
74
|
+
|
|
75
|
+
sums = np.zeros_like(values, dtype=np.float32)
|
|
76
|
+
counts = np.zeros_like(values, dtype=np.float32)
|
|
77
|
+
for y_offset in range(3):
|
|
78
|
+
for x_offset in range(3):
|
|
79
|
+
y = slice(y_offset, y_offset + values.shape[0])
|
|
80
|
+
x = slice(x_offset, x_offset + values.shape[1])
|
|
81
|
+
sums += values_pad[y, x]
|
|
82
|
+
counts += mask_pad[y, x]
|
|
83
|
+
|
|
84
|
+
interpolated = np.divide(
|
|
85
|
+
sums,
|
|
86
|
+
counts,
|
|
87
|
+
out=np.zeros_like(sums, dtype=np.float32),
|
|
88
|
+
where=counts > 0,
|
|
89
|
+
)
|
|
90
|
+
return np.where(mask, values, interpolated)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _demosaic_bayer(raw: np.ndarray, pattern: str) -> np.ndarray:
|
|
94
|
+
raw_f32 = raw.astype(np.float32, copy=False)
|
|
95
|
+
channel_values = {
|
|
96
|
+
"R": np.zeros(raw.shape, dtype=np.float32),
|
|
97
|
+
"G": np.zeros(raw.shape, dtype=np.float32),
|
|
98
|
+
"B": np.zeros(raw.shape, dtype=np.float32),
|
|
99
|
+
}
|
|
100
|
+
channel_masks = {
|
|
101
|
+
"R": np.zeros(raw.shape, dtype=bool),
|
|
102
|
+
"G": np.zeros(raw.shape, dtype=bool),
|
|
103
|
+
"B": np.zeros(raw.shape, dtype=bool),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
positions = (
|
|
107
|
+
(slice(0, None, 2), slice(0, None, 2), pattern[0]),
|
|
108
|
+
(slice(0, None, 2), slice(1, None, 2), pattern[1]),
|
|
109
|
+
(slice(1, None, 2), slice(0, None, 2), pattern[2]),
|
|
110
|
+
(slice(1, None, 2), slice(1, None, 2), pattern[3]),
|
|
111
|
+
)
|
|
112
|
+
for row_slice, col_slice, channel in positions:
|
|
113
|
+
channel_values[channel][row_slice, col_slice] = raw_f32[row_slice, col_slice]
|
|
114
|
+
channel_masks[channel][row_slice, col_slice] = True
|
|
115
|
+
|
|
116
|
+
channels = [
|
|
117
|
+
_interpolate_channel(channel_values[channel], channel_masks[channel])
|
|
118
|
+
for channel in ("R", "G", "B")
|
|
119
|
+
]
|
|
120
|
+
return np.stack(channels, axis=-1)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def load_rgb_array(
|
|
124
|
+
path: Union[str, BinaryIO],
|
|
125
|
+
meta: Mapping[str, Any] | None = None,
|
|
126
|
+
attributes: Mapping[str, Any] | None = None,
|
|
127
|
+
) -> np.ndarray:
|
|
128
|
+
"""Load a SeeingThroughFog RGB TIFF as an ``(H, W, 3)`` float32 array."""
|
|
129
|
+
with Image.open(path) as image:
|
|
130
|
+
raw = np.array(image)
|
|
131
|
+
|
|
132
|
+
if raw.ndim == 2:
|
|
133
|
+
rgb = _demosaic_bayer(raw, _bayer_pattern(meta, attributes))
|
|
134
|
+
scale = _rgb_scale(raw, meta, attributes)
|
|
135
|
+
elif raw.ndim == 3 and raw.shape[-1] >= 3:
|
|
136
|
+
rgb = raw[..., :3].astype(np.float32, copy=False)
|
|
137
|
+
scale = _rgb_scale(raw, meta, attributes)
|
|
138
|
+
else:
|
|
139
|
+
name = getattr(path, "name", path)
|
|
140
|
+
raise ValueError(f"Unsupported Princeton RGB image shape for {name!r}: {raw.shape}")
|
|
141
|
+
|
|
142
|
+
if scale <= 0:
|
|
143
|
+
raise ValueError(f"RGB scale must be positive, got {scale!r}")
|
|
144
|
+
return np.clip(rgb / scale, 0.0, 1.0).astype(np.float32, copy=False)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def load_sparse_depth_array(path: Union[str, BinaryIO]) -> np.ndarray:
|
|
148
|
+
"""Load a SeeingThroughFog lidar ``.bin`` file as ``(N, 5)`` float32."""
|
|
149
|
+
if isinstance(path, (str, os.PathLike)):
|
|
150
|
+
values = np.fromfile(path, dtype=np.float32)
|
|
151
|
+
else:
|
|
152
|
+
values = np.frombuffer(path.read(), dtype=np.float32).copy()
|
|
153
|
+
|
|
154
|
+
if values.size % 5 != 0:
|
|
155
|
+
name = getattr(path, "name", path)
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"Princeton sparse-depth file {name!r} contains {values.size} "
|
|
158
|
+
"float32 values, which is not divisible by 5."
|
|
159
|
+
)
|
|
160
|
+
return values.reshape((-1, 5))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _load_json(path: Union[str, BinaryIO]) -> Any:
|
|
164
|
+
if isinstance(path, (str, os.PathLike)):
|
|
165
|
+
with open(path) as f:
|
|
166
|
+
return json.load(f)
|
|
167
|
+
return json.load(path)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def load_intrinsics_array(path: Union[str, BinaryIO]) -> np.ndarray:
|
|
171
|
+
"""Load the left stereo camera intrinsics matrix from ROS camera-info JSON."""
|
|
172
|
+
data = _load_json(path)
|
|
173
|
+
if not isinstance(data, Mapping) or "K" not in data:
|
|
174
|
+
name = getattr(path, "name", path)
|
|
175
|
+
raise ValueError(f"Princeton camera calibration {name!r} must contain a K array.")
|
|
176
|
+
|
|
177
|
+
K = np.asarray(data["K"], dtype=np.float32)
|
|
178
|
+
if K.shape == (9,):
|
|
179
|
+
K = K.reshape(3, 3)
|
|
180
|
+
if K.shape != (3, 3):
|
|
181
|
+
raise ValueError(f"Princeton camera K must have shape (3, 3), got {K.shape}.")
|
|
182
|
+
return K
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _select_frame(
|
|
186
|
+
meta: Mapping[str, Any] | None,
|
|
187
|
+
attributes: Mapping[str, Any] | None,
|
|
188
|
+
keys: tuple[str, ...],
|
|
189
|
+
default: str,
|
|
190
|
+
) -> str:
|
|
191
|
+
value = _value_from_context(meta, attributes, keys)
|
|
192
|
+
if value is None:
|
|
193
|
+
return default
|
|
194
|
+
frame = str(value)
|
|
195
|
+
aliases = {
|
|
196
|
+
"camera": DEFAULT_CAMERA_FRAME,
|
|
197
|
+
"cam_stereo_left": DEFAULT_CAMERA_FRAME,
|
|
198
|
+
"left": DEFAULT_CAMERA_FRAME,
|
|
199
|
+
"rgb": DEFAULT_CAMERA_FRAME,
|
|
200
|
+
"lidar": DEFAULT_LIDAR_FRAME,
|
|
201
|
+
"hdl64": DEFAULT_LIDAR_FRAME,
|
|
202
|
+
"hdl64_s3": DEFAULT_LIDAR_FRAME,
|
|
203
|
+
"lidar_hdl64": DEFAULT_LIDAR_FRAME,
|
|
204
|
+
}
|
|
205
|
+
return aliases.get(frame, frame)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _matrix_from_transform(transform: Mapping[str, Any]) -> np.ndarray:
|
|
209
|
+
translation = transform.get("translation")
|
|
210
|
+
rotation = transform.get("rotation")
|
|
211
|
+
if not isinstance(translation, Mapping) or not isinstance(rotation, Mapping):
|
|
212
|
+
raise ValueError("TF transform entries must contain translation and rotation objects.")
|
|
213
|
+
|
|
214
|
+
x = float(rotation.get("x", 0.0))
|
|
215
|
+
y = float(rotation.get("y", 0.0))
|
|
216
|
+
z = float(rotation.get("z", 0.0))
|
|
217
|
+
w = float(rotation.get("w", 1.0))
|
|
218
|
+
norm = float(np.sqrt(x * x + y * y + z * z + w * w))
|
|
219
|
+
if norm <= 0.0:
|
|
220
|
+
raise ValueError("TF quaternion must have non-zero norm.")
|
|
221
|
+
x /= norm
|
|
222
|
+
y /= norm
|
|
223
|
+
z /= norm
|
|
224
|
+
w /= norm
|
|
225
|
+
|
|
226
|
+
matrix = np.eye(4, dtype=np.float64)
|
|
227
|
+
matrix[:3, :3] = np.array(
|
|
228
|
+
[
|
|
229
|
+
[1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
|
|
230
|
+
[2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
|
|
231
|
+
[2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
|
|
232
|
+
],
|
|
233
|
+
dtype=np.float64,
|
|
234
|
+
)
|
|
235
|
+
matrix[:3, 3] = [
|
|
236
|
+
float(translation.get("x", 0.0)),
|
|
237
|
+
float(translation.get("y", 0.0)),
|
|
238
|
+
float(translation.get("z", 0.0)),
|
|
239
|
+
]
|
|
240
|
+
return matrix
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _tf_edges(data: Any) -> dict[str, list[tuple[str, np.ndarray]]]:
|
|
244
|
+
if not isinstance(data, list):
|
|
245
|
+
raise ValueError("Princeton TF calibration must be a JSON list of transforms.")
|
|
246
|
+
|
|
247
|
+
edges: dict[str, list[tuple[str, np.ndarray]]] = {}
|
|
248
|
+
for entry in data:
|
|
249
|
+
if not isinstance(entry, Mapping):
|
|
250
|
+
continue
|
|
251
|
+
header = entry.get("header")
|
|
252
|
+
child = entry.get("child_frame_id")
|
|
253
|
+
transform = entry.get("transform")
|
|
254
|
+
if not isinstance(header, Mapping) or not isinstance(child, str):
|
|
255
|
+
continue
|
|
256
|
+
parent = header.get("frame_id")
|
|
257
|
+
if not isinstance(parent, str) or not isinstance(transform, Mapping):
|
|
258
|
+
continue
|
|
259
|
+
|
|
260
|
+
# ROS TransformStamped stores the child pose in the parent frame.
|
|
261
|
+
# This matrix maps coordinates from child frame to parent frame.
|
|
262
|
+
parent_from_child = _matrix_from_transform(transform)
|
|
263
|
+
child_from_parent = np.linalg.inv(parent_from_child)
|
|
264
|
+
edges.setdefault(child, []).append((parent, parent_from_child))
|
|
265
|
+
edges.setdefault(parent, []).append((child, child_from_parent))
|
|
266
|
+
return edges
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _compose_tf(
|
|
270
|
+
edges: Mapping[str, list[tuple[str, np.ndarray]]],
|
|
271
|
+
source_frame: str,
|
|
272
|
+
target_frame: str,
|
|
273
|
+
) -> np.ndarray:
|
|
274
|
+
queue: deque[tuple[str, np.ndarray]] = deque(
|
|
275
|
+
[(source_frame, np.eye(4, dtype=np.float64))]
|
|
276
|
+
)
|
|
277
|
+
seen = {source_frame}
|
|
278
|
+
|
|
279
|
+
while queue:
|
|
280
|
+
frame, frame_from_source = queue.popleft()
|
|
281
|
+
if frame == target_frame:
|
|
282
|
+
return frame_from_source.astype(np.float32)
|
|
283
|
+
for next_frame, next_from_frame in edges.get(frame, []):
|
|
284
|
+
if next_frame in seen:
|
|
285
|
+
continue
|
|
286
|
+
seen.add(next_frame)
|
|
287
|
+
queue.append((next_frame, next_from_frame @ frame_from_source))
|
|
288
|
+
|
|
289
|
+
available = ", ".join(sorted(edges.keys()))
|
|
290
|
+
raise KeyError(
|
|
291
|
+
f"No TF path from {source_frame!r} to {target_frame!r}. "
|
|
292
|
+
f"Available frames: {available}"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def load_extrinsics_array(
|
|
297
|
+
path: Union[str, BinaryIO],
|
|
298
|
+
meta: Mapping[str, Any] | None = None,
|
|
299
|
+
attributes: Mapping[str, Any] | None = None,
|
|
300
|
+
) -> np.ndarray:
|
|
301
|
+
"""Load a 4x4 transform mapping HDL64 lidar points into the camera frame."""
|
|
302
|
+
source_frame = _select_frame(
|
|
303
|
+
meta,
|
|
304
|
+
attributes,
|
|
305
|
+
("source_frame", "source_sensor", "from_frame", "from_sensor", "source"),
|
|
306
|
+
DEFAULT_LIDAR_FRAME,
|
|
307
|
+
)
|
|
308
|
+
target_frame = _select_frame(
|
|
309
|
+
meta,
|
|
310
|
+
attributes,
|
|
311
|
+
("target_frame", "target_sensor", "to_frame", "to_sensor", "target", "camera"),
|
|
312
|
+
DEFAULT_CAMERA_FRAME,
|
|
313
|
+
)
|
|
314
|
+
return _compose_tf(_tf_edges(_load_json(path)), source_frame, target_frame)
|
|
@@ -10,4 +10,6 @@ Available submodules:
|
|
|
10
10
|
- :mod:`euler_loading.loaders.cpu.vkitti2` — Virtual KITTI 2
|
|
11
11
|
- :mod:`euler_loading.loaders.cpu.real_drive_sim` — Real Drive Sim
|
|
12
12
|
- :mod:`euler_loading.loaders.cpu.muses` — MUSES
|
|
13
|
+
- :mod:`euler_loading.loaders.cpu.princeton_dense` — Princeton DENSE /
|
|
14
|
+
SeeingThroughFog
|
|
13
15
|
"""
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""CPU-oriented loaders for the Princeton DENSE / SeeingThroughFog dataset.
|
|
2
|
+
|
|
3
|
+
The SeeingThroughFog release stores stereo-camera frames as 12-bit Bayer TIFF
|
|
4
|
+
images and Velodyne lidar point clouds as binary ``float32`` records.
|
|
5
|
+
|
|
6
|
+
Return types
|
|
7
|
+
------------
|
|
8
|
+
- **rgb** -- ``np.ndarray`` of shape ``(H, W, 3)`` float32 in ``[0, 1]``.
|
|
9
|
+
- **sparse_depth** -- ``np.ndarray`` of shape ``(N, 5)`` float32 with columns
|
|
10
|
+
``x, y, z, intensity, ring``.
|
|
11
|
+
- **read_intrinsics** -- ``np.ndarray`` of shape ``(3, 3)`` float32 from
|
|
12
|
+
``calib_cam_stereo_left.json``.
|
|
13
|
+
- **read_extrinsics** -- ``np.ndarray`` of shape ``(4, 4)`` float32 from
|
|
14
|
+
``calib_tf_tree_full.json``, mapping HDL64 lidar points into the left camera
|
|
15
|
+
optical frame by default.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, BinaryIO, Union
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
from euler_loading.loaders._annotations import modality_meta
|
|
25
|
+
from euler_loading.loaders._princeton_dense import DEFAULT_CAMERA_FRAME
|
|
26
|
+
from euler_loading.loaders._princeton_dense import DEFAULT_LIDAR_FRAME
|
|
27
|
+
from euler_loading.loaders._princeton_dense import LIDAR_COLUMNS
|
|
28
|
+
from euler_loading.loaders._princeton_dense import load_extrinsics_array
|
|
29
|
+
from euler_loading.loaders._princeton_dense import load_intrinsics_array
|
|
30
|
+
from euler_loading.loaders._princeton_dense import load_rgb_array
|
|
31
|
+
from euler_loading.loaders._princeton_dense import load_sparse_depth_array
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@modality_meta(
|
|
35
|
+
modality_type="rgb",
|
|
36
|
+
dtype="float32",
|
|
37
|
+
shape="HWC",
|
|
38
|
+
file_formats=[".tif", ".tiff"],
|
|
39
|
+
output_range=[0.0, 1.0],
|
|
40
|
+
meta={
|
|
41
|
+
"dataset": "SeeingThroughFog",
|
|
42
|
+
"raw_bit_depth": 12,
|
|
43
|
+
"bayer_pattern": "GBRG",
|
|
44
|
+
"raw_max_value": 4095.0,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
def rgb(
|
|
48
|
+
path: Union[str, BinaryIO],
|
|
49
|
+
meta: dict[str, Any] | None = None,
|
|
50
|
+
*,
|
|
51
|
+
attributes: dict[str, Any] | None = None,
|
|
52
|
+
) -> np.ndarray:
|
|
53
|
+
"""Load a SeeingThroughFog 12-bit Bayer TIFF as RGB float32 in ``[0, 1]``."""
|
|
54
|
+
return load_rgb_array(path, meta, attributes)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@modality_meta(
|
|
58
|
+
modality_type="sparse_depth",
|
|
59
|
+
dtype="float32",
|
|
60
|
+
shape="NC",
|
|
61
|
+
file_formats=[".bin"],
|
|
62
|
+
meta={
|
|
63
|
+
"dataset": "SeeingThroughFog",
|
|
64
|
+
"representation": "point_cloud",
|
|
65
|
+
"columns": LIDAR_COLUMNS,
|
|
66
|
+
"coordinate_unit": "meters",
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
def sparse_depth(
|
|
70
|
+
path: Union[str, BinaryIO],
|
|
71
|
+
meta: dict[str, Any] | None = None,
|
|
72
|
+
*,
|
|
73
|
+
attributes: dict[str, Any] | None = None,
|
|
74
|
+
) -> np.ndarray:
|
|
75
|
+
"""Load a SeeingThroughFog lidar point cloud as ``(N, 5)`` float32."""
|
|
76
|
+
return load_sparse_depth_array(path)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def lidar_point_cloud(
|
|
80
|
+
path: Union[str, BinaryIO],
|
|
81
|
+
meta: dict[str, Any] | None = None,
|
|
82
|
+
*,
|
|
83
|
+
attributes: dict[str, Any] | None = None,
|
|
84
|
+
) -> np.ndarray:
|
|
85
|
+
"""Alias for :func:`sparse_depth`."""
|
|
86
|
+
return load_sparse_depth_array(path)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def point_cloud(
|
|
90
|
+
path: Union[str, BinaryIO],
|
|
91
|
+
meta: dict[str, Any] | None = None,
|
|
92
|
+
*,
|
|
93
|
+
attributes: dict[str, Any] | None = None,
|
|
94
|
+
) -> np.ndarray:
|
|
95
|
+
"""Alias for :func:`sparse_depth`."""
|
|
96
|
+
return load_sparse_depth_array(path)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@modality_meta(
|
|
100
|
+
modality_type="intrinsics",
|
|
101
|
+
dtype="float32",
|
|
102
|
+
hierarchical=True,
|
|
103
|
+
shape="3x3",
|
|
104
|
+
file_formats=[".json"],
|
|
105
|
+
meta={
|
|
106
|
+
"dataset": "SeeingThroughFog",
|
|
107
|
+
"camera_frame": DEFAULT_CAMERA_FRAME,
|
|
108
|
+
"source": "calib_cam_stereo_left",
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
def read_intrinsics(
|
|
112
|
+
path: Union[str, BinaryIO],
|
|
113
|
+
meta: dict[str, Any] | None = None,
|
|
114
|
+
*,
|
|
115
|
+
attributes: dict[str, Any] | None = None,
|
|
116
|
+
) -> np.ndarray:
|
|
117
|
+
"""Load the left stereo camera intrinsics matrix ``K``."""
|
|
118
|
+
return load_intrinsics_array(path)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@modality_meta(
|
|
122
|
+
modality_type="camera_extrinsics",
|
|
123
|
+
dtype="float32",
|
|
124
|
+
hierarchical=True,
|
|
125
|
+
shape="4x4",
|
|
126
|
+
file_formats=[".json"],
|
|
127
|
+
meta={
|
|
128
|
+
"dataset": "SeeingThroughFog",
|
|
129
|
+
"default_source_frame": DEFAULT_LIDAR_FRAME,
|
|
130
|
+
"default_target_frame": DEFAULT_CAMERA_FRAME,
|
|
131
|
+
"transform_direction": "source_frame_to_target_frame",
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
def read_extrinsics(
|
|
135
|
+
path: Union[str, BinaryIO],
|
|
136
|
+
meta: dict[str, Any] | None = None,
|
|
137
|
+
*,
|
|
138
|
+
attributes: dict[str, Any] | None = None,
|
|
139
|
+
) -> np.ndarray:
|
|
140
|
+
"""Load the default ``lidar_hdl64_s3_roof`` to left-camera transform."""
|
|
141
|
+
return load_extrinsics_array(path, meta, attributes)
|
|
@@ -453,6 +453,109 @@
|
|
|
453
453
|
}
|
|
454
454
|
]
|
|
455
455
|
},
|
|
456
|
+
{
|
|
457
|
+
"name": "princeton_dense",
|
|
458
|
+
"modalities": [
|
|
459
|
+
{
|
|
460
|
+
"type": "camera_extrinsics",
|
|
461
|
+
"function": "read_extrinsics",
|
|
462
|
+
"hierarchical": true,
|
|
463
|
+
"dtype": "float32",
|
|
464
|
+
"access": "key",
|
|
465
|
+
"file_formats": [
|
|
466
|
+
".json"
|
|
467
|
+
],
|
|
468
|
+
"meta": {
|
|
469
|
+
"dataset": "SeeingThroughFog",
|
|
470
|
+
"default_source_frame": "lidar_hdl64_s3_roof",
|
|
471
|
+
"default_target_frame": "cam_stereo_left_optical",
|
|
472
|
+
"transform_direction": "source_frame_to_target_frame"
|
|
473
|
+
},
|
|
474
|
+
"cpu": {
|
|
475
|
+
"shape": "4x4"
|
|
476
|
+
},
|
|
477
|
+
"gpu": {
|
|
478
|
+
"shape": "4x4"
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
"type": "intrinsics",
|
|
483
|
+
"function": "read_intrinsics",
|
|
484
|
+
"hierarchical": true,
|
|
485
|
+
"dtype": "float32",
|
|
486
|
+
"access": "key",
|
|
487
|
+
"file_formats": [
|
|
488
|
+
".json"
|
|
489
|
+
],
|
|
490
|
+
"meta": {
|
|
491
|
+
"dataset": "SeeingThroughFog",
|
|
492
|
+
"camera_frame": "cam_stereo_left_optical",
|
|
493
|
+
"source": "calib_cam_stereo_left"
|
|
494
|
+
},
|
|
495
|
+
"cpu": {
|
|
496
|
+
"shape": "3x3"
|
|
497
|
+
},
|
|
498
|
+
"gpu": {
|
|
499
|
+
"shape": "3x3"
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
"type": "rgb",
|
|
504
|
+
"function": "rgb",
|
|
505
|
+
"hierarchical": false,
|
|
506
|
+
"dtype": "float32",
|
|
507
|
+
"access": "key",
|
|
508
|
+
"output_range": [
|
|
509
|
+
0.0,
|
|
510
|
+
1.0
|
|
511
|
+
],
|
|
512
|
+
"file_formats": [
|
|
513
|
+
".tif",
|
|
514
|
+
".tiff"
|
|
515
|
+
],
|
|
516
|
+
"meta": {
|
|
517
|
+
"dataset": "SeeingThroughFog",
|
|
518
|
+
"raw_bit_depth": 12,
|
|
519
|
+
"bayer_pattern": "GBRG",
|
|
520
|
+
"raw_max_value": 4095.0
|
|
521
|
+
},
|
|
522
|
+
"cpu": {
|
|
523
|
+
"shape": "HWC"
|
|
524
|
+
},
|
|
525
|
+
"gpu": {
|
|
526
|
+
"shape": "CHW"
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
"type": "sparse_depth",
|
|
531
|
+
"function": "sparse_depth",
|
|
532
|
+
"hierarchical": false,
|
|
533
|
+
"dtype": "float32",
|
|
534
|
+
"access": "key",
|
|
535
|
+
"file_formats": [
|
|
536
|
+
".bin"
|
|
537
|
+
],
|
|
538
|
+
"meta": {
|
|
539
|
+
"dataset": "SeeingThroughFog",
|
|
540
|
+
"representation": "point_cloud",
|
|
541
|
+
"columns": [
|
|
542
|
+
"x",
|
|
543
|
+
"y",
|
|
544
|
+
"z",
|
|
545
|
+
"intensity",
|
|
546
|
+
"ring"
|
|
547
|
+
],
|
|
548
|
+
"coordinate_unit": "meters"
|
|
549
|
+
},
|
|
550
|
+
"cpu": {
|
|
551
|
+
"shape": "NC"
|
|
552
|
+
},
|
|
553
|
+
"gpu": {
|
|
554
|
+
"shape": "NC"
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
]
|
|
558
|
+
},
|
|
456
559
|
{
|
|
457
560
|
"name": "real_drive_sim",
|
|
458
561
|
"modalities": [
|
|
@@ -465,6 +568,9 @@
|
|
|
465
568
|
"file_formats": [
|
|
466
569
|
".json"
|
|
467
570
|
],
|
|
571
|
+
"cpu": {
|
|
572
|
+
"shape": "dict"
|
|
573
|
+
},
|
|
468
574
|
"gpu": {
|
|
469
575
|
"shape": "dict"
|
|
470
576
|
}
|
|
@@ -490,6 +596,9 @@
|
|
|
490
596
|
"distortion"
|
|
491
597
|
]
|
|
492
598
|
},
|
|
599
|
+
"cpu": {
|
|
600
|
+
"shape": "dict"
|
|
601
|
+
},
|
|
493
602
|
"gpu": {
|
|
494
603
|
"shape": "dict"
|
|
495
604
|
}
|
|
@@ -546,6 +655,9 @@
|
|
|
546
655
|
"meta": {
|
|
547
656
|
"sensor": "CS_FRONT"
|
|
548
657
|
},
|
|
658
|
+
"cpu": {
|
|
659
|
+
"shape": "3x3"
|
|
660
|
+
},
|
|
549
661
|
"gpu": {
|
|
550
662
|
"shape": "3x3"
|
|
551
663
|
}
|
|
@@ -9,4 +9,6 @@ Available submodules:
|
|
|
9
9
|
- :mod:`euler_loading.loaders.gpu.vkitti2` — Virtual KITTI 2
|
|
10
10
|
- :mod:`euler_loading.loaders.gpu.real_drive_sim` — Real Drive Sim
|
|
11
11
|
- :mod:`euler_loading.loaders.gpu.muses` — MUSES
|
|
12
|
+
- :mod:`euler_loading.loaders.gpu.princeton_dense` — Princeton DENSE /
|
|
13
|
+
SeeingThroughFog
|
|
12
14
|
"""
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""GPU-oriented loaders for the Princeton DENSE / SeeingThroughFog dataset.
|
|
2
|
+
|
|
3
|
+
The SeeingThroughFog release stores stereo-camera frames as 12-bit Bayer TIFF
|
|
4
|
+
images and Velodyne lidar point clouds as binary ``float32`` records.
|
|
5
|
+
|
|
6
|
+
Return types
|
|
7
|
+
------------
|
|
8
|
+
- **rgb** -- ``torch.FloatTensor`` of shape ``(3, H, W)`` in ``[0, 1]``.
|
|
9
|
+
- **sparse_depth** -- ``torch.FloatTensor`` of shape ``(N, 5)`` with columns
|
|
10
|
+
``x, y, z, intensity, ring``.
|
|
11
|
+
- **read_intrinsics** -- ``torch.FloatTensor`` of shape ``(3, 3)`` from
|
|
12
|
+
``calib_cam_stereo_left.json``.
|
|
13
|
+
- **read_extrinsics** -- ``torch.FloatTensor`` of shape ``(4, 4)`` from
|
|
14
|
+
``calib_tf_tree_full.json``, mapping HDL64 lidar points into the left camera
|
|
15
|
+
optical frame by default.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, BinaryIO, Union
|
|
21
|
+
|
|
22
|
+
import torch
|
|
23
|
+
|
|
24
|
+
from euler_loading.loaders._annotations import modality_meta
|
|
25
|
+
from euler_loading.loaders._princeton_dense import DEFAULT_CAMERA_FRAME
|
|
26
|
+
from euler_loading.loaders._princeton_dense import DEFAULT_LIDAR_FRAME
|
|
27
|
+
from euler_loading.loaders._princeton_dense import LIDAR_COLUMNS
|
|
28
|
+
from euler_loading.loaders._princeton_dense import load_extrinsics_array
|
|
29
|
+
from euler_loading.loaders._princeton_dense import load_intrinsics_array
|
|
30
|
+
from euler_loading.loaders._princeton_dense import load_rgb_array
|
|
31
|
+
from euler_loading.loaders._princeton_dense import load_sparse_depth_array
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@modality_meta(
|
|
35
|
+
modality_type="rgb",
|
|
36
|
+
dtype="float32",
|
|
37
|
+
shape="CHW",
|
|
38
|
+
file_formats=[".tif", ".tiff"],
|
|
39
|
+
output_range=[0.0, 1.0],
|
|
40
|
+
meta={
|
|
41
|
+
"dataset": "SeeingThroughFog",
|
|
42
|
+
"raw_bit_depth": 12,
|
|
43
|
+
"bayer_pattern": "GBRG",
|
|
44
|
+
"raw_max_value": 4095.0,
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
def rgb(
|
|
48
|
+
path: Union[str, BinaryIO],
|
|
49
|
+
meta: dict[str, Any] | None = None,
|
|
50
|
+
*,
|
|
51
|
+
attributes: dict[str, Any] | None = None,
|
|
52
|
+
) -> torch.Tensor:
|
|
53
|
+
"""Load a SeeingThroughFog 12-bit Bayer TIFF as an RGB tensor."""
|
|
54
|
+
arr = load_rgb_array(path, meta, attributes)
|
|
55
|
+
return torch.from_numpy(arr).permute(2, 0, 1).contiguous()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@modality_meta(
|
|
59
|
+
modality_type="sparse_depth",
|
|
60
|
+
dtype="float32",
|
|
61
|
+
shape="NC",
|
|
62
|
+
file_formats=[".bin"],
|
|
63
|
+
meta={
|
|
64
|
+
"dataset": "SeeingThroughFog",
|
|
65
|
+
"representation": "point_cloud",
|
|
66
|
+
"columns": LIDAR_COLUMNS,
|
|
67
|
+
"coordinate_unit": "meters",
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
def sparse_depth(
|
|
71
|
+
path: Union[str, BinaryIO],
|
|
72
|
+
meta: dict[str, Any] | None = None,
|
|
73
|
+
*,
|
|
74
|
+
attributes: dict[str, Any] | None = None,
|
|
75
|
+
) -> torch.Tensor:
|
|
76
|
+
"""Load a SeeingThroughFog lidar point cloud as ``(N, 5)`` float32."""
|
|
77
|
+
return torch.from_numpy(load_sparse_depth_array(path)).contiguous()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def lidar_point_cloud(
|
|
81
|
+
path: Union[str, BinaryIO],
|
|
82
|
+
meta: dict[str, Any] | None = None,
|
|
83
|
+
*,
|
|
84
|
+
attributes: dict[str, Any] | None = None,
|
|
85
|
+
) -> torch.Tensor:
|
|
86
|
+
"""Alias for :func:`sparse_depth`."""
|
|
87
|
+
return torch.from_numpy(load_sparse_depth_array(path)).contiguous()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def point_cloud(
|
|
91
|
+
path: Union[str, BinaryIO],
|
|
92
|
+
meta: dict[str, Any] | None = None,
|
|
93
|
+
*,
|
|
94
|
+
attributes: dict[str, Any] | None = None,
|
|
95
|
+
) -> torch.Tensor:
|
|
96
|
+
"""Alias for :func:`sparse_depth`."""
|
|
97
|
+
return torch.from_numpy(load_sparse_depth_array(path)).contiguous()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@modality_meta(
|
|
101
|
+
modality_type="intrinsics",
|
|
102
|
+
dtype="float32",
|
|
103
|
+
hierarchical=True,
|
|
104
|
+
shape="3x3",
|
|
105
|
+
file_formats=[".json"],
|
|
106
|
+
meta={
|
|
107
|
+
"dataset": "SeeingThroughFog",
|
|
108
|
+
"camera_frame": DEFAULT_CAMERA_FRAME,
|
|
109
|
+
"source": "calib_cam_stereo_left",
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
def read_intrinsics(
|
|
113
|
+
path: Union[str, BinaryIO],
|
|
114
|
+
meta: dict[str, Any] | None = None,
|
|
115
|
+
*,
|
|
116
|
+
attributes: dict[str, Any] | None = None,
|
|
117
|
+
) -> torch.Tensor:
|
|
118
|
+
"""Load the left stereo camera intrinsics matrix ``K``."""
|
|
119
|
+
return torch.from_numpy(load_intrinsics_array(path)).contiguous()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@modality_meta(
|
|
123
|
+
modality_type="camera_extrinsics",
|
|
124
|
+
dtype="float32",
|
|
125
|
+
hierarchical=True,
|
|
126
|
+
shape="4x4",
|
|
127
|
+
file_formats=[".json"],
|
|
128
|
+
meta={
|
|
129
|
+
"dataset": "SeeingThroughFog",
|
|
130
|
+
"default_source_frame": DEFAULT_LIDAR_FRAME,
|
|
131
|
+
"default_target_frame": DEFAULT_CAMERA_FRAME,
|
|
132
|
+
"transform_direction": "source_frame_to_target_frame",
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
def read_extrinsics(
|
|
136
|
+
path: Union[str, BinaryIO],
|
|
137
|
+
meta: dict[str, Any] | None = None,
|
|
138
|
+
*,
|
|
139
|
+
attributes: dict[str, Any] | None = None,
|
|
140
|
+
) -> torch.Tensor:
|
|
141
|
+
"""Load the default ``lidar_hdl64_s3_roof`` to left-camera transform."""
|
|
142
|
+
return torch.from_numpy(load_extrinsics_array(path, meta, attributes)).contiguous()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Loader functions for the Princeton DENSE / SeeingThroughFog dataset.
|
|
2
|
+
|
|
3
|
+
This module re-exports the GPU-oriented loaders from
|
|
4
|
+
:mod:`euler_loading.loaders.gpu.princeton_dense` for convenience. For
|
|
5
|
+
explicit control, import from :mod:`~euler_loading.loaders.gpu` or
|
|
6
|
+
:mod:`~euler_loading.loaders.cpu` directly::
|
|
7
|
+
|
|
8
|
+
from euler_loading.loaders.gpu import princeton_dense # torch tensors
|
|
9
|
+
from euler_loading.loaders.cpu import princeton_dense # numpy arrays
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from euler_loading.loaders.gpu.princeton_dense import * # noqa: F401,F403
|
|
@@ -11,19 +11,23 @@ import pytest
|
|
|
11
11
|
import torch
|
|
12
12
|
from PIL import Image
|
|
13
13
|
|
|
14
|
+
from euler_loading import resolve_loader_module
|
|
14
15
|
from euler_loading.loaders import vkitti2
|
|
15
16
|
from euler_loading.loaders import generic as generic_top
|
|
16
17
|
from euler_loading.loaders import muses as muses_top
|
|
18
|
+
from euler_loading.loaders import princeton_dense as princeton_dense_top
|
|
17
19
|
from euler_loading.loaders.gpu import vkitti2 as gpu_vkitti2
|
|
18
20
|
from euler_loading.loaders.gpu import real_drive_sim as gpu_rds
|
|
19
21
|
from euler_loading.loaders.gpu import generic as gpu_generic
|
|
20
22
|
from euler_loading.loaders.gpu import generic_dense_depth as gpu_generic_dense_depth
|
|
21
23
|
from euler_loading.loaders.gpu import muses as gpu_muses
|
|
24
|
+
from euler_loading.loaders.gpu import princeton_dense as gpu_princeton_dense
|
|
22
25
|
from euler_loading.loaders.cpu import vkitti2 as cpu_vkitti2
|
|
23
26
|
from euler_loading.loaders.cpu import real_drive_sim as cpu_rds
|
|
24
27
|
from euler_loading.loaders.cpu import generic as cpu_generic
|
|
25
28
|
from euler_loading.loaders.cpu import generic_dense_depth as cpu_generic_dense_depth
|
|
26
29
|
from euler_loading.loaders.cpu import muses as cpu_muses
|
|
30
|
+
from euler_loading.loaders.cpu import princeton_dense as cpu_princeton_dense
|
|
27
31
|
|
|
28
32
|
# ---------------------------------------------------------------------------
|
|
29
33
|
# Shared fixtures
|
|
@@ -149,6 +153,214 @@ class TestGenericDenseDepthAttributes:
|
|
|
149
153
|
np.testing.assert_allclose(result, expected)
|
|
150
154
|
|
|
151
155
|
|
|
156
|
+
# ---------------------------------------------------------------------------
|
|
157
|
+
# Princeton DENSE / SeeingThroughFog loader smoke tests
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
PRINCETON_DENSE_LOADER_NAMES = [
|
|
161
|
+
"rgb",
|
|
162
|
+
"sparse_depth",
|
|
163
|
+
"read_intrinsics",
|
|
164
|
+
"read_extrinsics",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.fixture()
|
|
169
|
+
def princeton_dense_rgb_path(tmp_path):
|
|
170
|
+
arr = np.full((4, 4), 2048, dtype=np.uint16)
|
|
171
|
+
path = tmp_path / "2018-02-05_12-09-01_00000.tiff"
|
|
172
|
+
Image.fromarray(arr).save(path)
|
|
173
|
+
return str(path)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@pytest.fixture()
|
|
177
|
+
def princeton_dense_sparse_depth_path(tmp_path):
|
|
178
|
+
arr = np.array(
|
|
179
|
+
[
|
|
180
|
+
[1.0, 2.0, 3.0, 128.0, 12.0],
|
|
181
|
+
[4.0, 5.0, 6.0, 255.0, 63.0],
|
|
182
|
+
],
|
|
183
|
+
dtype=np.float32,
|
|
184
|
+
)
|
|
185
|
+
path = tmp_path / "2018-02-06_15-48-12_00200.bin"
|
|
186
|
+
arr.tofile(path)
|
|
187
|
+
return str(path), arr
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
@pytest.fixture()
|
|
191
|
+
def princeton_dense_intrinsics_path(tmp_path):
|
|
192
|
+
data = {
|
|
193
|
+
"K": [2612.86, 0.0, 966.525, 0.0, 2612.86, 508.297, 0.0, 0.0, 1.0],
|
|
194
|
+
"D": [-0.567575, 0.0, 0.0, 0.0, 0.0],
|
|
195
|
+
"R": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
|
|
196
|
+
"P": [2355.722801, 0.0, 988.138054, 0.0, 0.0, 2355.722801, 508.051838, 0.0, 0.0, 0.0, 1.0, 0.0],
|
|
197
|
+
"width": 1920,
|
|
198
|
+
"height": 1024,
|
|
199
|
+
"header": {"frame_id": "cam_stereo_left_optical"},
|
|
200
|
+
}
|
|
201
|
+
path = tmp_path / "calib_cam_stereo_left.json"
|
|
202
|
+
path.write_text(json.dumps(data))
|
|
203
|
+
return str(path), np.asarray(data["K"], dtype=np.float32).reshape(3, 3)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@pytest.fixture()
|
|
207
|
+
def princeton_dense_tf_tree_path(tmp_path):
|
|
208
|
+
data = [
|
|
209
|
+
{
|
|
210
|
+
"header": {"frame_id": "body"},
|
|
211
|
+
"child_frame_id": "lidar_hdl64_s3_roof",
|
|
212
|
+
"transform": {
|
|
213
|
+
"translation": {"x": 1.0, "y": 0.0, "z": 0.0},
|
|
214
|
+
"rotation": {"x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"header": {"frame_id": "body"},
|
|
219
|
+
"child_frame_id": "cam_stereo_left_optical",
|
|
220
|
+
"transform": {
|
|
221
|
+
"translation": {"x": 0.0, "y": 2.0, "z": 0.0},
|
|
222
|
+
"rotation": {"x": 0.0, "y": 0.0, "z": 0.0, "w": 1.0},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
]
|
|
226
|
+
path = tmp_path / "calib_tf_tree_full.json"
|
|
227
|
+
path.write_text(json.dumps(data))
|
|
228
|
+
expected = np.asarray(
|
|
229
|
+
[
|
|
230
|
+
[1.0, 0.0, 0.0, 1.0],
|
|
231
|
+
[0.0, 1.0, 0.0, -2.0],
|
|
232
|
+
[0.0, 0.0, 1.0, 0.0],
|
|
233
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
234
|
+
],
|
|
235
|
+
dtype=np.float32,
|
|
236
|
+
)
|
|
237
|
+
return str(path), expected
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class TestPrincetonDenseModuleContents:
|
|
241
|
+
"""The Princeton DENSE module exposes the expected loader functions."""
|
|
242
|
+
|
|
243
|
+
@pytest.mark.parametrize("name", PRINCETON_DENSE_LOADER_NAMES)
|
|
244
|
+
def test_top_level_has_callable(self, name):
|
|
245
|
+
assert callable(getattr(princeton_dense_top, name))
|
|
246
|
+
|
|
247
|
+
@pytest.mark.parametrize("name", PRINCETON_DENSE_LOADER_NAMES)
|
|
248
|
+
def test_gpu_has_callable(self, name):
|
|
249
|
+
assert callable(getattr(gpu_princeton_dense, name))
|
|
250
|
+
|
|
251
|
+
@pytest.mark.parametrize("name", PRINCETON_DENSE_LOADER_NAMES)
|
|
252
|
+
def test_cpu_has_callable(self, name):
|
|
253
|
+
assert callable(getattr(cpu_princeton_dense, name))
|
|
254
|
+
|
|
255
|
+
def test_auto_resolution_uses_gpu_module(self):
|
|
256
|
+
module = resolve_loader_module("princeton_dense")
|
|
257
|
+
assert module.__name__ == "euler_loading.loaders.gpu.princeton_dense"
|
|
258
|
+
|
|
259
|
+
def test_auto_resolution_supports_dataset_alias(self):
|
|
260
|
+
module = resolve_loader_module("seeing_through_fog")
|
|
261
|
+
assert module.__name__ == "euler_loading.loaders.gpu.princeton_dense"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class TestPrincetonDenseGPULoaders:
|
|
265
|
+
"""GPU Princeton DENSE loaders produce torch tensors."""
|
|
266
|
+
|
|
267
|
+
def test_rgb_shape_dtype_and_range(self, princeton_dense_rgb_path):
|
|
268
|
+
result = gpu_princeton_dense.rgb(princeton_dense_rgb_path)
|
|
269
|
+
assert isinstance(result, torch.Tensor)
|
|
270
|
+
assert result.dtype == torch.float32
|
|
271
|
+
assert result.shape == (3, 4, 4)
|
|
272
|
+
assert result.min() >= 0.0
|
|
273
|
+
assert result.max() <= 1.0
|
|
274
|
+
|
|
275
|
+
def test_sparse_depth_preserves_float32_columns(self, princeton_dense_sparse_depth_path):
|
|
276
|
+
path, expected = princeton_dense_sparse_depth_path
|
|
277
|
+
result = gpu_princeton_dense.sparse_depth(path)
|
|
278
|
+
assert result.dtype == torch.float32
|
|
279
|
+
assert result.shape == (2, 5)
|
|
280
|
+
assert torch.equal(result, torch.from_numpy(expected))
|
|
281
|
+
|
|
282
|
+
def test_read_intrinsics_returns_camera_k(self, princeton_dense_intrinsics_path):
|
|
283
|
+
path, expected = princeton_dense_intrinsics_path
|
|
284
|
+
result = gpu_princeton_dense.read_intrinsics(path)
|
|
285
|
+
assert result.dtype == torch.float32
|
|
286
|
+
assert result.shape == (3, 3)
|
|
287
|
+
assert torch.equal(result, torch.from_numpy(expected))
|
|
288
|
+
|
|
289
|
+
def test_read_extrinsics_maps_hdl64_lidar_to_camera(self, princeton_dense_tf_tree_path):
|
|
290
|
+
path, expected = princeton_dense_tf_tree_path
|
|
291
|
+
result = gpu_princeton_dense.read_extrinsics(path)
|
|
292
|
+
assert result.dtype == torch.float32
|
|
293
|
+
assert result.shape == (4, 4)
|
|
294
|
+
assert torch.equal(result, torch.from_numpy(expected))
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class TestPrincetonDenseCPULoaders:
|
|
298
|
+
"""CPU Princeton DENSE loaders produce numpy arrays."""
|
|
299
|
+
|
|
300
|
+
def test_rgb_shape_dtype_and_range(self, princeton_dense_rgb_path):
|
|
301
|
+
result = cpu_princeton_dense.rgb(princeton_dense_rgb_path)
|
|
302
|
+
assert isinstance(result, np.ndarray)
|
|
303
|
+
assert result.dtype == np.float32
|
|
304
|
+
assert result.shape == (4, 4, 3)
|
|
305
|
+
assert result.min() >= 0.0
|
|
306
|
+
assert result.max() <= 1.0
|
|
307
|
+
|
|
308
|
+
def test_sparse_depth_preserves_float32_columns(self, princeton_dense_sparse_depth_path):
|
|
309
|
+
path, expected = princeton_dense_sparse_depth_path
|
|
310
|
+
result = cpu_princeton_dense.sparse_depth(path)
|
|
311
|
+
assert result.dtype == np.float32
|
|
312
|
+
assert result.shape == (2, 5)
|
|
313
|
+
assert np.array_equal(result, expected)
|
|
314
|
+
|
|
315
|
+
def test_sparse_depth_supports_binary_streams(self, princeton_dense_sparse_depth_path):
|
|
316
|
+
_, expected = princeton_dense_sparse_depth_path
|
|
317
|
+
stream = io.BytesIO(expected.tobytes())
|
|
318
|
+
result = cpu_princeton_dense.sparse_depth(stream)
|
|
319
|
+
assert np.array_equal(result, expected)
|
|
320
|
+
|
|
321
|
+
def test_read_intrinsics_returns_camera_k(self, princeton_dense_intrinsics_path):
|
|
322
|
+
path, expected = princeton_dense_intrinsics_path
|
|
323
|
+
result = cpu_princeton_dense.read_intrinsics(path)
|
|
324
|
+
assert result.dtype == np.float32
|
|
325
|
+
assert result.shape == (3, 3)
|
|
326
|
+
assert np.array_equal(result, expected)
|
|
327
|
+
|
|
328
|
+
def test_read_extrinsics_maps_hdl64_lidar_to_camera(self, princeton_dense_tf_tree_path):
|
|
329
|
+
path, expected = princeton_dense_tf_tree_path
|
|
330
|
+
result = cpu_princeton_dense.read_extrinsics(path)
|
|
331
|
+
assert result.dtype == np.float32
|
|
332
|
+
assert result.shape == (4, 4)
|
|
333
|
+
assert np.array_equal(result, expected)
|
|
334
|
+
|
|
335
|
+
def test_read_extrinsics_supports_frame_overrides(self, princeton_dense_tf_tree_path):
|
|
336
|
+
path, expected_lidar_to_camera = princeton_dense_tf_tree_path
|
|
337
|
+
result = cpu_princeton_dense.read_extrinsics(
|
|
338
|
+
path,
|
|
339
|
+
attributes={
|
|
340
|
+
"source_frame": "cam_stereo_left_optical",
|
|
341
|
+
"target_frame": "lidar_hdl64_s3_roof",
|
|
342
|
+
},
|
|
343
|
+
)
|
|
344
|
+
assert np.allclose(result, np.linalg.inv(expected_lidar_to_camera))
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class TestPrincetonDenseBackwardCompat:
|
|
348
|
+
"""``from euler_loading.loaders import princeton_dense`` returns GPU loaders."""
|
|
349
|
+
|
|
350
|
+
def test_top_level_rgb_matches_gpu(self, princeton_dense_rgb_path):
|
|
351
|
+
assert torch.equal(
|
|
352
|
+
princeton_dense_top.rgb(princeton_dense_rgb_path),
|
|
353
|
+
gpu_princeton_dense.rgb(princeton_dense_rgb_path),
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
def test_top_level_sparse_depth_matches_gpu(self, princeton_dense_sparse_depth_path):
|
|
357
|
+
path, _ = princeton_dense_sparse_depth_path
|
|
358
|
+
assert torch.equal(
|
|
359
|
+
princeton_dense_top.sparse_depth(path),
|
|
360
|
+
gpu_princeton_dense.sparse_depth(path),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
152
364
|
# ---------------------------------------------------------------------------
|
|
153
365
|
# MUSES loader smoke tests
|
|
154
366
|
# ---------------------------------------------------------------------------
|
|
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.12.0 → euler_loading-2.14.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
|
{euler_loading-2.12.0 → euler_loading-2.14.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
|