euler-loading 2.14.0__tar.gz → 2.17.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.
Files changed (57) hide show
  1. {euler_loading-2.14.0 → euler_loading-2.17.0}/PKG-INFO +1 -1
  2. {euler_loading-2.14.0 → euler_loading-2.17.0}/README.md +10 -2
  3. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/cpu/generic.py +33 -0
  4. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/cpu/princeton_dense.py +22 -3
  5. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/generate/loaders.json +45 -8
  6. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/gpu/generic.py +31 -0
  7. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/gpu/princeton_dense.py +24 -3
  8. {euler_loading-2.14.0 → euler_loading-2.17.0}/package-lock.json +1 -1
  9. {euler_loading-2.14.0 → euler_loading-2.17.0}/pyproject.toml +1 -1
  10. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/test_loaders.py +113 -0
  11. {euler_loading-2.14.0 → euler_loading-2.17.0}/.github/workflows/workflow.yml +0 -0
  12. {euler_loading-2.14.0 → euler_loading-2.17.0}/.gitignore +0 -0
  13. {euler_loading-2.14.0 → euler_loading-2.17.0}/docs/loader-attributes.md +0 -0
  14. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/__init__.py +0 -0
  15. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/_dataset_contract.py +0 -0
  16. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/_ds_crawler_utils.py +0 -0
  17. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/_metadata.py +0 -0
  18. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/_resolution.py +0 -0
  19. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/_writing.py +0 -0
  20. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/dataset.py +0 -0
  21. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/indexing.py +0 -0
  22. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/__init__.py +0 -0
  23. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/_annotations.py +0 -0
  24. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/_princeton_dense.py +0 -0
  25. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/_writer_utils.py +0 -0
  26. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/contracts.py +0 -0
  27. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/cpu/__init__.py +0 -0
  28. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
  29. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/cpu/muses.py +0 -0
  30. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
  31. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
  32. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/generate/__init__.py +0 -0
  33. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/generate/__main__.py +0 -0
  34. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/generic.py +0 -0
  35. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/gpu/__init__.py +0 -0
  36. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
  37. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/gpu/muses.py +0 -0
  38. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
  39. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
  40. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/muses.py +0 -0
  41. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/princeton_dense.py +0 -0
  42. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/real_drive_sim.py +0 -0
  43. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/loaders/vkitti2.py +0 -0
  44. {euler_loading-2.14.0 → euler_loading-2.17.0}/euler_loading/preprocessing.py +0 -0
  45. {euler_loading-2.14.0 → euler_loading-2.17.0}/example.py +0 -0
  46. {euler_loading-2.14.0 → euler_loading-2.17.0}/sample_rds.py +0 -0
  47. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/__init__.py +0 -0
  48. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/conftest.py +0 -0
  49. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/example_rds_calib.json +0 -0
  50. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/test_dataset.py +0 -0
  51. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/test_id_schema.py +0 -0
  52. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/test_indexing.py +0 -0
  53. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/test_preprocessing.py +0 -0
  54. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/test_real_dataset.py +0 -0
  55. {euler_loading-2.14.0 → euler_loading-2.17.0}/tests/test_writing.py +0 -0
  56. {euler_loading-2.14.0 → euler_loading-2.17.0}/vkitti_cpu_example_output.json +0 -0
  57. {euler_loading-2.14.0 → euler_loading-2.17.0}/vkitti_gpu_example_output.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-loading
3
- Version: 2.14.0
3
+ Version: 2.17.0
4
4
  Summary: Multi-modal PyTorch dataloader using ds-crawler indices
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: ds-crawler>=2.3.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`, `princeton_dense`, 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`, `generic`, 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
 
@@ -550,7 +550,15 @@ A format-agnostic loader that infers the loading strategy from the file extensio
550
550
  | `sky_mask` | Binary mask by comparing pixels against `meta["sky_mask"]` (`[R, G, B]`). Requires `meta` |
551
551
  | `read_intrinsics` | Returns `meta["intrinsics"]` as a `(3, 3)` tensor. Ignores path; requires `meta` |
552
552
 
553
- CPU variants of all loaders live under `euler_loading.loaders.cpu.{vkitti2,real_drive_sim,muses,princeton_dense,generic_dense_depth}`.
553
+ ### Generic NumPy Modalities (`euler_loading.loaders.gpu.generic`)
554
+
555
+ Generic loaders for `.npy` and `.npz` modalities without dataset-specific decoding.
556
+
557
+ | Function | Description |
558
+ |----------|-------------|
559
+ | `points_3d` | Dense 3D point map from NumPy files stored as `(3, H, W)` float32 |
560
+
561
+ CPU variants of all loaders live under `euler_loading.loaders.cpu.{vkitti2,real_drive_sim,muses,princeton_dense,generic,generic_dense_depth}`.
554
562
 
555
563
  ### Flattening hierarchical modalities
556
564
 
@@ -12,6 +12,7 @@ Return types
12
12
  ------------
13
13
  - **map_2d** / **scattering_coefficient** -- ``np.ndarray`` of shape ``(H, W)`` float32.
14
14
  - **map_3d** / **atmospheric_light** -- ``np.ndarray`` of shape ``(H, W, C)`` float32.
15
+ - **points_3d** -- ``np.ndarray`` of shape ``(3, H, W)`` float32.
15
16
  - **spherical_map** -- ``np.ndarray`` of shape ``(H, W, C)`` float32.
16
17
  - **intrinsics** -- ``np.ndarray`` of shape ``(3, 3)`` float32.
17
18
  - **sh_coeffs** -- ``np.ndarray`` of shape ``(N, 3)`` float32.
@@ -23,6 +24,7 @@ Usage::
23
24
 
24
25
  Modality("/data/my_dataset/scattering_coefficient", loader=generic.scattering_coefficient)
25
26
  Modality("/data/my_dataset/atmospheric_light", loader=generic.atmospheric_light)
27
+ Modality("/data/my_dataset/points_3d", loader=generic.points_3d)
26
28
  Modality("/data/my_dataset/spherical_map", loader=generic.spherical_map)
27
29
  Modality("/data/my_dataset/intrinsics", loader=generic.intrinsics)
28
30
  Modality("/data/my_dataset/sh_coeffs", loader=generic.sh_coeffs)
@@ -85,6 +87,14 @@ def _write_numpy(path: Union[str, BinaryIO], value: Any) -> None:
85
87
  raise ValueError(f"Unsupported output extension: {ext}")
86
88
 
87
89
 
90
+ def _as_3d_points(value: Any) -> np.ndarray:
91
+ """Return a float32 ``(3, H, W)`` array for dense 3D point maps."""
92
+ arr = to_numpy(value).astype(np.float32)
93
+ if arr.ndim != 3 or arr.shape[0] != 3:
94
+ raise ValueError(f"points_3d must have shape (3, H, W), got {arr.shape}")
95
+ return arr
96
+
97
+
88
98
  # ---------------------------------------------------------------------------
89
99
  # Public loaders
90
100
  # ---------------------------------------------------------------------------
@@ -124,6 +134,23 @@ def map_3d(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, at
124
134
  return np.transpose(arr, (1, 2, 0))
125
135
 
126
136
 
137
+ @modality_meta(
138
+ modality_type="points_3d",
139
+ dtype="float32",
140
+ shape="3HW",
141
+ file_formats=[".npy", ".npz"],
142
+ )
143
+ def points_3d(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, attributes: dict[str, Any] | None = None) -> np.ndarray:
144
+ """Load dense 3D points as a ``(3, H, W)`` float32 array.
145
+
146
+ Files are expected to store the per-pixel 3D point coordinates directly in
147
+ ``(3, H, W)`` layout. Unlike :func:`map_3d`, this loader preserves the
148
+ channel-first representation on CPU because it is a coordinate point map,
149
+ not a generic HWC image-style map.
150
+ """
151
+ return _as_3d_points(_load_numpy(path))
152
+
153
+
127
154
  @modality_meta(
128
155
  modality_type="scattering_coefficient",
129
156
  dtype="float32",
@@ -239,6 +266,12 @@ def write_map_3d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] |
239
266
  raise ValueError(f"Unsupported map_3d output extension: {ext}")
240
267
 
241
268
 
269
+ @mark_stream_supported
270
+ def write_points_3d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
271
+ """Write dense 3D points to NumPy formats in ``(3, H, W)`` layout."""
272
+ _write_numpy(path, _as_3d_points(value))
273
+
274
+
242
275
  @mark_stream_supported
243
276
  def write_scattering_coefficient(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
244
277
  """Write a scattering-coefficient map (delegates to :func:`write_map_2d`)."""
@@ -5,6 +5,7 @@ images and Velodyne lidar point clouds as binary ``float32`` records.
5
5
 
6
6
  Return types
7
7
  ------------
8
+ - **rccb** -- ``np.ndarray`` of shape ``(H, W, 3)`` float32 in ``[0, 1]``.
8
9
  - **rgb** -- ``np.ndarray`` of shape ``(H, W, 3)`` float32 in ``[0, 1]``.
9
10
  - **sparse_depth** -- ``np.ndarray`` of shape ``(N, 5)`` float32 with columns
10
11
  ``x, y, z, intensity, ring``.
@@ -20,6 +21,7 @@ from __future__ import annotations
20
21
  from typing import Any, BinaryIO, Union
21
22
 
22
23
  import numpy as np
24
+ from PIL import Image
23
25
 
24
26
  from euler_loading.loaders._annotations import modality_meta
25
27
  from euler_loading.loaders._princeton_dense import DEFAULT_CAMERA_FRAME
@@ -32,7 +34,7 @@ from euler_loading.loaders._princeton_dense import load_sparse_depth_array
32
34
 
33
35
 
34
36
  @modality_meta(
35
- modality_type="rgb",
37
+ modality_type="rccb",
36
38
  dtype="float32",
37
39
  shape="HWC",
38
40
  file_formats=[".tif", ".tiff"],
@@ -44,16 +46,33 @@ from euler_loading.loaders._princeton_dense import load_sparse_depth_array
44
46
  "raw_max_value": 4095.0,
45
47
  },
46
48
  )
47
- def rgb(
49
+ def rccb(
48
50
  path: Union[str, BinaryIO],
49
51
  meta: dict[str, Any] | None = None,
50
52
  *,
51
53
  attributes: dict[str, Any] | None = None,
52
54
  ) -> np.ndarray:
53
- """Load a SeeingThroughFog 12-bit Bayer TIFF as RGB float32 in ``[0, 1]``."""
55
+ """Load a SeeingThroughFog 12-bit Bayer TIFF as RCCB float32 in ``[0, 1]``."""
54
56
  return load_rgb_array(path, meta, attributes)
55
57
 
56
58
 
59
+ @modality_meta(
60
+ modality_type="rgb",
61
+ dtype="float32",
62
+ shape="HWC",
63
+ file_formats=[".png"],
64
+ output_range=[0.0, 1.0],
65
+ )
66
+ def rgb(
67
+ path: Union[str, BinaryIO],
68
+ meta: dict[str, Any] | None = None,
69
+ *,
70
+ attributes: dict[str, Any] | None = None,
71
+ ) -> np.ndarray:
72
+ """Load a SeeingThroughFog plain 8-bit PNG as RGB float32 in ``[0, 1]``."""
73
+ return np.array(Image.open(path).convert("RGB"), dtype=np.float32) / 255.0
74
+
75
+
57
76
  @modality_meta(
58
77
  modality_type="sparse_depth",
59
78
  dtype="float32",
@@ -71,6 +71,23 @@
71
71
  "shape": "CHW"
72
72
  }
73
73
  },
74
+ {
75
+ "type": "points_3d",
76
+ "function": "points_3d",
77
+ "hierarchical": false,
78
+ "dtype": "float32",
79
+ "access": "key",
80
+ "file_formats": [
81
+ ".npy",
82
+ ".npz"
83
+ ],
84
+ "cpu": {
85
+ "shape": "3HW"
86
+ },
87
+ "gpu": {
88
+ "shape": "3HW"
89
+ }
90
+ },
74
91
  {
75
92
  "type": "scattering_coefficient",
76
93
  "function": "scattering_coefficient",
@@ -456,6 +473,33 @@
456
473
  {
457
474
  "name": "princeton_dense",
458
475
  "modalities": [
476
+ {
477
+ "type": "rccb",
478
+ "function": "rccb",
479
+ "hierarchical": false,
480
+ "dtype": "float32",
481
+ "access": "key",
482
+ "output_range": [
483
+ 0.0,
484
+ 1.0
485
+ ],
486
+ "file_formats": [
487
+ ".tif",
488
+ ".tiff"
489
+ ],
490
+ "meta": {
491
+ "dataset": "SeeingThroughFog",
492
+ "raw_bit_depth": 12,
493
+ "bayer_pattern": "GBRG",
494
+ "raw_max_value": 4095.0
495
+ },
496
+ "cpu": {
497
+ "shape": "HWC"
498
+ },
499
+ "gpu": {
500
+ "shape": "CHW"
501
+ }
502
+ },
459
503
  {
460
504
  "type": "camera_extrinsics",
461
505
  "function": "read_extrinsics",
@@ -510,15 +554,8 @@
510
554
  1.0
511
555
  ],
512
556
  "file_formats": [
513
- ".tif",
514
- ".tiff"
557
+ ".png"
515
558
  ],
516
- "meta": {
517
- "dataset": "SeeingThroughFog",
518
- "raw_bit_depth": 12,
519
- "bayer_pattern": "GBRG",
520
- "raw_max_value": 4095.0
521
- },
522
559
  "cpu": {
523
560
  "shape": "HWC"
524
561
  },
@@ -12,6 +12,7 @@ Return types
12
12
  ------------
13
13
  - **map_2d** / **scattering_coefficient** -- ``torch.FloatTensor`` of shape ``(H, W)``.
14
14
  - **map_3d** / **atmospheric_light** -- ``torch.FloatTensor`` of shape ``(C, H, W)``.
15
+ - **points_3d** -- ``torch.FloatTensor`` of shape ``(3, H, W)``.
15
16
  - **spherical_map** -- ``torch.FloatTensor`` of shape ``(C, H, W)``.
16
17
  - **intrinsics** -- ``torch.FloatTensor`` of shape ``(3, 3)``.
17
18
  - **sh_coeffs** -- ``torch.FloatTensor`` of shape ``(N, 3)``.
@@ -23,6 +24,7 @@ Usage::
23
24
 
24
25
  Modality("/data/my_dataset/scattering_coefficient", loader=generic.scattering_coefficient)
25
26
  Modality("/data/my_dataset/atmospheric_light", loader=generic.atmospheric_light)
27
+ Modality("/data/my_dataset/points_3d", loader=generic.points_3d)
26
28
  Modality("/data/my_dataset/spherical_map", loader=generic.spherical_map)
27
29
  Modality("/data/my_dataset/intrinsics", loader=generic.intrinsics)
28
30
  Modality("/data/my_dataset/sh_coeffs", loader=generic.sh_coeffs)
@@ -86,6 +88,14 @@ def _write_numpy(path: Union[str, BinaryIO], value: Any) -> None:
86
88
  raise ValueError(f"Unsupported output extension: {ext}")
87
89
 
88
90
 
91
+ def _as_3d_points(value: Any) -> np.ndarray:
92
+ """Return a float32 ``(3, H, W)`` array for dense 3D point maps."""
93
+ arr = to_numpy(value).astype(np.float32)
94
+ if arr.ndim != 3 or arr.shape[0] != 3:
95
+ raise ValueError(f"points_3d must have shape (3, H, W), got {arr.shape}")
96
+ return arr
97
+
98
+
89
99
  # ---------------------------------------------------------------------------
90
100
  # Public loaders
91
101
  # ---------------------------------------------------------------------------
@@ -125,6 +135,21 @@ def map_3d(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, at
125
135
  return torch.from_numpy(arr).contiguous()
126
136
 
127
137
 
138
+ @modality_meta(
139
+ modality_type="points_3d",
140
+ dtype="float32",
141
+ shape="3HW",
142
+ file_formats=[".npy", ".npz"],
143
+ )
144
+ def points_3d(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None, *, attributes: dict[str, Any] | None = None) -> torch.Tensor:
145
+ """Load dense 3D points as a ``(3, H, W)`` float32 tensor.
146
+
147
+ Files are expected to store the per-pixel 3D point coordinates directly in
148
+ ``(3, H, W)`` layout.
149
+ """
150
+ return torch.from_numpy(_as_3d_points(_load_numpy(path))).contiguous()
151
+
152
+
128
153
  @modality_meta(
129
154
  modality_type="scattering_coefficient",
130
155
  dtype="float32",
@@ -229,6 +254,12 @@ def write_map_3d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] |
229
254
  _write_numpy(path, value)
230
255
 
231
256
 
257
+ @mark_stream_supported
258
+ def write_points_3d(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
259
+ """Write dense 3D points to NumPy formats in ``(3, H, W)`` layout."""
260
+ _write_numpy(path, _as_3d_points(value))
261
+
262
+
232
263
  @mark_stream_supported
233
264
  def write_scattering_coefficient(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
234
265
  """Write a scattering-coefficient map (delegates to :func:`write_map_2d`)."""
@@ -5,6 +5,7 @@ images and Velodyne lidar point clouds as binary ``float32`` records.
5
5
 
6
6
  Return types
7
7
  ------------
8
+ - **rccb** -- ``torch.FloatTensor`` of shape ``(3, H, W)`` in ``[0, 1]``.
8
9
  - **rgb** -- ``torch.FloatTensor`` of shape ``(3, H, W)`` in ``[0, 1]``.
9
10
  - **sparse_depth** -- ``torch.FloatTensor`` of shape ``(N, 5)`` with columns
10
11
  ``x, y, z, intensity, ring``.
@@ -19,6 +20,8 @@ from __future__ import annotations
19
20
 
20
21
  from typing import Any, BinaryIO, Union
21
22
 
23
+ import numpy as np
24
+ from PIL import Image
22
25
  import torch
23
26
 
24
27
  from euler_loading.loaders._annotations import modality_meta
@@ -32,7 +35,7 @@ from euler_loading.loaders._princeton_dense import load_sparse_depth_array
32
35
 
33
36
 
34
37
  @modality_meta(
35
- modality_type="rgb",
38
+ modality_type="rccb",
36
39
  dtype="float32",
37
40
  shape="CHW",
38
41
  file_formats=[".tif", ".tiff"],
@@ -44,17 +47,35 @@ from euler_loading.loaders._princeton_dense import load_sparse_depth_array
44
47
  "raw_max_value": 4095.0,
45
48
  },
46
49
  )
47
- def rgb(
50
+ def rccb(
48
51
  path: Union[str, BinaryIO],
49
52
  meta: dict[str, Any] | None = None,
50
53
  *,
51
54
  attributes: dict[str, Any] | None = None,
52
55
  ) -> torch.Tensor:
53
- """Load a SeeingThroughFog 12-bit Bayer TIFF as an RGB tensor."""
56
+ """Load a SeeingThroughFog 12-bit Bayer TIFF as an RCCB tensor."""
54
57
  arr = load_rgb_array(path, meta, attributes)
55
58
  return torch.from_numpy(arr).permute(2, 0, 1).contiguous()
56
59
 
57
60
 
61
+ @modality_meta(
62
+ modality_type="rgb",
63
+ dtype="float32",
64
+ shape="CHW",
65
+ file_formats=[".png"],
66
+ output_range=[0.0, 1.0],
67
+ )
68
+ def rgb(
69
+ path: Union[str, BinaryIO],
70
+ meta: dict[str, Any] | None = None,
71
+ *,
72
+ attributes: dict[str, Any] | None = None,
73
+ ) -> torch.Tensor:
74
+ """Load a SeeingThroughFog plain 8-bit PNG as an RGB tensor."""
75
+ arr = np.array(Image.open(path).convert("RGB"), dtype=np.float32) / 255.0
76
+ return torch.from_numpy(arr).permute(2, 0, 1).contiguous()
77
+
78
+
58
79
  @modality_meta(
59
80
  modality_type="sparse_depth",
60
81
  dtype="float32",
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "loading-dense",
2
+ "name": "euler-loading-3d",
3
3
  "lockfileVersion": 3,
4
4
  "requires": true,
5
5
  "packages": {}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "euler-loading"
3
- version = "2.14.0"
3
+ version = "2.17.0"
4
4
  description = "Multi-modal PyTorch dataloader using ds-crawler indices"
5
5
  requires-python = ">=3.9"
6
6
  dependencies = [
@@ -159,6 +159,7 @@ class TestGenericDenseDepthAttributes:
159
159
 
160
160
  PRINCETON_DENSE_LOADER_NAMES = [
161
161
  "rgb",
162
+ "rccb",
162
163
  "sparse_depth",
163
164
  "read_intrinsics",
164
165
  "read_extrinsics",
@@ -167,6 +168,14 @@ PRINCETON_DENSE_LOADER_NAMES = [
167
168
 
168
169
  @pytest.fixture()
169
170
  def princeton_dense_rgb_path(tmp_path):
171
+ arr = np.full((4, 4, 3), 128, dtype=np.uint8)
172
+ path = tmp_path / "2018-02-05_12-09-01_00000.png"
173
+ Image.fromarray(arr).save(path)
174
+ return str(path)
175
+
176
+
177
+ @pytest.fixture()
178
+ def princeton_dense_rccb_path(tmp_path):
170
179
  arr = np.full((4, 4), 2048, dtype=np.uint16)
171
180
  path = tmp_path / "2018-02-05_12-09-01_00000.tiff"
172
181
  Image.fromarray(arr).save(path)
@@ -264,6 +273,14 @@ class TestPrincetonDenseModuleContents:
264
273
  class TestPrincetonDenseGPULoaders:
265
274
  """GPU Princeton DENSE loaders produce torch tensors."""
266
275
 
276
+ def test_rccb_shape_dtype_and_range(self, princeton_dense_rccb_path):
277
+ result = gpu_princeton_dense.rccb(princeton_dense_rccb_path)
278
+ assert isinstance(result, torch.Tensor)
279
+ assert result.dtype == torch.float32
280
+ assert result.shape == (3, 4, 4)
281
+ assert result.min() >= 0.0
282
+ assert result.max() <= 1.0
283
+
267
284
  def test_rgb_shape_dtype_and_range(self, princeton_dense_rgb_path):
268
285
  result = gpu_princeton_dense.rgb(princeton_dense_rgb_path)
269
286
  assert isinstance(result, torch.Tensor)
@@ -297,6 +314,14 @@ class TestPrincetonDenseGPULoaders:
297
314
  class TestPrincetonDenseCPULoaders:
298
315
  """CPU Princeton DENSE loaders produce numpy arrays."""
299
316
 
317
+ def test_rccb_shape_dtype_and_range(self, princeton_dense_rccb_path):
318
+ result = cpu_princeton_dense.rccb(princeton_dense_rccb_path)
319
+ assert isinstance(result, np.ndarray)
320
+ assert result.dtype == np.float32
321
+ assert result.shape == (4, 4, 3)
322
+ assert result.min() >= 0.0
323
+ assert result.max() <= 1.0
324
+
300
325
  def test_rgb_shape_dtype_and_range(self, princeton_dense_rgb_path):
301
326
  result = cpu_princeton_dense.rgb(princeton_dense_rgb_path)
302
327
  assert isinstance(result, np.ndarray)
@@ -347,6 +372,12 @@ class TestPrincetonDenseCPULoaders:
347
372
  class TestPrincetonDenseBackwardCompat:
348
373
  """``from euler_loading.loaders import princeton_dense`` returns GPU loaders."""
349
374
 
375
+ def test_top_level_rccb_matches_gpu(self, princeton_dense_rccb_path):
376
+ assert torch.equal(
377
+ princeton_dense_top.rccb(princeton_dense_rccb_path),
378
+ gpu_princeton_dense.rccb(princeton_dense_rccb_path),
379
+ )
380
+
350
381
  def test_top_level_rgb_matches_gpu(self, princeton_dense_rgb_path):
351
382
  assert torch.equal(
352
383
  princeton_dense_top.rgb(princeton_dense_rgb_path),
@@ -1148,6 +1179,7 @@ class TestRDSWriters:
1148
1179
  GENERIC_LOADER_NAMES = [
1149
1180
  "map_2d",
1150
1181
  "map_3d",
1182
+ "points_3d",
1151
1183
  "scattering_coefficient",
1152
1184
  "atmospheric_light",
1153
1185
  "spherical_map",
@@ -1155,6 +1187,7 @@ GENERIC_LOADER_NAMES = [
1155
1187
  GENERIC_WRITER_NAMES = [
1156
1188
  "write_map_2d",
1157
1189
  "write_map_3d",
1190
+ "write_points_3d",
1158
1191
  "write_scattering_coefficient",
1159
1192
  "write_atmospheric_light",
1160
1193
  "write_spherical_map",
@@ -1353,6 +1386,24 @@ def map_3d_npz_path(tmp_path):
1353
1386
  return str(p), arr
1354
1387
 
1355
1388
 
1389
+ @pytest.fixture()
1390
+ def points_3d_npy_path(tmp_path):
1391
+ """Write a small (3, 4, 5) float32 .npy file in 3HW layout."""
1392
+ arr = np.random.default_rng(4).random((3, 4, 5), dtype=np.float32)
1393
+ p = tmp_path / "points_3d.npy"
1394
+ np.save(str(p), arr)
1395
+ return str(p), arr
1396
+
1397
+
1398
+ @pytest.fixture()
1399
+ def points_3d_npz_path(tmp_path):
1400
+ """Write a small (3, 4, 5) float32 .npz file in 3HW layout."""
1401
+ arr = np.random.default_rng(5).random((3, 4, 5), dtype=np.float32)
1402
+ p = tmp_path / "points_3d.npz"
1403
+ np.savez_compressed(str(p), data=arr)
1404
+ return str(p), arr
1405
+
1406
+
1356
1407
  class TestGPUMap2DLoader:
1357
1408
  """GPU ``map_2d`` loads a 2D map as an ``(H, W)`` float32 tensor."""
1358
1409
 
@@ -1434,6 +1485,52 @@ class TestCPUMap3DLoader:
1434
1485
  assert np.allclose(result, np.transpose(expected, (1, 2, 0)))
1435
1486
 
1436
1487
 
1488
+ class TestGPUPoints3DLoader:
1489
+ """GPU ``points_3d`` loads dense 3D points as a ``(3, H, W)`` tensor."""
1490
+
1491
+ def test_npy_dtype_and_shape(self, points_3d_npy_path):
1492
+ path, _ = points_3d_npy_path
1493
+ result = gpu_generic.points_3d(path)
1494
+ assert isinstance(result, torch.Tensor)
1495
+ assert result.dtype == torch.float32
1496
+ assert result.shape == (3, 4, 5)
1497
+
1498
+ def test_npy_values_match(self, points_3d_npy_path):
1499
+ path, expected = points_3d_npy_path
1500
+ result = gpu_generic.points_3d(path)
1501
+ assert torch.allclose(result, torch.from_numpy(expected))
1502
+
1503
+ def test_npz_dtype_and_shape(self, points_3d_npz_path):
1504
+ path, _ = points_3d_npz_path
1505
+ result = gpu_generic.points_3d(path)
1506
+ assert result.dtype == torch.float32
1507
+ assert result.shape == (3, 4, 5)
1508
+
1509
+
1510
+ class TestCPUPoints3DLoader:
1511
+ """CPU ``points_3d`` preserves the ``(3, H, W)`` dense point layout."""
1512
+
1513
+ def test_npy_dtype_and_shape(self, points_3d_npy_path):
1514
+ path, _ = points_3d_npy_path
1515
+ result = cpu_generic.points_3d(path)
1516
+ assert isinstance(result, np.ndarray)
1517
+ assert result.dtype == np.float32
1518
+ assert result.shape == (3, 4, 5)
1519
+
1520
+ def test_npy_values_match(self, points_3d_npy_path):
1521
+ path, expected = points_3d_npy_path
1522
+ result = cpu_generic.points_3d(path)
1523
+ assert np.allclose(result, expected)
1524
+
1525
+ def test_rejects_hwc_input(self, tmp_path):
1526
+ arr = np.random.default_rng(6).random((4, 5, 3), dtype=np.float32)
1527
+ path = tmp_path / "points_3d.npy"
1528
+ np.save(str(path), arr)
1529
+
1530
+ with pytest.raises(ValueError, match=r"points_3d must have shape"):
1531
+ cpu_generic.points_3d(str(path))
1532
+
1533
+
1437
1534
  class TestMapWriters:
1438
1535
  """Writer round-trip tests for ``map_2d`` / ``map_3d``."""
1439
1536
 
@@ -1473,6 +1570,13 @@ class TestMapWriters:
1473
1570
  loaded = gpu_generic.map_3d(str(path))
1474
1571
  assert torch.allclose(loaded, data)
1475
1572
 
1573
+ def test_gpu_write_points_3d_npy_roundtrip(self, tmp_path):
1574
+ data = torch.rand(3, 4, 5, dtype=torch.float32)
1575
+ path = tmp_path / "points.npy"
1576
+ gpu_generic.write_points_3d(str(path), data)
1577
+ loaded = gpu_generic.points_3d(str(path))
1578
+ assert torch.allclose(loaded, data)
1579
+
1476
1580
  def test_cpu_write_map_3d_npy_roundtrip(self, tmp_path):
1477
1581
  data = np.random.default_rng(0).random((4, 5, 2)).astype(np.float32)
1478
1582
  path = tmp_path / "m.npy"
@@ -1480,6 +1584,13 @@ class TestMapWriters:
1480
1584
  loaded = cpu_generic.map_3d(str(path))
1481
1585
  assert np.allclose(loaded, data)
1482
1586
 
1587
+ def test_cpu_write_points_3d_npz_roundtrip(self, tmp_path):
1588
+ data = np.random.default_rng(0).random((3, 4, 5)).astype(np.float32)
1589
+ path = tmp_path / "points.npz"
1590
+ cpu_generic.write_points_3d(str(path), data)
1591
+ loaded = cpu_generic.points_3d(str(path))
1592
+ assert np.allclose(loaded, data)
1593
+
1483
1594
  def test_cpu_write_map_3d_npz_roundtrip(self, tmp_path):
1484
1595
  data = np.random.default_rng(0).random((4, 5, 2)).astype(np.float32)
1485
1596
  path = tmp_path / "m.npz"
@@ -1529,6 +1640,8 @@ class TestSpecificMapAliases:
1529
1640
 
1530
1641
  def test_modality_meta_distinct_types(self):
1531
1642
  """Each alias carries its own modality_type even though logic is shared."""
1643
+ assert gpu_generic.points_3d._modality_meta["type"] == "points_3d"
1644
+ assert cpu_generic.points_3d._modality_meta["type"] == "points_3d"
1532
1645
  assert (
1533
1646
  gpu_generic.scattering_coefficient._modality_meta["type"]
1534
1647
  == "scattering_coefficient"