euler-loading 1.3.0__tar.gz → 2.1.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 (46) hide show
  1. {euler_loading-1.3.0 → euler_loading-2.1.0}/PKG-INFO +2 -1
  2. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/__init__.py +1 -0
  3. euler_loading-2.1.0/euler_loading/_dataset_contract.py +91 -0
  4. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/_ds_crawler_utils.py +4 -19
  5. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/_metadata.py +17 -7
  6. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/_resolution.py +12 -2
  7. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/_writing.py +8 -16
  8. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/dataset.py +30 -7
  9. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/cpu/generic.py +71 -0
  10. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/gpu/generic.py +73 -0
  11. {euler_loading-1.3.0 → euler_loading-2.1.0}/pyproject.toml +2 -1
  12. {euler_loading-1.3.0 → euler_loading-2.1.0}/.github/workflows/workflow.yml +0 -0
  13. {euler_loading-1.3.0 → euler_loading-2.1.0}/.gitignore +0 -0
  14. {euler_loading-1.3.0 → euler_loading-2.1.0}/README.md +0 -0
  15. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/indexing.py +0 -0
  16. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/__init__.py +0 -0
  17. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/_annotations.py +0 -0
  18. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/_writer_utils.py +0 -0
  19. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/contracts.py +0 -0
  20. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/cpu/__init__.py +0 -0
  21. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/cpu/generic_dense_depth.py +0 -0
  22. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/cpu/real_drive_sim.py +0 -0
  23. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/cpu/vkitti2.py +0 -0
  24. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/generate/__init__.py +0 -0
  25. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/generate/__main__.py +0 -0
  26. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/generate/loaders.json +0 -0
  27. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/generic.py +0 -0
  28. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/gpu/__init__.py +0 -0
  29. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/gpu/generic_dense_depth.py +0 -0
  30. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/gpu/real_drive_sim.py +0 -0
  31. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/gpu/vkitti2.py +0 -0
  32. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/real_drive_sim.py +0 -0
  33. {euler_loading-1.3.0 → euler_loading-2.1.0}/euler_loading/loaders/vkitti2.py +0 -0
  34. {euler_loading-1.3.0 → euler_loading-2.1.0}/example.py +0 -0
  35. {euler_loading-1.3.0 → euler_loading-2.1.0}/package-lock.json +0 -0
  36. {euler_loading-1.3.0 → euler_loading-2.1.0}/sample_rds.py +0 -0
  37. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/__init__.py +0 -0
  38. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/conftest.py +0 -0
  39. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/example_rds_calib.json +0 -0
  40. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/test_dataset.py +0 -0
  41. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/test_indexing.py +0 -0
  42. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/test_loaders.py +0 -0
  43. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/test_real_dataset.py +0 -0
  44. {euler_loading-1.3.0 → euler_loading-2.1.0}/tests/test_writing.py +0 -0
  45. {euler_loading-1.3.0 → euler_loading-2.1.0}/vkitti_cpu_example_output.json +0 -0
  46. {euler_loading-1.3.0 → euler_loading-2.1.0}/vkitti_gpu_example_output.json +0 -0
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euler-loading
3
- Version: 1.3.0
3
+ Version: 2.1.0
4
4
  Summary: Multi-modal PyTorch dataloader using ds-crawler indices
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: ds-crawler
7
+ Requires-Dist: euler-dataset-contract
7
8
  Requires-Dist: numpy
8
9
  Requires-Dist: pillow
9
10
  Provides-Extra: dev
@@ -1,5 +1,6 @@
1
1
  """euler-loading: Multi-modal PyTorch dataloader using ds-crawler indices."""
2
2
 
3
+ from . import _dataset_contract # noqa: F401
3
4
  from ._resolution import resolve_loader_module, resolve_writer_module
4
5
  from ._writing import create_dataset_writer_from_index
5
6
  from .dataset import Modality, MultiModalDataset
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from euler_dataset_contract import (
6
+ register_addon_validator,
7
+ validate_addon_version,
8
+ validate_slot,
9
+ validate_string_list,
10
+ validate_token,
11
+ )
12
+
13
+
14
+ _ALLOWED_USED_AS = {"condition", "input", "output", "target"}
15
+ _ALLOWED_KEYS = {
16
+ "version",
17
+ "used_as",
18
+ "slot",
19
+ "hierarchy_scope",
20
+ "applies_to",
21
+ "task",
22
+ "loader",
23
+ "function",
24
+ "writer_function",
25
+ }
26
+
27
+
28
+ def validate_euler_loading_addon(value: Any, context: str) -> None:
29
+ if not isinstance(value, dict):
30
+ raise ValueError(f"{context} must be an object")
31
+
32
+ unknown = sorted(set(value.keys()) - _ALLOWED_KEYS)
33
+ if unknown:
34
+ joined = ", ".join(unknown)
35
+ raise ValueError(f"Unknown {context} key(s): {joined}")
36
+
37
+ validate_addon_version(value.get("version"), f"{context}.version")
38
+
39
+ used_as = value.get("used_as")
40
+ if used_as is not None:
41
+ if not isinstance(used_as, str) or not used_as:
42
+ raise ValueError(f"{context}.used_as must be a non-empty string")
43
+ if used_as not in _ALLOWED_USED_AS:
44
+ allowed = ", ".join(sorted(_ALLOWED_USED_AS))
45
+ raise ValueError(
46
+ f"{context}.used_as must be one of {{{allowed}}}, got {used_as!r}"
47
+ )
48
+
49
+ slot = value.get("slot")
50
+ if slot is not None:
51
+ validate_slot(slot, f"{context}.slot")
52
+
53
+ task = value.get("task")
54
+ if task is not None:
55
+ validate_token(task, f"{context}.task")
56
+
57
+ loader = value.get("loader")
58
+ function = value.get("function")
59
+ if loader is not None:
60
+ validate_token(loader, f"{context}.loader")
61
+ if function is not None:
62
+ validate_token(function, f"{context}.function")
63
+ if (loader is None) != (function is None):
64
+ raise ValueError(
65
+ f"{context}.loader and {context}.function must either both be set "
66
+ "or both be omitted"
67
+ )
68
+
69
+ writer_function = value.get("writer_function")
70
+ if writer_function is not None:
71
+ validate_token(writer_function, f"{context}.writer_function")
72
+
73
+ hierarchy_scope = value.get("hierarchy_scope")
74
+ applies_to = value.get("applies_to")
75
+ if hierarchy_scope is not None or applies_to is not None:
76
+ if used_as != "condition":
77
+ raise ValueError(
78
+ f"{context}.hierarchy_scope and {context}.applies_to are only "
79
+ "allowed when used_as is 'condition'"
80
+ )
81
+ if hierarchy_scope is not None:
82
+ validate_token(hierarchy_scope, f"{context}.hierarchy_scope")
83
+ if applies_to is not None:
84
+ validate_string_list(
85
+ applies_to,
86
+ f"{context}.applies_to",
87
+ allow_wildcard=True,
88
+ )
89
+
90
+
91
+ register_addon_validator("euler_loading", validate_euler_loading_addon, overwrite=True)
@@ -5,6 +5,7 @@ from collections.abc import Mapping
5
5
  from pathlib import Path
6
6
  from typing import Any
7
7
 
8
+ from ds_crawler import get_dataset_contract
8
9
  from ds_crawler.zip_utils import read_metadata_json
9
10
 
10
11
  try:
@@ -13,19 +14,6 @@ except ImportError:
13
14
  _load_dataset_split = None
14
15
 
15
16
 
16
- DS_CRAWLER_STRUCTURAL_KEYS = frozenset({
17
- "name",
18
- "type",
19
- "id_regex",
20
- "id_regex_join_char",
21
- "euler_train",
22
- "dataset",
23
- "hierarchy_regex",
24
- "named_capture_group_value_separator",
25
- "sampled",
26
- })
27
-
28
-
29
17
  def as_non_empty_str(value: Any) -> str | None:
30
18
  if value is None:
31
19
  return None
@@ -61,11 +49,7 @@ def first_non_empty_list(*candidates: list[str] | None) -> list[str]:
61
49
 
62
50
 
63
51
  def extract_ds_crawler_properties(index_output: Mapping[str, Any]) -> dict[str, Any]:
64
- return {
65
- str(key): value
66
- for key, value in index_output.items()
67
- if key not in DS_CRAWLER_STRUCTURAL_KEYS
68
- }
52
+ return get_dataset_contract(dict(index_output)).to_properties_dict()
69
53
 
70
54
 
71
55
  _SPLIT_NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]*$")
@@ -164,5 +148,6 @@ def load_index_output(
164
148
  )
165
149
 
166
150
  result = dict(base_output)
167
- result["dataset"] = split_dataset
151
+ result["index"] = split_dataset
152
+ result.pop("dataset", None)
168
153
  return result
@@ -4,6 +4,8 @@ import re
4
4
  from collections.abc import Mapping
5
5
  from typing import TYPE_CHECKING, Any, Callable
6
6
 
7
+ from ds_crawler import get_dataset_contract
8
+
7
9
  from ._ds_crawler_utils import (
8
10
  as_non_empty_str,
9
11
  as_string_list,
@@ -44,6 +46,7 @@ def _build_runlog_entry(
44
46
  as_non_empty_str(
45
47
  _resolve_euler_loading_property(euler_loading_properties, "modality_type")
46
48
  ),
49
+ as_non_empty_str(descriptor.get("modality_key")),
47
50
  as_non_empty_str(descriptor.get("modality_type")),
48
51
  _infer_modality_type(name=name, path=modality.path),
49
52
  )
@@ -146,12 +149,19 @@ def _read_ds_crawler_descriptor(
146
149
 
147
150
  if isinstance(index_output, Mapping):
148
151
  properties.update(extract_ds_crawler_properties(index_output))
149
- index_type = as_non_empty_str(index_output.get("type"))
150
- if index_type is not None:
151
- descriptor["modality_type"] = index_type
152
- hierarchy_regex = as_non_empty_str(index_output.get("hierarchy_regex"))
153
- if hierarchy_regex is not None:
154
- descriptor["hierarchy_regex"] = hierarchy_regex
152
+ try:
153
+ contract = get_dataset_contract(dict(index_output))
154
+ except Exception:
155
+ contract = None
156
+ if contract is not None:
157
+ descriptor["modality_key"] = contract.modality_key
158
+ indexing = index_output.get("indexing")
159
+ if isinstance(indexing, Mapping):
160
+ hierarchy = indexing.get("hierarchy")
161
+ if isinstance(hierarchy, Mapping):
162
+ hierarchy_regex = as_non_empty_str(hierarchy.get("regex"))
163
+ if hierarchy_regex is not None:
164
+ descriptor["hierarchy_regex"] = hierarchy_regex
155
165
 
156
166
  try:
157
167
  cfg = load_dataset_config_fn({"path": path})
@@ -164,7 +174,7 @@ def _read_ds_crawler_descriptor(
164
174
 
165
175
  cfg_type = as_non_empty_str(getattr(cfg, "type", None))
166
176
  if cfg_type is not None:
167
- descriptor["modality_type"] = cfg_type
177
+ descriptor["modality_key"] = cfg_type
168
178
 
169
179
  cfg_hierarchy_regex = as_non_empty_str(getattr(cfg, "hierarchy_regex", None))
170
180
  if cfg_hierarchy_regex is not None:
@@ -6,6 +6,8 @@ from collections.abc import Mapping
6
6
  from types import ModuleType
7
7
  from typing import TYPE_CHECKING, Any, Callable
8
8
 
9
+ from ds_crawler import get_dataset_contract
10
+
9
11
  from ._ds_crawler_utils import as_non_empty_str
10
12
 
11
13
  if TYPE_CHECKING:
@@ -61,7 +63,11 @@ def _resolve_loader(
61
63
  if modality.loader is not None:
62
64
  return modality.loader
63
65
 
64
- euler_loading_meta = index.get("euler_loading")
66
+ try:
67
+ contract = get_dataset_contract(index)
68
+ euler_loading_meta = contract.get_addon("euler_loading")
69
+ except Exception:
70
+ euler_loading_meta = None
65
71
  if not isinstance(euler_loading_meta, Mapping):
66
72
  raise ValueError(
67
73
  f"Modality {modality_name!r}: no explicit loader provided and the "
@@ -126,7 +132,11 @@ def _resolve_writer(
126
132
  if modality.writer is not None:
127
133
  return modality.writer
128
134
 
129
- euler_loading_meta = index.get("euler_loading")
135
+ try:
136
+ contract = get_dataset_contract(index)
137
+ euler_loading_meta = contract.get_addon("euler_loading")
138
+ except Exception:
139
+ euler_loading_meta = None
130
140
  if not isinstance(euler_loading_meta, Mapping):
131
141
  return None
132
142
 
@@ -7,11 +7,10 @@ from collections.abc import Mapping
7
7
  from pathlib import Path
8
8
  from typing import Any, Callable
9
9
 
10
- from ds_crawler import DatasetWriter, ZipDatasetWriter
10
+ from ds_crawler import DatasetWriter, ZipDatasetWriter, get_dataset_contract
11
11
 
12
12
  from ._ds_crawler_utils import (
13
13
  as_non_empty_str,
14
- extract_ds_crawler_properties,
15
14
  first_non_empty,
16
15
  )
17
16
  from .loaders._writer_utils import supports_stream_target
@@ -30,27 +29,20 @@ def create_dataset_writer_from_index(
30
29
  zip: bool = False,
31
30
  ) -> DatasetWriter | ZipDatasetWriter:
32
31
  """Create a ds-crawler writer that mirrors an existing index's metadata."""
33
- name = as_non_empty_str(index_output.get("name")) or "dataset"
34
- type_name = as_non_empty_str(index_output.get("type")) or "other"
35
- euler_train = index_output.get("euler_train")
36
- if not isinstance(euler_train, Mapping):
37
- raise ValueError(
38
- "index_output must contain an 'euler_train' mapping to build a writer."
39
- )
32
+ contract = get_dataset_contract(dict(index_output))
40
33
 
34
+ indexing = index_output.get("indexing")
35
+ hierarchy = indexing.get("hierarchy") if isinstance(indexing, Mapping) else None
36
+ id_cfg = indexing.get("id") if isinstance(indexing, Mapping) else None
41
37
  separator = first_non_empty(
42
- as_non_empty_str(index_output.get("named_capture_group_value_separator")),
43
- as_non_empty_str(index_output.get("id_regex_join_char")),
38
+ as_non_empty_str(hierarchy.get("separator")) if isinstance(hierarchy, Mapping) else None,
39
+ as_non_empty_str(id_cfg.get("join_char")) if isinstance(id_cfg, Mapping) else None,
44
40
  )
45
- properties = extract_ds_crawler_properties(index_output)
46
41
  writer_cls = ZipDatasetWriter if zip else DatasetWriter
47
42
  return writer_cls(
48
43
  root,
49
- name=name,
50
- type=type_name,
51
- euler_train=dict(euler_train),
44
+ head=contract.to_mapping(),
52
45
  separator=separator,
53
- **properties,
54
46
  )
55
47
 
56
48
 
@@ -18,6 +18,7 @@ except ImportError:
18
18
  from ds_crawler import (
19
19
  DatasetWriter,
20
20
  ZipDatasetWriter,
21
+ get_dataset_contract,
21
22
  index_dataset_from_path,
22
23
  load_dataset_config,
23
24
  )
@@ -319,7 +320,7 @@ class MultiModalDataset(_BaseDataset):
319
320
  self._resolved_writers[name] = _resolve_writer(
320
321
  modality_name=name, modality=modality, index=index,
321
322
  )
322
- records = collect_files(index["dataset"])
323
+ records = collect_files(index["index"])
323
324
  lookup = {
324
325
  "/".join(rec.hierarchy_path + (rec.file_entry["id"],)): rec
325
326
  for rec in records
@@ -351,7 +352,7 @@ class MultiModalDataset(_BaseDataset):
351
352
  self._resolved_writers[name] = _resolve_writer(
352
353
  modality_name=name, modality=modality, index=index,
353
354
  )
354
- files_by_level = collect_hierarchical_files(index["dataset"])
355
+ files_by_level = collect_hierarchical_files(index["index"])
355
356
  self._hierarchical_lookups[name] = files_by_level
356
357
  total_files = sum(len(f) for f in files_by_level.values())
357
358
  logger.info(
@@ -407,7 +408,11 @@ class MultiModalDataset(_BaseDataset):
407
408
 
408
409
  def get_modality_metadata(self, modality_name: str) -> dict[str, Any]:
409
410
  """Return the metadata dict for a given modality name."""
410
- return self._index_outputs.get(modality_name, {}).get("meta", {})
411
+ index = self._index_outputs.get(modality_name, {})
412
+ try:
413
+ return get_dataset_contract(index).meta or {}
414
+ except Exception:
415
+ return {}
411
416
 
412
417
  def get_modality_index(self, modality_name: str) -> dict[str, Any]:
413
418
  """Return the cached ds-crawler index for a modality."""
@@ -505,7 +510,12 @@ class MultiModalDataset(_BaseDataset):
505
510
  output_root=output_root, modality_name=modality_name
506
511
  )
507
512
  record = self._lookups[modality_name][sample_id]
508
- modality_meta = self._index_outputs[modality_name].get("meta")
513
+ try:
514
+ modality_meta = get_dataset_contract(
515
+ self._index_outputs[modality_name]
516
+ ).meta
517
+ except Exception:
518
+ modality_meta = None
509
519
  basename = Path(record.file_entry["path"]).name
510
520
  relative_path = record.file_entry["path"]
511
521
  full_id = _build_writer_full_id(
@@ -545,7 +555,10 @@ class MultiModalDataset(_BaseDataset):
545
555
  first_modality: str | None = None
546
556
 
547
557
  for modality_name, index in self._index_outputs.items():
548
- name = index.get("name")
558
+ try:
559
+ name = get_dataset_contract(index).name
560
+ except Exception:
561
+ name = None
549
562
  if name is None:
550
563
  continue
551
564
  if first_name is None:
@@ -604,7 +617,12 @@ class MultiModalDataset(_BaseDataset):
604
617
 
605
618
  for name, modality in self._modalities.items():
606
619
  record = self._lookups[name][sample_id]
607
- modality_meta = self._index_outputs[name].get("meta")
620
+ try:
621
+ modality_meta = get_dataset_contract(
622
+ self._index_outputs[name]
623
+ ).meta
624
+ except Exception:
625
+ modality_meta = None
608
626
 
609
627
  if name in self._zip_modalities:
610
628
  file_or_path = self._open_from_zip(
@@ -625,7 +643,12 @@ class MultiModalDataset(_BaseDataset):
625
643
  for name, modality in self._hierarchical_modalities.items():
626
644
  files_by_level = self._hierarchical_lookups[name]
627
645
  matched = match_hierarchical_files(hierarchy_path, files_by_level)
628
- modality_meta = self._hierarchical_index_outputs[name].get("meta")
646
+ try:
647
+ modality_meta = get_dataset_contract(
648
+ self._hierarchical_index_outputs[name]
649
+ ).meta
650
+ except Exception:
651
+ modality_meta = None
629
652
  loaded: dict[str, Any] = {}
630
653
  for entry in matched:
631
654
  cache_key = f"{modality.path}/{entry['path']}"
@@ -11,6 +11,8 @@ Supported file extensions:
11
11
  Return types
12
12
  ------------
13
13
  - **spherical_map** -- ``np.ndarray`` of shape ``(H, W, C)`` float32.
14
+ - **intrinsics** -- ``np.ndarray`` of shape ``(3, 3)`` float32.
15
+ - **sh_coeffs** -- ``np.ndarray`` of shape ``(N, 3)`` float32.
14
16
 
15
17
  Usage::
16
18
 
@@ -18,6 +20,8 @@ Usage::
18
20
  from euler_loading import Modality
19
21
 
20
22
  Modality("/data/my_dataset/spherical_map", loader=generic.spherical_map)
23
+ Modality("/data/my_dataset/intrinsics", loader=generic.intrinsics)
24
+ Modality("/data/my_dataset/sh_coeffs", loader=generic.sh_coeffs)
21
25
  """
22
26
 
23
27
  from __future__ import annotations
@@ -60,6 +64,22 @@ def _load_numpy(path: Union[str, BinaryIO]) -> np.ndarray:
60
64
  return arr.astype(np.float32)
61
65
 
62
66
 
67
+ def _write_numpy(path: Union[str, BinaryIO], value: Any) -> None:
68
+ """Write an array to ``.npy`` or ``.npz`` based on extension."""
69
+ ensure_parent(path)
70
+ ext = os.path.splitext(_get_name(path))[1].lower()
71
+ arr = to_numpy(value).astype(np.float32)
72
+
73
+ if ext == _NPY_EXTENSION:
74
+ np.save(path, arr)
75
+ return
76
+ if ext == _NPZ_EXTENSION:
77
+ np.savez_compressed(path, data=arr)
78
+ return
79
+
80
+ raise ValueError(f"Unsupported output extension: {ext}")
81
+
82
+
63
83
  # ---------------------------------------------------------------------------
64
84
  # Public loaders
65
85
  # ---------------------------------------------------------------------------
@@ -86,6 +106,45 @@ def spherical_map(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None
86
106
  # ---------------------------------------------------------------------------
87
107
 
88
108
 
109
+ @modality_meta(
110
+ modality_type="intrinsics",
111
+ dtype="float32",
112
+ shape="3x3",
113
+ file_formats=[".npy", ".npz"],
114
+ )
115
+ def intrinsics(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None) -> np.ndarray:
116
+ """Load a camera intrinsics matrix as a ``(3, 3)`` float32 array.
117
+
118
+ The file is expected to contain a ``(3, 3)`` array::
119
+
120
+ [[fx, 0, cx],
121
+ [ 0, fy, cy],
122
+ [ 0, 0, 1]]
123
+ """
124
+ return _load_numpy(path)
125
+
126
+
127
+ @modality_meta(
128
+ modality_type="sh_coeffs",
129
+ dtype="float32",
130
+ shape="NC",
131
+ file_formats=[".npy", ".npz"],
132
+ )
133
+ def sh_coeffs(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None) -> np.ndarray:
134
+ """Load spherical-harmonic coefficients as an ``(N, 3)`` float32 array.
135
+
136
+ *N* is the number of SH basis functions (e.g. 15 for degree-3 SH with
137
+ the constant term removed). Each row is a 3-vector (one per spatial
138
+ dimension).
139
+ """
140
+ return _load_numpy(path)
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # Writers
145
+ # ---------------------------------------------------------------------------
146
+
147
+
89
148
  @mark_stream_supported
90
149
  def write_spherical_map(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
91
150
  """Write spherical-map data to NumPy formats based on extension.
@@ -107,3 +166,15 @@ def write_spherical_map(path: Union[str, BinaryIO], value: Any, meta: dict[str,
107
166
  return
108
167
 
109
168
  raise ValueError(f"Unsupported spherical-map output extension: {ext}")
169
+
170
+
171
+ @mark_stream_supported
172
+ def write_intrinsics(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
173
+ """Write a ``(3, 3)`` intrinsics matrix to a NumPy file."""
174
+ _write_numpy(path, value)
175
+
176
+
177
+ @mark_stream_supported
178
+ def write_sh_coeffs(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
179
+ """Write ``(N, 3)`` spherical-harmonic coefficients to a NumPy file."""
180
+ _write_numpy(path, value)
@@ -11,6 +11,8 @@ Supported file extensions:
11
11
  Return types
12
12
  ------------
13
13
  - **spherical_map** -- ``torch.FloatTensor`` of shape ``(C, H, W)``.
14
+ - **intrinsics** -- ``torch.FloatTensor`` of shape ``(3, 3)``.
15
+ - **sh_coeffs** -- ``torch.FloatTensor`` of shape ``(N, 3)``.
14
16
 
15
17
  Usage::
16
18
 
@@ -18,6 +20,8 @@ Usage::
18
20
  from euler_loading import Modality
19
21
 
20
22
  Modality("/data/my_dataset/spherical_map", loader=generic.spherical_map)
23
+ Modality("/data/my_dataset/intrinsics", loader=generic.intrinsics)
24
+ Modality("/data/my_dataset/sh_coeffs", loader=generic.sh_coeffs)
21
25
  """
22
26
 
23
27
  from __future__ import annotations
@@ -61,6 +65,22 @@ def _load_numpy(path: Union[str, BinaryIO]) -> np.ndarray:
61
65
  return arr.astype(np.float32)
62
66
 
63
67
 
68
+ def _write_numpy(path: Union[str, BinaryIO], value: Any) -> None:
69
+ """Write an array to ``.npy`` or ``.npz`` based on extension."""
70
+ ensure_parent(path)
71
+ ext = os.path.splitext(_get_name(path))[1].lower()
72
+ arr = to_numpy(value).astype(np.float32)
73
+
74
+ if ext == _NPY_EXTENSION:
75
+ np.save(path, arr)
76
+ return
77
+ if ext == _NPZ_EXTENSION:
78
+ np.savez_compressed(path, data=arr)
79
+ return
80
+
81
+ raise ValueError(f"Unsupported output extension: {ext}")
82
+
83
+
64
84
  # ---------------------------------------------------------------------------
65
85
  # Public loaders
66
86
  # ---------------------------------------------------------------------------
@@ -86,6 +106,47 @@ def spherical_map(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None
86
106
  # ---------------------------------------------------------------------------
87
107
 
88
108
 
109
+ @modality_meta(
110
+ modality_type="intrinsics",
111
+ dtype="float32",
112
+ shape="3x3",
113
+ file_formats=[".npy", ".npz"],
114
+ )
115
+ def intrinsics(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None) -> torch.Tensor:
116
+ """Load a camera intrinsics matrix as a ``(3, 3)`` float32 tensor.
117
+
118
+ The file is expected to contain a ``(3, 3)`` array::
119
+
120
+ [[fx, 0, cx],
121
+ [ 0, fy, cy],
122
+ [ 0, 0, 1]]
123
+ """
124
+ arr = _load_numpy(path)
125
+ return torch.from_numpy(arr).contiguous()
126
+
127
+
128
+ @modality_meta(
129
+ modality_type="sh_coeffs",
130
+ dtype="float32",
131
+ shape="NC",
132
+ file_formats=[".npy", ".npz"],
133
+ )
134
+ def sh_coeffs(path: Union[str, BinaryIO], meta: dict[str, Any] | None = None) -> torch.Tensor:
135
+ """Load spherical-harmonic coefficients as an ``(N, 3)`` float32 tensor.
136
+
137
+ *N* is the number of SH basis functions (e.g. 15 for degree-3 SH with
138
+ the constant term removed). Each row is a 3-vector (one per spatial
139
+ dimension).
140
+ """
141
+ arr = _load_numpy(path)
142
+ return torch.from_numpy(arr).contiguous()
143
+
144
+
145
+ # ---------------------------------------------------------------------------
146
+ # Writers
147
+ # ---------------------------------------------------------------------------
148
+
149
+
89
150
  @mark_stream_supported
90
151
  def write_spherical_map(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
91
152
  """Write spherical-map data to NumPy formats based on extension."""
@@ -101,3 +162,15 @@ def write_spherical_map(path: Union[str, BinaryIO], value: Any, meta: dict[str,
101
162
  return
102
163
 
103
164
  raise ValueError(f"Unsupported spherical-map output extension: {ext}")
165
+
166
+
167
+ @mark_stream_supported
168
+ def write_intrinsics(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
169
+ """Write a ``(3, 3)`` intrinsics matrix to a NumPy file."""
170
+ _write_numpy(path, value)
171
+
172
+
173
+ @mark_stream_supported
174
+ def write_sh_coeffs(path: Union[str, BinaryIO], value: Any, meta: dict[str, Any] | None = None) -> None:
175
+ """Write ``(N, 3)`` spherical-harmonic coefficients to a NumPy file."""
176
+ _write_numpy(path, value)
@@ -1,12 +1,13 @@
1
1
  [project]
2
2
  name = "euler-loading"
3
- version = "1.3.0"
3
+ version = "2.1.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",
10
+ "euler-dataset-contract",
10
11
  ]
11
12
 
12
13
  [project.optional-dependencies]
File without changes
File without changes
File without changes