Back to report index

Step 3 segmentationtrajectory 45eb56e: AI3D-382 Generalize the LAS field fallback helper and freeze DEFAULT_FIELD_DTYPES

Miroslav Simko <ms@iolabs.ch> 2026-09-01T15:11:02+02:00

Commit #99 ยท 4 snippets

 .../segment_mapper.py                              | 42 ++++++++++++++--------
 1 file changed, 28 insertions(+), 14 deletions(-)
Importance #1: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -8,9 +8,11 @@
8import os8import os
9import shutil9import shutil
10import tempfile10import tempfile
11import time11import time
12import types
12import zipfile13import zipfile
14from collections.abc import Mapping
13from dataclasses import dataclass15from dataclasses import dataclass
14from pathlib import Path16from pathlib import Path
15from typing import Any17from typing import Any
1618
Importance #2: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -65,47 +67,55 @@
65SEGMENT_NPZ_FIELD_NAMES: tuple[str, ...] = (POINTS_FIELD_NAME, *ANCILLARY_FIELD_NAMES)67SEGMENT_NPZ_FIELD_NAMES: tuple[str, ...] = (POINTS_FIELD_NAME, *ANCILLARY_FIELD_NAMES)
6668
67#: Dtypes used when a field is missing from the observed `field_dtypes` mapping.69#: Dtypes used when a field is missing from the observed `field_dtypes` mapping.
68#: Fields absent here are looked up strictly (a missing entry is a bug).70#: Fields absent here are looked up strictly (a missing entry is a bug).
69DEFAULT_FIELD_DTYPES: dict[str, np.dtype] = {71#: Frozen so a consumer cannot mutate the producer's schema process-wide, the
72#: same way `iolabs.common.segment_points_io` freezes its field registry.
73DEFAULT_FIELD_DTYPES: Mapping[str, np.dtype] = types.MappingProxyType({
70 POINTS_FIELD_NAME: np.dtype(np.float64),74 POINTS_FIELD_NAME: np.dtype(np.float64),
71 NUMBER_OF_RETURNS_KEY: np.dtype(NUMBER_OF_RETURNS_DTYPE),75 NUMBER_OF_RETURNS_KEY: np.dtype(NUMBER_OF_RETURNS_DTYPE),
72}76})
7377
7478
75def _record_number_of_returns(79def _record_field_or_zeros(
76 record: Any,80 record: Any,
77 *,81 *,
82 key: str,
83 dtype: np.dtype | type[np.generic],
78 point_count: int,84 point_count: int,
79 source: str,85 source: str,
80 logger: logging.Logger | None = None,86 logger: logging.Logger | None = None,
81) -> np.ndarray:87) -> np.ndarray:
82 """Return per-point LAS return counts as uint8, zero-filled when absent.88 """Return a per-point LAS field as *dtype*, zero-filled when absent.
8389
84 Mirrors the scan-angle field fallback: a LAS record that does not expose90 Generic counterpart of the scan-angle fallback: a LAS record that does not
85 `number_of_returns` degrades to zeros rather than raising, and 0 is read91 expose *key* degrades to zeros rather than raising. Adding another optional
86 downstream as "unknown" (see :data:`NUMBER_OF_RETURNS_DTYPE`).92 LAS-backed field means calling this with a new `key`/`dtype`, not writing a
93 new helper. For `number_of_returns` the zero fill reads downstream as
94 "unknown" (see :data:`NUMBER_OF_RETURNS_DTYPE`).
8795
88 Args:96 Args:
89 record: A laspy point record (whole-file `LasData` or a chunk).97 record: A laspy point record (whole-file `LasData` or a chunk).
98 key: Name of the LAS field to read off *record*.
99 dtype: Storage dtype the values are cast to, and of the zero fill.
90 point_count: Number of points in *record*, used to size the fallback.100 point_count: Number of points in *record*, used to size the fallback.
91 source: Label used in the fallback log message (typically a file name).101 source: Label used in the fallback log message (typically a file name).
92 logger: Optional logger for the fallback notice.102 logger: Optional logger for the fallback notice.
93103
94 Returns:104 Returns:
95 A uint8 array of shape `(point_count,)`.105 An array of shape `(point_count,)` with dtype *dtype*.
96 """106 """
97 values = getattr(record, NUMBER_OF_RETURNS_KEY, None)107 values = getattr(record, key, None)
98 if values is None:108 if values is None:
99 if logger is not None:109 if logger is not None:
100 logger.debug(110 logger.debug(
101 "No `%s` field for %s; filling %d zeros (unknown).",111 "No `%s` field for %s; filling %d zeros (unknown).",
102 NUMBER_OF_RETURNS_KEY,112 key,
103 source,113 source,
104 point_count,114 point_count,
105 )115 )
106 return np.zeros(point_count, dtype=NUMBER_OF_RETURNS_DTYPE)116 return np.zeros(point_count, dtype=dtype)
107 return np.asarray(values).astype(NUMBER_OF_RETURNS_DTYPE, copy=False)117 return np.asarray(values).astype(dtype, copy=False)
108118
109119
110def _load_geoshift_from_json(geoshift_path: Path) -> np.ndarray:120def _load_geoshift_from_json(geoshift_path: Path) -> np.ndarray:
111 if not geoshift_path.exists():121 if not geoshift_path.exists():
Importance #3: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2563,10 +2573,12 @@
2563 "intensity": np.asarray(chunk.intensity),2573 "intensity": np.asarray(chunk.intensity),
2564 "red": red,2574 "red": red,
2565 "green": green,2575 "green": green,
2566 "blue": blue,2576 "blue": blue,
2567 NUMBER_OF_RETURNS_KEY: _record_number_of_returns(2577 NUMBER_OF_RETURNS_KEY: _record_field_or_zeros(
2568 chunk,2578 chunk,
2579 key=NUMBER_OF_RETURNS_KEY,
2580 dtype=NUMBER_OF_RETURNS_DTYPE,
2569 point_count=point_count,2581 point_count=point_count,
2570 source=f"{las_file.name} chunk {chunk_count}",2582 source=f"{las_file.name} chunk {chunk_count}",
2571 logger=logger,2583 logger=logger,
2572 ),2584 ),
Importance #4: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2607,10 +2619,12 @@
2607 intensity = np.asarray(las.intensity)2619 intensity = np.asarray(las.intensity)
2608 red = np.asarray(las.red)2620 red = np.asarray(las.red)
2609 green = np.asarray(las.green)2621 green = np.asarray(las.green)
2610 blue = np.asarray(las.blue)2622 blue = np.asarray(las.blue)
2611 number_of_returns = _record_number_of_returns(2623 number_of_returns = _record_field_or_zeros(
2612 las,2624 las,
2625 key=NUMBER_OF_RETURNS_KEY,
2626 dtype=NUMBER_OF_RETURNS_DTYPE,
2613 point_count=int(red.shape[0]),2627 point_count=int(red.shape[0]),
2614 source="LAS file",2628 source="LAS file",
2615 )2629 )
26162630