euler-loading 2.19.0__tar.gz → 2.20.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 (59) hide show
  1. {euler_loading-2.19.0 → euler_loading-2.20.0}/PKG-INFO +2 -2
  2. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/_writing.py +9 -2
  3. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/cpu/princeton_dense.py +19 -0
  4. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/gpu/princeton_dense.py +19 -0
  5. {euler_loading-2.19.0 → euler_loading-2.20.0}/pyproject.toml +2 -2
  6. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/test_loaders.py +14 -0
  7. euler_loading-2.20.0/tests/test_python_compat.py +29 -0
  8. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/test_writing.py +50 -0
  9. {euler_loading-2.19.0 → euler_loading-2.20.0}/.github/workflows/workflow.yml +0 -0
  10. {euler_loading-2.19.0 → euler_loading-2.20.0}/.gitignore +0 -0
  11. {euler_loading-2.19.0 → euler_loading-2.20.0}/README.md +0 -0
  12. {euler_loading-2.19.0 → euler_loading-2.20.0}/docs/loader-attributes.md +0 -0
  13. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/__init__.py +0 -0
  14. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/_dataset_contract.py +0 -0
  15. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/_ds_crawler_utils.py +0 -0
  16. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/_metadata.py +0 -0
  17. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/_resolution.py +0 -0
  18. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/dataset.py +0 -0
  19. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/indexing.py +0 -0
  20. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/__init__.py +0 -0
  21. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/_annotations.py +0 -0
  22. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/_princeton_dense.py +0 -0
  23. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/_writer_utils.py +0 -0
  24. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/contracts.py +0 -0
  25. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/cpu/__init__.py +0 -0
  26. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/cpu/generic.py +0 -0
  27. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
  28. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/cpu/muses.py +0 -0
  29. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
  30. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
  31. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/generate/__init__.py +0 -0
  32. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/generate/__main__.py +0 -0
  33. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/generate/loaders.json +0 -0
  34. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/generic.py +0 -0
  35. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/gpu/__init__.py +0 -0
  36. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/gpu/generic.py +0 -0
  37. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
  38. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/gpu/muses.py +0 -0
  39. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
  40. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
  41. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/muses.py +0 -0
  42. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/princeton_dense.py +0 -0
  43. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/real_drive_sim.py +0 -0
  44. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/loaders/vkitti2.py +0 -0
  45. {euler_loading-2.19.0 → euler_loading-2.20.0}/euler_loading/preprocessing.py +0 -0
  46. {euler_loading-2.19.0 → euler_loading-2.20.0}/example.py +0 -0
  47. {euler_loading-2.19.0 → euler_loading-2.20.0}/gen_loaders.sh +0 -0
  48. {euler_loading-2.19.0 → euler_loading-2.20.0}/package-lock.json +0 -0
  49. {euler_loading-2.19.0 → euler_loading-2.20.0}/sample_rds.py +0 -0
  50. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/__init__.py +0 -0
  51. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/conftest.py +0 -0
  52. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/example_rds_calib.json +0 -0
  53. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/test_dataset.py +0 -0
  54. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/test_id_schema.py +0 -0
  55. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/test_indexing.py +0 -0
  56. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/test_preprocessing.py +0 -0
  57. {euler_loading-2.19.0 → euler_loading-2.20.0}/tests/test_real_dataset.py +0 -0
  58. {euler_loading-2.19.0 → euler_loading-2.20.0}/vkitti_cpu_example_output.json +0 -0
  59. {euler_loading-2.19.0 → euler_loading-2.20.0}/vkitti_gpu_example_output.json +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-loading
3
- Version: 2.19.0
3
+ Version: 2.20.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
7
- Requires-Dist: euler-dataset-contract
7
+ Requires-Dist: euler-dataset-contract>=0.2.1
8
8
  Requires-Dist: numpy
9
9
  Requires-Dist: pillow
10
10
  Provides-Extra: dev
@@ -7,7 +7,7 @@ import tempfile
7
7
  import zipfile
8
8
  from collections.abc import Mapping
9
9
  from pathlib import Path
10
- from typing import Any, Callable
10
+ from typing import Any, Callable, Union
11
11
 
12
12
  from ds_crawler import DatasetWriter, ZipDatasetWriter, get_dataset_contract
13
13
 
@@ -23,7 +23,14 @@ logger = logging.getLogger(__name__)
23
23
  _DEFAULT_WRITER_INDEX_FILENAME = "output.json"
24
24
 
25
25
 
26
- OutputDestination = str | os.PathLike[str] | DatasetWriter | ZipDatasetWriter
26
+ # Type aliases are evaluated at import time even with postponed annotations.
27
+ # ``typing.Union`` keeps this module importable on the supported Python 3.9.
28
+ OutputDestination = Union[
29
+ str,
30
+ os.PathLike[str],
31
+ DatasetWriter,
32
+ ZipDatasetWriter,
33
+ ]
27
34
 
28
35
 
29
36
  def _wrap_writer_save_index(
@@ -24,6 +24,13 @@ import numpy as np
24
24
  from PIL import Image
25
25
 
26
26
  from euler_loading.loaders._annotations import modality_meta
27
+ from euler_loading.loaders._writer_utils import (
28
+ ensure_parent,
29
+ mark_stream_supported,
30
+ save_image,
31
+ to_hwc_rgb,
32
+ to_uint8,
33
+ )
27
34
  from euler_loading.loaders._princeton_dense import DEFAULT_CAMERA_FRAME
28
35
  from euler_loading.loaders._princeton_dense import DEFAULT_LIDAR_FRAME
29
36
  from euler_loading.loaders._princeton_dense import LIDAR_COLUMNS
@@ -73,6 +80,18 @@ def rgb(
73
80
  return np.array(Image.open(path).convert("RGB"), dtype=np.float32) / 255.0
74
81
 
75
82
 
83
+ @mark_stream_supported
84
+ def write_rgb(
85
+ path: Union[str, BinaryIO],
86
+ value: Any,
87
+ meta: dict[str, Any] | None = None,
88
+ ) -> None:
89
+ """Write an RGB array/tensor as an 8-bit PNG."""
90
+ ensure_parent(path)
91
+ arr = to_uint8(to_hwc_rgb(value, name="rgb"), scale_unit_range=True)
92
+ save_image(path, Image.fromarray(arr, mode="RGB"), format="PNG")
93
+
94
+
76
95
  @modality_meta(
77
96
  modality_type="sparse_depth",
78
97
  dtype="float32",
@@ -25,6 +25,13 @@ from PIL import Image
25
25
  import torch
26
26
 
27
27
  from euler_loading.loaders._annotations import modality_meta
28
+ from euler_loading.loaders._writer_utils import (
29
+ ensure_parent,
30
+ mark_stream_supported,
31
+ save_image,
32
+ to_hwc_rgb,
33
+ to_uint8,
34
+ )
28
35
  from euler_loading.loaders._princeton_dense import DEFAULT_CAMERA_FRAME
29
36
  from euler_loading.loaders._princeton_dense import DEFAULT_LIDAR_FRAME
30
37
  from euler_loading.loaders._princeton_dense import LIDAR_COLUMNS
@@ -76,6 +83,18 @@ def rgb(
76
83
  return torch.from_numpy(arr).permute(2, 0, 1).contiguous()
77
84
 
78
85
 
86
+ @mark_stream_supported
87
+ def write_rgb(
88
+ path: Union[str, BinaryIO],
89
+ value: Any,
90
+ meta: dict[str, Any] | None = None,
91
+ ) -> None:
92
+ """Write an RGB tensor/array as an 8-bit PNG."""
93
+ ensure_parent(path)
94
+ arr = to_uint8(to_hwc_rgb(value, name="rgb"), scale_unit_range=True)
95
+ save_image(path, Image.fromarray(arr, mode="RGB"), format="PNG")
96
+
97
+
79
98
  @modality_meta(
80
99
  modality_type="sparse_depth",
81
100
  dtype="float32",
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "euler-loading"
3
- version = "2.19.0"
3
+ version = "2.20.0"
4
4
  description = "Multi-modal PyTorch dataloader using ds-crawler indices"
5
5
  requires-python = ">=3.9"
6
6
  dependencies = [
7
7
  "numpy",
8
8
  "Pillow",
9
9
  "ds-crawler>=2.3.0",
10
- "euler-dataset-contract",
10
+ "euler-dataset-contract>=0.2.1",
11
11
  ]
12
12
 
13
13
  [project.optional-dependencies]
@@ -165,6 +165,8 @@ PRINCETON_DENSE_LOADER_NAMES = [
165
165
  "read_extrinsics",
166
166
  ]
167
167
 
168
+ PRINCETON_DENSE_WRITER_NAMES = ["write_rgb"]
169
+
168
170
 
169
171
  @pytest.fixture()
170
172
  def princeton_dense_rgb_path(tmp_path):
@@ -261,6 +263,18 @@ class TestPrincetonDenseModuleContents:
261
263
  def test_cpu_has_callable(self, name):
262
264
  assert callable(getattr(cpu_princeton_dense, name))
263
265
 
266
+ @pytest.mark.parametrize("name", PRINCETON_DENSE_WRITER_NAMES)
267
+ def test_top_level_has_writer(self, name):
268
+ assert callable(getattr(princeton_dense_top, name))
269
+
270
+ @pytest.mark.parametrize("name", PRINCETON_DENSE_WRITER_NAMES)
271
+ def test_gpu_has_writer(self, name):
272
+ assert callable(getattr(gpu_princeton_dense, name))
273
+
274
+ @pytest.mark.parametrize("name", PRINCETON_DENSE_WRITER_NAMES)
275
+ def test_cpu_has_writer(self, name):
276
+ assert callable(getattr(cpu_princeton_dense, name))
277
+
264
278
  def test_auto_resolution_uses_gpu_module(self):
265
279
  module = resolve_loader_module("princeton_dense")
266
280
  assert module.__name__ == "euler_loading.loaders.gpu.princeton_dense"
@@ -0,0 +1,29 @@
1
+ """Static checks for constructs evaluated at the Python version floor."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ from pathlib import Path
7
+
8
+
9
+ PACKAGE_ROOT = Path(__file__).parents[1] / "euler_loading"
10
+
11
+
12
+ def test_no_runtime_pep604_type_aliases() -> None:
13
+ """PEP 604 unions in assignment expressions fail during Python 3.9 imports."""
14
+ offenders: list[str] = []
15
+ for path in PACKAGE_ROOT.rglob("*.py"):
16
+ tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
17
+ for statement in tree.body:
18
+ if not isinstance(statement, ast.Assign):
19
+ continue
20
+ if any(
21
+ isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr)
22
+ for node in ast.walk(statement.value)
23
+ ):
24
+ offenders.append(f"{path.relative_to(PACKAGE_ROOT)}:{statement.lineno}")
25
+
26
+ assert offenders == [], (
27
+ "Runtime PEP 604 aliases are not Python 3.9 compatible; use "
28
+ f"typing.Union/Optional instead: {offenders}"
29
+ )
@@ -14,6 +14,7 @@ from PIL import Image
14
14
 
15
15
  from euler_loading import Modality, MultiModalDataset, resolve_writer_module
16
16
  from euler_loading.loaders.gpu import vkitti2 as gpu_vkitti2
17
+ from euler_loading.loaders.gpu import princeton_dense as gpu_princeton_dense
17
18
 
18
19
  from .conftest import dummy_loader
19
20
 
@@ -92,6 +93,55 @@ class TestWriterResolution:
92
93
 
93
94
  assert ds.get_writer("intrinsics") is gpu_vkitti2.write_intrinsics
94
95
 
96
+ def test_auto_resolves_princeton_dense_rgb_writer(self):
97
+ index = _flat_index(
98
+ "png",
99
+ ["f001"],
100
+ euler_loading={"loader": "princeton_dense", "function": "rgb"},
101
+ )
102
+ with patch(
103
+ "euler_loading.dataset.index_dataset_from_path",
104
+ return_value=index,
105
+ ):
106
+ ds = MultiModalDataset(modalities={"rgb": Modality("/data/rgb")})
107
+
108
+ assert ds.get_writer("rgb") is gpu_princeton_dense.write_rgb
109
+
110
+ @pytest.mark.parametrize(
111
+ "writer,chw",
112
+ [
113
+ (gpu_princeton_dense.write_rgb, True),
114
+ pytest.param(None, False, id="cpu"),
115
+ ],
116
+ )
117
+ def test_princeton_dense_rgb_writer_round_trip(self, tmp_path, writer, chw):
118
+ if writer is None:
119
+ from euler_loading.loaders.cpu import princeton_dense as cpu_princeton_dense
120
+
121
+ writer = cpu_princeton_dense.write_rgb
122
+ expected = np.array(
123
+ [[[0.0, 0.5, 1.0], [1.0, 0.25, 0.0]]],
124
+ dtype=np.float32,
125
+ )
126
+ value = np.transpose(expected, (2, 0, 1)) if chw else expected
127
+ path = tmp_path / ("gpu.png" if chw else "cpu.png")
128
+
129
+ writer(path, value)
130
+
131
+ actual = np.asarray(Image.open(path).convert("RGB"), dtype=np.float32) / 255.0
132
+ np.testing.assert_allclose(actual, expected, atol=1.0 / 255.0)
133
+
134
+ def test_princeton_dense_rgb_writer_supports_binary_stream(self):
135
+ stream = io.BytesIO()
136
+ gpu_princeton_dense.write_rgb(
137
+ stream,
138
+ np.ones((3, 2, 2), dtype=np.float32),
139
+ )
140
+ stream.seek(0)
141
+ image = Image.open(stream)
142
+ assert image.format == "PNG"
143
+ assert image.size == (2, 2)
144
+
95
145
 
96
146
  class TestWriteSample:
97
147
  def test_write_sample_preserves_relative_path(self, tmp_path):
File without changes