Back to report index

Step 3 segmentationtrajectory 36771c7: AI3D-382 Use module imports (Google style) in touched files

Miroslav Simko <ms@iolabs.ch> 2026-09-02T07:29:48+02:00

Commit #103 ยท 58 snippets

 .../segment_mapper.py                              | 124 ++++++++++-----------
 tests/test_number_of_returns.py                    |  12 +-
 tests/test_segment_mapper_overflow.py              |  37 +++---
 3 files changed, 83 insertions(+), 90 deletions(-)
Importance #1: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -924,9 +924,9 @@
924 manifest = {924 manifest = {
925 "version": 1,925 "version": 1,
926 "format": "segment_mapper_branch_geometries",926 "format": "segment_mapper_branch_geometries",
927 "branch_count": len(branch_entries),927 "branch_count": len(branch_entries),
928 "shared_geoshift_file": str(Path("lane_points") / geoshift_fn),928 "shared_geoshift_file": str(pathlib.Path("lane_points") / geoshift_fn),
929 "branches": branch_entries,929 "branches": branch_entries,
930 }930 }
931 manifest_path = output_dir / "manifests" / "branches.json"931 manifest_path = output_dir / "manifests" / "branches.json"
932 _write_json_atomic(manifest_path, manifest)932 _write_json_atomic(manifest_path, manifest)
Importance #2: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1608,20 +1608,20 @@
1608 version_info.save_version_json(1608 version_info.save_version_json(
1609 segment_dir / "run3_versions.json", "step3_segment_mapper"1609 segment_dir / "run3_versions.json", "step3_segment_mapper"
1610 )1610 )
16111611
1612 def divide_las_file_by_planes(self, las_file: Path) -> None:1612 def divide_las_file_by_planes(self, las_file: pathlib.Path) -> None:
1613 """1613 """
1614 Divide a LAS file by planes and save the points between planes into npz files with1614 Divide a LAS file by planes and save the points between planes into npz files with
1615 """1615 """
1616 with logstash.las_file_scope(las_file):1616 with logstash.las_file_scope(las_file):
1617 self._divide_las_file_by_planes_impl(las_file)1617 self._divide_las_file_by_planes_impl(las_file)
16181618
1619 def _divide_las_file_by_planes_impl(self, las_file: Path) -> None:1619 def _divide_las_file_by_planes_impl(self, las_file: pathlib.Path) -> None:
1620 logger = self.logger.getChild("divide_las_file_by_planes")1620 logger = self.logger.getChild("divide_las_file_by_planes")
1621 gc.collect()1621 gc.collect()
16221622
1623 segments_base_dir_name = Path(1623 segments_base_dir_name = pathlib.Path(
1624 str(self.config.get("segments_base_dir_name", "lane_points"))1624 str(self.config.get("segments_base_dir_name", "lane_points"))
1625 )1625 )
1626 n_extra_planes:int = self.config.get("n_extra_planes", 0)1626 n_extra_planes:int = self.config.get("n_extra_planes", 0)
16271627
Importance #3: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -6,15 +6,15 @@
6import itertools6import itertools
7import json7import json
8import logging8import logging
9import os9import os
10import pathlib
10import shutil11import shutil
11import tempfile12import tempfile
12import time13import time
13import types14import types
14import zipfile15import zipfile
15from collections.abc import Mapping16from collections import abc
16from pathlib import Path
17from typing import Any17from typing import Any
1818
19import laspy19import laspy
20import numpy as np20import numpy as np
Importance #4: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -69,9 +69,9 @@
69#: 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.
70#: 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).
71#: Frozen so a consumer cannot mutate the producer's schema process-wide, the71#: 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.72#: same way `iolabs.common.segment_points_io` freezes its field registry.
73DEFAULT_FIELD_DTYPES: Mapping[str, np.dtype] = types.MappingProxyType({73DEFAULT_FIELD_DTYPES: abc.Mapping[str, np.dtype] = types.MappingProxyType({
74 POINTS_FIELD_NAME: np.dtype(np.float64),74 POINTS_FIELD_NAME: np.dtype(np.float64),
75 NUMBER_OF_RETURNS_KEY: np.dtype(NUMBER_OF_RETURNS_DTYPE),75 NUMBER_OF_RETURNS_KEY: np.dtype(NUMBER_OF_RETURNS_DTYPE),
76})76})
7777
Importance #5: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -123,9 +123,9 @@
123 return np.zeros(point_count, dtype=dtype)123 return np.zeros(point_count, dtype=dtype)
124 return np.asarray(values).astype(dtype, copy=False)124 return np.asarray(values).astype(dtype, copy=False)
125125
126126
127def _load_geoshift_from_json(geoshift_path: Path) -> np.ndarray:127def _load_geoshift_from_json(geoshift_path: pathlib.Path) -> np.ndarray:
128 if not geoshift_path.exists():128 if not geoshift_path.exists():
129 raise FileNotFoundError(129 raise FileNotFoundError(
130 f"reuse_existing_geoshift=true but geoshift file not found: {geoshift_path}"130 f"reuse_existing_geoshift=true but geoshift file not found: {geoshift_path}"
131 )131 )
Importance #6: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -134,9 +134,9 @@
134 return np.array([float(data["x"]), float(data["y"]), float(data["z"])])134 return np.array([float(data["x"]), float(data["y"]), float(data["z"])])
135135
136136
137def _load_planes_from_npz(137def _load_planes_from_npz(
138 planes_path: Path,138 planes_path: pathlib.Path,
139 geoshift: np.ndarray,139 geoshift: np.ndarray,
140) -> list[geometry_tools.Plane]:140) -> list[geometry_tools.Plane]:
141 if not planes_path.exists():141 if not planes_path.exists():
142 raise FileNotFoundError(142 raise FileNotFoundError(
Importance #7: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -166,9 +166,9 @@
166 planes.append(geometry_tools.Plane(point, normal))166 planes.append(geometry_tools.Plane(point, normal))
167 return planes167 return planes
168168
169169
170def _save_geoshift_to_json(geoshift_path: Path, geoshift: np.ndarray) -> None:170def _save_geoshift_to_json(geoshift_path: pathlib.Path, geoshift: np.ndarray) -> None:
171 geoshift_path.parent.mkdir(parents=True, exist_ok=True)171 geoshift_path.parent.mkdir(parents=True, exist_ok=True)
172 with geoshift_path.open("w", encoding="utf-8") as f:172 with geoshift_path.open("w", encoding="utf-8") as f:
173 json.dump(173 json.dump(
174 {174 {
Importance #8: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -180,16 +180,16 @@
180 indent=2,180 indent=2,
181 )181 )
182182
183183
184def _load_json(path: Path) -> Any:184def _load_json(path: pathlib.Path) -> Any:
185 if not path.exists():185 if not path.exists():
186 raise FileNotFoundError(f"JSON file does not exist: {path}")186 raise FileNotFoundError(f"JSON file does not exist: {path}")
187 with path.open("r", encoding="utf-8") as f:187 with path.open("r", encoding="utf-8") as f:
188 return json.load(f)188 return json.load(f)
189189
190190
191def _write_json_atomic(path: Path, payload: Any) -> None:191def _write_json_atomic(path: pathlib.Path, payload: Any) -> None:
192 path.parent.mkdir(parents=True, exist_ok=True)192 path.parent.mkdir(parents=True, exist_ok=True)
193 with tempfile.NamedTemporaryFile(193 with tempfile.NamedTemporaryFile(
194 suffix=".json",194 suffix=".json",
195 prefix=f".{path.stem}_",195 prefix=f".{path.stem}_",
Importance #9: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -198,17 +198,17 @@
198 mode="w",198 mode="w",
199 encoding="utf-8",199 encoding="utf-8",
200 ) as tmp_file:200 ) as tmp_file:
201 json.dump(payload, tmp_file, indent=2)201 json.dump(payload, tmp_file, indent=2)
202 tmp_path = Path(tmp_file.name)202 tmp_path = pathlib.Path(tmp_file.name)
203 try:203 try:
204 os.replace(tmp_path, path)204 os.replace(tmp_path, path)
205 finally:205 finally:
206 if tmp_path.exists():206 if tmp_path.exists():
207 tmp_path.unlink()207 tmp_path.unlink()
208208
209209
210def _copy_file_atomic(source: Path, destination: Path) -> None:210def _copy_file_atomic(source: pathlib.Path, destination: pathlib.Path) -> None:
211 if not source.exists():211 if not source.exists():
212 raise FileNotFoundError(f"Branch geometry artifact does not exist: {source}")212 raise FileNotFoundError(f"Branch geometry artifact does not exist: {source}")
213 destination.parent.mkdir(parents=True, exist_ok=True)213 destination.parent.mkdir(parents=True, exist_ok=True)
214 with tempfile.NamedTemporaryFile(214 with tempfile.NamedTemporaryFile(
Importance #10: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -216,9 +216,9 @@
216 prefix=f".{destination.stem}_",216 prefix=f".{destination.stem}_",
217 dir=str(destination.parent),217 dir=str(destination.parent),
218 delete=False,218 delete=False,
219 ) as tmp_file:219 ) as tmp_file:
220 tmp_path = Path(tmp_file.name)220 tmp_path = pathlib.Path(tmp_file.name)
221 try:221 try:
222 shutil.copy2(source, tmp_path)222 shutil.copy2(source, tmp_path)
223 os.replace(tmp_path, destination)223 os.replace(tmp_path, destination)
224 finally:224 finally:
Importance #11: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -247,25 +247,25 @@
247 out.append(item)247 out.append(item)
248 return out248 return out
249249
250250
251def _segment_trajectories_from_json(path: Path) -> dict[int, list[str]]:251def _segment_trajectories_from_json(path: pathlib.Path) -> dict[int, list[str]]:
252 raw = _load_json(path)252 raw = _load_json(path)
253 return {int(segment_idx): list(las_paths) for segment_idx, las_paths in raw.items()}253 return {int(segment_idx): list(las_paths) for segment_idx, las_paths in raw.items()}
254254
255255
256def _rewrite_segment_trajectory_paths(256def _rewrite_segment_trajectory_paths(
257 segment_trajectories: dict[int, list[str]],257 segment_trajectories: dict[int, list[str]],
258 source_las_by_stem: dict[str, Path | str],258 source_las_by_stem: dict[str, pathlib.Path | str],
259) -> dict[int, list[str]]:259) -> dict[int, list[str]]:
260 mapped_las_by_stem = {260 mapped_las_by_stem = {
261 str(stem): str(path) for stem, path in source_las_by_stem.items()261 str(stem): str(path) for stem, path in source_las_by_stem.items()
262 }262 }
263 rewritten: dict[int, list[str]] = {}263 rewritten: dict[int, list[str]] = {}
264 for segment_idx, las_paths in segment_trajectories.items():264 for segment_idx, las_paths in segment_trajectories.items():
265 mapped_paths: list[str] = []265 mapped_paths: list[str] = []
266 for las_path in las_paths:266 for las_path in las_paths:
267 stem = Path(str(las_path)).stem267 stem = pathlib.Path(str(las_path)).stem
268 mapped_paths.append(mapped_las_by_stem.get(stem, str(las_path)))268 mapped_paths.append(mapped_las_by_stem.get(stem, str(las_path)))
269 rewritten[int(segment_idx)] = _deduplicate_keep_order(mapped_paths)269 rewritten[int(segment_idx)] = _deduplicate_keep_order(mapped_paths)
270 return rewritten270 return rewritten
271271
Importance #12: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -286,10 +286,10 @@
286@dataclasses.dataclass286@dataclasses.dataclass
287class BranchGeometry:287class BranchGeometry:
288 branch_index: int288 branch_index: int
289 branch_id: str289 branch_id: str
290 geometry_dir: Path290 geometry_dir: pathlib.Path
291 output_dir: Path291 output_dir: pathlib.Path
292 connected_trajectory_idx: list[int]292 connected_trajectory_idx: list[int]
293 included_trajectory_idx: list[int]293 included_trajectory_idx: list[int]
294 planes: list[geometry_tools.Plane]294 planes: list[geometry_tools.Plane]
295 segment_trajectories: dict[int, list[str]]295 segment_trajectories: dict[int, list[str]]
Importance #13: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -304,10 +304,10 @@
304304
305 def __init__(305 def __init__(
306 self,306 self,
307 *,307 *,
308 las_file: Path,308 las_file: pathlib.Path,
309 segments_base_dir: Path,309 segments_base_dir: pathlib.Path,
310 points_suffix: str,310 points_suffix: str,
311 geoshift: np.ndarray,311 geoshift: np.ndarray,
312 point_count_by_segment: dict[int, int],312 point_count_by_segment: dict[int, int],
313 field_dtypes: dict[str, np.dtype],313 field_dtypes: dict[str, np.dtype],
Importance #14: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -324,9 +324,9 @@
324 self._tmp = tempfile.TemporaryDirectory(324 self._tmp = tempfile.TemporaryDirectory(
325 prefix=f".{las_file.stem}_split_",325 prefix=f".{las_file.stem}_split_",
326 dir=str(self.segments_base_dir),326 dir=str(self.segments_base_dir),
327 )327 )
328 self.tmp_dir = Path(self._tmp.name)328 self.tmp_dir = pathlib.Path(self._tmp.name)
329329
330 def __enter__(self) -> "_SegmentSplitWriter":330 def __enter__(self) -> "_SegmentSplitWriter":
331 return self331 return self
332332
Importance #15: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -405,9 +405,9 @@
405 prefix=f".{npz_file_path.stem}_",405 prefix=f".{npz_file_path.stem}_",
406 dir=str(segment_output_dir),406 dir=str(segment_output_dir),
407 delete=False,407 delete=False,
408 ) as tmp_file:408 ) as tmp_file:
409 tmp_npz_path = Path(tmp_file.name)409 tmp_npz_path = pathlib.Path(tmp_file.name)
410410
411 try:411 try:
412 segment_tmp_dir = self.tmp_dir / f"segment_{segment_idx:03d}"412 segment_tmp_dir = self.tmp_dir / f"segment_{segment_idx:03d}"
413 with zipfile.ZipFile(413 with zipfile.ZipFile(
Importance #16: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -655,9 +655,9 @@
655 def __init__(655 def __init__(
656 self,656 self,
657 config: dict[str, Any],657 config: dict[str, Any],
658 *,658 *,
659 segments_output_root: Path | None = None,659 segments_output_root: pathlib.Path | None = None,
660 ) -> None:660 ) -> None:
661 """Initialize with a configuration dictionary."""661 """Initialize with a configuration dictionary."""
662 self.logger = logstash.get_props_logger(__name__, _log_props.LOG_PROPS).getChild(self.__class__.__name__)662 self.logger = logstash.get_props_logger(__name__, _log_props.LOG_PROPS).getChild(self.__class__.__name__)
663 try:663 try:
Importance #17: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -694,13 +694,13 @@
694694
695 def build_branch_geometries(695 def build_branch_geometries(
696 self,696 self,
697 *,697 *,
698 trajectories: list[Path],698 trajectories: list[pathlib.Path],
699 connected_trajectory_branches: list[list[int]],699 connected_trajectory_branches: list[list[int]],
700 output_dir: Path,700 output_dir: pathlib.Path,
701 included_trajectory_branches: list[list[int]] | None = None,701 included_trajectory_branches: list[list[int]] | None = None,
702 source_las_by_stem: dict[str, Path | str] | None = None,702 source_las_by_stem: dict[str, pathlib.Path | str] | None = None,
703 ) -> dict[str, Any]:703 ) -> dict[str, Any]:
704 """704 """
705 Build Step 3 geometry for multiple independent spine branches.705 Build Step 3 geometry for multiple independent spine branches.
706706
Importance #18: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -879,26 +879,26 @@
879 entry = {879 entry = {
880 "branch_index": branch_index,880 "branch_index": branch_index,
881 "branch_id": branch_id,881 "branch_id": branch_id,
882 "connected_trajectory_idx": branch,882 "connected_trajectory_idx": branch,
883 "geometry_dir": str(Path("branches") / branch_id),883 "geometry_dir": str(pathlib.Path("branches") / branch_id),
884 "geoshift_file": str(884 "geoshift_file": str(
885 Path("branches") / branch_id / "lane_points" / geoshift_fn885 pathlib.Path("branches") / branch_id / "lane_points" / geoshift_fn
886 ),886 ),
887 "planes_file": str(887 "planes_file": str(
888 Path("branches")888 pathlib.Path("branches")
889 / branch_id889 / branch_id
890 / "lane_points"890 / "lane_points"
891 / planes_path.name891 / planes_path.name
892 ),892 ),
893 "longitudinal_limit_planes_file": str(893 "longitudinal_limit_planes_file": str(
894 Path("branches")894 pathlib.Path("branches")
895 / branch_id895 / branch_id
896 / "lane_points"896 / "lane_points"
897 / longitudinal_planes_path.name897 / longitudinal_planes_path.name
898 ),898 ),
899 "segment_trajectories_file": str(899 "segment_trajectories_file": str(
900 Path("branches") / branch_id / "segment_trajectories.json"900 pathlib.Path("branches") / branch_id / "segment_trajectories.json"
901 ),901 ),
902 "plane_count": len(branch_mapper.planes),902 "plane_count": len(branch_mapper.planes),
903 "mapped_segments": len(branch_mapper.segment_trajectories),903 "mapped_segments": len(branch_mapper.segment_trajectories),
904 "mapped_las_files": len(mapped_las_files),904 "mapped_las_files": len(mapped_las_files),
Importance #19: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -939,19 +939,19 @@
939939
940 def load_branch_geometries(940 def load_branch_geometries(
941 self,941 self,
942 *,942 *,
943 geometry_root: Path,943 geometry_root: pathlib.Path,
944 output_dir: Path | None = None,944 output_dir: pathlib.Path | None = None,
945 source_las_by_stem: dict[str, Path | str] | None = None,945 source_las_by_stem: dict[str, pathlib.Path | str] | None = None,
946 ) -> dict[str, Any]:946 ) -> dict[str, Any]:
947 """Load branch geometry produced by `build_branch_geometries`."""947 """Load branch geometry produced by `build_branch_geometries`."""
948 logger = self.logger.getChild("load_branch_geometries")948 logger = self.logger.getChild("load_branch_geometries")
949 manifest_path = geometry_root / "manifests" / "branches.json"949 manifest_path = geometry_root / "manifests" / "branches.json"
950 manifest = _load_json(manifest_path)950 manifest = _load_json(manifest_path)
951 output_root = output_dir or geometry_root951 output_root = output_dir or geometry_root
952952
953 shared_geoshift_file = Path(953 shared_geoshift_file = pathlib.Path(
954 str(manifest.get("shared_geoshift_file", "lane_points/run3_geoshift.json"))954 str(manifest.get("shared_geoshift_file", "lane_points/run3_geoshift.json"))
955 )955 )
956 shared_geoshift_source = geometry_root / shared_geoshift_file956 shared_geoshift_source = geometry_root / shared_geoshift_file
957 self.geoshift = _load_geoshift_from_json(shared_geoshift_source)957 self.geoshift = _load_geoshift_from_json(shared_geoshift_source)
Importance #20: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1000,9 +1000,9 @@
1000 _copy_file_atomic(planes_file, output_root / str(entry["planes_file"]))1000 _copy_file_atomic(planes_file, output_root / str(entry["planes_file"]))
1001 longitudinal_rel = str(1001 longitudinal_rel = str(
1002 entry.get(1002 entry.get(
1003 "longitudinal_limit_planes_file",1003 "longitudinal_limit_planes_file",
1004 Path(entry["geometry_dir"])1004 pathlib.Path(entry["geometry_dir"])
1005 / "lane_points"1005 / "lane_points"
1006 / "run3_longitudinal_limit_planes.npz",1006 / "run3_longitudinal_limit_planes.npz",
1007 )1007 )
1008 )1008 )
Importance #21: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1050,9 +1050,9 @@
1050 manifest_path,1050 manifest_path,
1051 )1051 )
1052 return manifest1052 return manifest
10531053
1054 def divide_las_file_by_branch_planes(self, las_file: Path) -> dict[str, list[int]]:1054 def divide_las_file_by_branch_planes(self, las_file: pathlib.Path) -> dict[str, list[int]]:
1055 """1055 """
1056 Divide one LAS file against every loaded branch geometry that references it.1056 Divide one LAS file against every loaded branch geometry that references it.
10571057
1058 The same LAS can be processed by multiple branches. Outputs are written under1058 The same LAS can be processed by multiple branches. Outputs are written under
Importance #22: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1114,9 +1114,9 @@
1114 return processed1114 return processed
11151115
1116 def _segment_indices_for_las_in_mapping(1116 def _segment_indices_for_las_in_mapping(
1117 self,1117 self,
1118 las_file: Path,1118 las_file: pathlib.Path,
1119 segment_trajectories: dict[int, list[str]],1119 segment_trajectories: dict[int, list[str]],
1120 ) -> list[int]:1120 ) -> list[int]:
1121 las_str = str(las_file)1121 las_str = str(las_file)
1122 try:1122 try:
Importance #23: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1127,9 +1127,9 @@
1127 exact_matches: list[int] = []1127 exact_matches: list[int] = []
1128 stem_matches: list[int] = []1128 stem_matches: list[int] = []
1129 for segment_idx, las_paths in segment_trajectories.items():1129 for segment_idx, las_paths in segment_trajectories.items():
1130 for raw_path in las_paths:1130 for raw_path in las_paths:
1131 candidate = Path(str(raw_path))1131 candidate = pathlib.Path(str(raw_path))
1132 if str(candidate) == las_str:1132 if str(candidate) == las_str:
1133 exact_matches.append(int(segment_idx))1133 exact_matches.append(int(segment_idx))
1134 break1134 break
1135 try:1135 try:
Importance #24: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1147,16 +1147,16 @@
1147 return sorted(set(stem_matches))1147 return sorted(set(stem_matches))
11481148
1149 def _segment_trajectories_for_current_las(1149 def _segment_trajectories_for_current_las(
1150 self,1150 self,
1151 las_file: Path,1151 las_file: pathlib.Path,
1152 segment_trajectories: dict[int, list[str]],1152 segment_trajectories: dict[int, list[str]],
1153 ) -> dict[int, list[str]]:1153 ) -> dict[int, list[str]]:
1154 rewritten: dict[int, list[str]] = {}1154 rewritten: dict[int, list[str]] = {}
1155 for segment_idx, las_paths in segment_trajectories.items():1155 for segment_idx, las_paths in segment_trajectories.items():
1156 rewritten_paths = [1156 rewritten_paths = [
1157 str(las_file)1157 str(las_file)
1158 if Path(str(raw_path)).stem == las_file.stem1158 if pathlib.Path(str(raw_path)).stem == las_file.stem
1159 else str(raw_path)1159 else str(raw_path)
1160 for raw_path in las_paths1160 for raw_path in las_paths
1161 ]1161 ]
1162 rewritten[int(segment_idx)] = _deduplicate_keep_order(rewritten_paths)1162 rewritten[int(segment_idx)] = _deduplicate_keep_order(rewritten_paths)
Importance #25: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1166,9 +1166,9 @@
1166 self,1166 self,
1167 branch: BranchGeometry,1167 branch: BranchGeometry,
1168 segment_indices: list[int],1168 segment_indices: list[int],
1169 ) -> None:1169 ) -> None:
1170 segments_base_dir_name = Path(1170 segments_base_dir_name = pathlib.Path(
1171 str(self.config.get("segments_base_dir_name", "lane_points"))1171 str(self.config.get("segments_base_dir_name", "lane_points"))
1172 )1172 )
1173 if segments_base_dir_name.is_absolute():1173 if segments_base_dir_name.is_absolute():
1174 segments_base_dir = segments_base_dir_name1174 segments_base_dir = segments_base_dir_name
Importance #26: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1184,11 +1184,11 @@
1184 )1184 )
11851185
1186 def process(1186 def process(
1187 self,1187 self,
1188 trajectories: list[Path],1188 trajectories: list[pathlib.Path],
1189 connected_trajectory_idx: list[int],1189 connected_trajectory_idx: list[int],
1190 output_dir: Path,1190 output_dir: pathlib.Path,
1191 ) -> None:1191 ) -> None:
1192 """1192 """
1193 Build segment planes from connected trajectory splines, map segments to LAS files,1193 Build segment planes from connected trajectory splines, map segments to LAS files,
1194 and optionally save per-segment point subsets.1194 and optionally save per-segment point subsets.
Importance #27: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1429,9 +1429,9 @@
14291429
1430 for i, plane in enumerate(self.planes[:-1]):1430 for i, plane in enumerate(self.planes[:-1]):
1431 logger.debug(f"Segment {i}")1431 logger.debug(f"Segment {i}")
14321432
1433 intercepting_las_files: set[Path] = set()1433 intercepting_las_files: set[pathlib.Path] = set()
1434 for spline_info in splines_info:1434 for spline_info in splines_info:
1435 spline = spline_info["spline"]1435 spline = spline_info["spline"]
1436 intersections1 = spline.plane_intersections(plane)1436 intersections1 = spline.plane_intersections(plane)
1437 intersection_distances1 = np.array(1437 intersection_distances1 = np.array(
Importance #28: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1550,9 +1550,9 @@
1550 len(splines_info),1550 len(splines_info),
1551 len(target_las_paths),1551 len(target_las_paths),
1552 )1552 )
15531553
1554 las_files_to_divide = [Path(str(spline_info["las_file"])) for spline_info in splines_info]1554 las_files_to_divide = [pathlib.Path(str(spline_info["las_file"])) for spline_info in splines_info]
1555 requested_workers = int(self.config.get("max_parallel_las_files", 2))1555 requested_workers = int(self.config.get("max_parallel_las_files", 2))
1556 cpu_count = os.cpu_count() or 11556 cpu_count = os.cpu_count() or 1
1557 max_workers = max(1, min(requested_workers, cpu_count))1557 max_workers = max(1, min(requested_workers, cpu_count))
15581558
Importance #29: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1569,9 +1569,9 @@
1569 logger.info(f"Dividing {spline_info['las_file'].name} by planes")1569 logger.info(f"Dividing {spline_info['las_file'].name} by planes")
1570 logger.info("----------------------------------------------------------")1570 logger.info("----------------------------------------------------------")
1571 logger.debug(f"Spline length: {spline_info['spline'].length}")1571 logger.debug(f"Spline length: {spline_info['spline'].length}")
15721572
1573 self.divide_las_file_by_planes(Path(str(spline_info["las_file"])))1573 self.divide_las_file_by_planes(pathlib.Path(str(spline_info["las_file"])))
1574 else:1574 else:
1575 logger.info(1575 logger.info(
1576 "Parallel LAS splitting enabled for %d files (workers=%d)",1576 "Parallel LAS splitting enabled for %d files (workers=%d)",
1577 len(las_files_to_divide),1577 len(las_files_to_divide),
Importance #30: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1822,9 +1822,9 @@
18221822
1823 def _collect_las_split_attempt(1823 def _collect_las_split_attempt(
1824 self,1824 self,
1825 *,1825 *,
1826 las_file: Path,1826 las_file: pathlib.Path,
1827 division_planes_np: list[tuple[np.ndarray, np.ndarray]],1827 division_planes_np: list[tuple[np.ndarray, np.ndarray]],
1828 starting_plane_idx: int,1828 starting_plane_idx: int,
1829 angle_limit: int | None,1829 angle_limit: int | None,
1830 las_points_per_chunk: int,1830 las_points_per_chunk: int,
Importance #31: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1847,10 +1847,10 @@
18471847
1848 def _write_las_split_outputs(1848 def _write_las_split_outputs(
1849 self,1849 self,
1850 *,1850 *,
1851 las_file: Path,1851 las_file: pathlib.Path,
1852 segments_base_dir: Path,1852 segments_base_dir: pathlib.Path,
1853 points_suffix: str,1853 points_suffix: str,
1854 point_count_by_segment: dict[int, int],1854 point_count_by_segment: dict[int, int],
1855 field_dtypes: dict[str, np.dtype],1855 field_dtypes: dict[str, np.dtype],
1856 division_planes_np: list[tuple[np.ndarray, np.ndarray]],1856 division_planes_np: list[tuple[np.ndarray, np.ndarray]],
Importance #32: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -1896,9 +1896,9 @@
18961896
1897 def _process_las_split_attempt(1897 def _process_las_split_attempt(
1898 self,1898 self,
1899 *,1899 *,
1900 las_file: Path,1900 las_file: pathlib.Path,
1901 division_planes_np: list[tuple[np.ndarray, np.ndarray]],1901 division_planes_np: list[tuple[np.ndarray, np.ndarray]],
1902 starting_plane_idx: int,1902 starting_plane_idx: int,
1903 angle_limit: int | None,1903 angle_limit: int | None,
1904 las_points_per_chunk: int,1904 las_points_per_chunk: int,
Importance #33: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2151,9 +2151,9 @@
2151 after_overflow=after_overflow,2151 after_overflow=after_overflow,
2152 non_prefix_rejected_points=non_prefix_rejected_points,2152 non_prefix_rejected_points=non_prefix_rejected_points,
2153 )2153 )
21542154
2155 def _save_longitudinal_limit_planes(self, planes_path: Path) -> None:2155 def _save_longitudinal_limit_planes(self, planes_path: pathlib.Path) -> None:
2156 planes_path.parent.mkdir(parents=True, exist_ok=True)2156 planes_path.parent.mkdir(parents=True, exist_ok=True)
2157 archive_data: dict[str, np.ndarray] = {}2157 archive_data: dict[str, np.ndarray] = {}
2158 for segment_idx in sorted(self.longitudinal_limit_planes_by_segment):2158 for segment_idx in sorted(self.longitudinal_limit_planes_by_segment):
2159 left_plane, right_plane = self.longitudinal_limit_planes_by_segment[2159 left_plane, right_plane = self.longitudinal_limit_planes_by_segment[
Importance #34: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2270,26 +2270,26 @@
22702270
2271 def _pick_las_for_segment_coloring_viz(2271 def _pick_las_for_segment_coloring_viz(
2272 self,2272 self,
2273 splines_info: list[dict[str, Any]],2273 splines_info: list[dict[str, Any]],
2274 ) -> Path | None:2274 ) -> pathlib.Path | None:
2275 las_segment_counts: dict[str, int] = {}2275 las_segment_counts: dict[str, int] = {}
2276 for segment_idx, las_paths in self.segment_trajectories.items():2276 for segment_idx, las_paths in self.segment_trajectories.items():
2277 for las_path in las_paths:2277 for las_path in las_paths:
2278 las_segment_counts[las_path] = las_segment_counts.get(las_path, 0) + 12278 las_segment_counts[las_path] = las_segment_counts.get(las_path, 0) + 1
2279 if las_segment_counts:2279 if las_segment_counts:
2280 best_las = max(las_segment_counts.items(), key=lambda kv: kv[1])[0]2280 best_las = max(las_segment_counts.items(), key=lambda kv: kv[1])[0]
2281 return Path(best_las)2281 return pathlib.Path(best_las)
2282 for spline_info in splines_info:2282 for spline_info in splines_info:
2283 candidate = Path(str(spline_info.get("las_file", "")))2283 candidate = pathlib.Path(str(spline_info.get("las_file", "")))
2284 if candidate.exists():2284 if candidate.exists():
2285 return candidate2285 return candidate
2286 return None2286 return None
22872287
2288 def _visualize_las_segment_coloring(2288 def _visualize_las_segment_coloring(
2289 self,2289 self,
2290 connected_splines: list[o3d.geometry.PointCloud],2290 connected_splines: list[o3d.geometry.PointCloud],
2291 las_file: Path,2291 las_file: pathlib.Path,
2292 ) -> None:2292 ) -> None:
2293 logger = self.logger.getChild("visualize_las_segment_coloring")2293 logger = self.logger.getChild("visualize_las_segment_coloring")
2294 logger.info("Loading %s for segment-coloring visualization", las_file.name)2294 logger.info("Loading %s for segment-coloring visualization", las_file.name)
2295 las = laspy.read(str(las_file))2295 las = laspy.read(str(las_file))
Importance #35: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2389,9 +2389,9 @@
2389 "other rejected points are black"2389 "other rejected points are black"
2390 ),2390 ),
2391 )2391 )
23922392
2393 def _save_longitudinal_limit_planes(self, planes_path: Path) -> None:2393 def _save_longitudinal_limit_planes(self, planes_path: pathlib.Path) -> None:
2394 planes_path.parent.mkdir(parents=True, exist_ok=True)2394 planes_path.parent.mkdir(parents=True, exist_ok=True)
2395 archive_data: dict[str, np.ndarray] = {}2395 archive_data: dict[str, np.ndarray] = {}
2396 for segment_idx in sorted(self.longitudinal_limit_planes_by_segment):2396 for segment_idx in sorted(self.longitudinal_limit_planes_by_segment):
2397 left_plane, right_plane = self.longitudinal_limit_planes_by_segment[2397 left_plane, right_plane = self.longitudinal_limit_planes_by_segment[
Importance #36: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2506,9 +2506,9 @@
2506 return [str(n) for n in dtype_names]2506 return [str(n) for n in dtype_names]
2507 return names2507 return names
25082508
2509 @staticmethod2509 @staticmethod
2510 def _chunk_scan_angle(chunk: Any, las_file: Path, logger: logging.Logger) -> np.ndarray:2510 def _chunk_scan_angle(chunk: Any, las_file: pathlib.Path, logger: logging.Logger) -> np.ndarray:
2511 """2511 """
2512 Return scan-angle values from a chunk.2512 Return scan-angle values from a chunk.
25132513
2514 LAS point formats vary between `scan_angle_rank` (legacy) and `scan_angle`2514 LAS point formats vary between `scan_angle_rank` (legacy) and `scan_angle`
Importance #37: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2528,9 +2528,9 @@
2528 f"available={SegmentMapper._chunk_field_names(chunk)}"2528 f"available={SegmentMapper._chunk_field_names(chunk)}"
2529 )2529 )
25302530
2531 @staticmethod2531 @staticmethod
2532 def _chunk_rgb(chunk: Any, las_file: Path) -> tuple[np.ndarray, np.ndarray, np.ndarray]:2532 def _chunk_rgb(chunk: Any, las_file: pathlib.Path) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
2533 """Return RGB arrays for a LAS chunk."""2533 """Return RGB arrays for a LAS chunk."""
2534 if all(hasattr(chunk, attr) for attr in ("red", "green", "blue")):2534 if all(hasattr(chunk, attr) for attr in ("red", "green", "blue")):
2535 return (2535 return (
2536 np.asarray(chunk.red),2536 np.asarray(chunk.red),
Importance #38: src/iolabs_point_cloud_segmentation_trajectory/segment_mapper.py @@ -2547,9 +2547,9 @@
2547 @staticmethod2547 @staticmethod
2548 def _chunk_field_arrays(2548 def _chunk_field_arrays(
2549 chunk: Any,2549 chunk: Any,
2550 *,2550 *,
2551 las_file: Path,2551 las_file: pathlib.Path,
2552 point_count: int,2552 point_count: int,
2553 chunk_count: int,2553 chunk_count: int,
2554 logger: logging.Logger,2554 logger: logging.Logger,
2555 ) -> dict[str, np.ndarray]:2555 ) -> dict[str, np.ndarray]:
Importance #39: tests/test_number_of_returns.py @@ -1,8 +1,8 @@
1import dataclasses1import dataclasses
2import logging2import logging
3import pathlib
3import types4import types
4from pathlib import Path
55
6import laspy6import laspy
7import numpy as np7import numpy as np
8import pytest8import pytest
Importance #40: tests/test_number_of_returns.py @@ -46,9 +46,9 @@
4646
4747
48def _split_one_las(48def _split_one_las(
49 monkeypatch: pytest.MonkeyPatch,49 monkeypatch: pytest.MonkeyPatch,
50 tmp_path: Path,50 tmp_path: pathlib.Path,
51 number_of_returns: np.ndarray | None,51 number_of_returns: np.ndarray | None,
52) -> dict[str, np.ndarray]:52) -> dict[str, np.ndarray]:
53 monkeypatch.setattr(53 monkeypatch.setattr(
54 sm.laspy,54 sm.laspy,
Importance #41: tests/test_number_of_returns.py @@ -57,9 +57,9 @@
57 )57 )
58 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)58 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)
59 mapper.geoshift = np.zeros(3, dtype=np.float64)59 mapper.geoshift = np.zeros(3, dtype=np.float64)
60 logger = logging.getLogger("test")60 logger = logging.getLogger("test")
61 las_file = Path("Record001.las")61 las_file = pathlib.Path("Record001.las")
6262
63 result = mapper._collect_las_split_attempt(63 result = mapper._collect_las_split_attempt(
64 las_file=las_file,64 las_file=las_file,
65 division_planes_np=DIVISION_PLANES,65 division_planes_np=DIVISION_PLANES,
Importance #42: tests/test_number_of_returns.py @@ -90,9 +90,9 @@
9090
9191
92def test_number_of_returns_is_written_per_point(92def test_number_of_returns_is_written_per_point(
93 monkeypatch: pytest.MonkeyPatch,93 monkeypatch: pytest.MonkeyPatch,
94 tmp_path: Path,94 tmp_path: pathlib.Path,
95) -> None:95) -> None:
96 record = _split_one_las(96 record = _split_one_las(
97 monkeypatch,97 monkeypatch,
98 tmp_path,98 tmp_path,
Importance #43: tests/test_number_of_returns.py @@ -108,9 +108,9 @@
108108
109109
110def test_number_of_returns_falls_back_to_zeros_when_las_lacks_field(110def test_number_of_returns_falls_back_to_zeros_when_las_lacks_field(
111 monkeypatch: pytest.MonkeyPatch,111 monkeypatch: pytest.MonkeyPatch,
112 tmp_path: Path,112 tmp_path: pathlib.Path,
113) -> None:113) -> None:
114 record = _split_one_las(monkeypatch, tmp_path, None)114 record = _split_one_las(monkeypatch, tmp_path, None)
115115
116 assert record["number_of_returns"].dtype == np.uint8116 assert record["number_of_returns"].dtype == np.uint8
Importance #44: tests/test_number_of_returns.py @@ -194,9 +194,9 @@
194 with pytest.raises(TypeError):194 with pytest.raises(TypeError):
195 sm.DEFAULT_FIELD_DTYPES["points"] = np.dtype(np.float32) # type: ignore[index]195 sm.DEFAULT_FIELD_DTYPES["points"] = np.dtype(np.float32) # type: ignore[index]
196196
197197
198def test_npz_schema_matches_common_segment_points_io(tmp_path: Path) -> None:198def test_npz_schema_matches_common_segment_points_io(tmp_path: pathlib.Path) -> None:
199 """The duplicated run3 NPZ schema must not drift from the SSOT in common.199 """The duplicated run3 NPZ schema must not drift from the SSOT in common.
200200
201 Skips against an `iolabs-common` that predates `segment_points_io`, and201 Skips against an `iolabs-common` that predates `segment_points_io`, and
202 activates by itself once the floor is raised to a release that has it.202 activates by itself once the floor is raised to a release that has it.
Importance #45: tests/test_segment_mapper_overflow.py @@ -1,26 +1,19 @@
1import logging1import logging
2from pathlib import Path2import pathlib
33
4import numpy as np4import numpy as np
55
6from iolabs_point_cloud_segmentation_trajectory import segment_mapper as sm6from iolabs_point_cloud_segmentation_trajectory import segment_mapper as sm
7from iolabs_point_cloud_segmentation_trajectory.segment_mapper import (
8 MAX_OVERFLOW_RETRIES,
9 _SegmentSplitWriter,
10 classify_plane_bucket,
11 overflow_retry_windows,
12 should_retry_overflow,
13)
147
158
16def test_overflow_buckets_are_dropped() -> None:9def test_overflow_buckets_are_dropped() -> None:
17 before_segment_idx, before_overflow = classify_plane_bucket(10 before_segment_idx, before_overflow = sm.classify_plane_bucket(
18 0,11 0,
19 starting_plane_idx=55,12 starting_plane_idx=55,
20 plane_count=7,13 plane_count=7,
21 )14 )
22 after_segment_idx, after_overflow = classify_plane_bucket(15 after_segment_idx, after_overflow = sm.classify_plane_bucket(
23 7,16 7,
24 starting_plane_idx=55,17 starting_plane_idx=55,
25 plane_count=7,18 plane_count=7,
26 )19 )
Importance #46: tests/test_segment_mapper_overflow.py @@ -32,45 +25,45 @@
3225
3326
34def test_valid_buckets_map_to_bounded_segments() -> None:27def test_valid_buckets_map_to_bounded_segments() -> None:
35 mapped_segments = [28 mapped_segments = [
36 classify_plane_bucket(mask, starting_plane_idx=55, plane_count=7)[0]29 sm.classify_plane_bucket(mask, starting_plane_idx=55, plane_count=7)[0]
37 for mask in range(1, 7)30 for mask in range(1, 7)
38 ]31 ]
3932
40 assert mapped_segments == [55, 56, 57, 58, 59, 60]33 assert mapped_segments == [55, 56, 57, 58, 59, 60]
4134
4235
43def test_retry_trigger_threshold() -> None:36def test_retry_trigger_threshold() -> None:
44 assert should_retry_overflow(101, 0)37 assert sm.should_retry_overflow(101, 0)
45 assert should_retry_overflow(0, 101)38 assert sm.should_retry_overflow(0, 101)
46 assert not should_retry_overflow(100, 100)39 assert not sm.should_retry_overflow(100, 100)
4740
4841
49def test_retry_limit_stops_after_three_doublings() -> None:42def test_retry_limit_stops_after_three_doublings() -> None:
50 windows = overflow_retry_windows(43 windows = sm.overflow_retry_windows(
51 [59, 60],44 [59, 60],
52 plane_count=200,45 plane_count=200,
53 configured_extra_planes=4,46 configured_extra_planes=4,
54 )47 )
5548
56 assert len(windows) == MAX_OVERFLOW_RETRIES + 149 assert len(windows) == sm.MAX_OVERFLOW_RETRIES + 1
57 assert [window[0] for window in windows] == [4, 8, 16, 32]50 assert [window[0] for window in windows] == [4, 8, 16, 32]
5851
5952
60def test_plane_window_cap_stops_when_all_planes_selected() -> None:53def test_plane_window_cap_stops_when_all_planes_selected() -> None:
61 windows = overflow_retry_windows(54 windows = sm.overflow_retry_windows(
62 [2, 3],55 [2, 3],
63 plane_count=6,56 plane_count=6,
64 configured_extra_planes=4,57 configured_extra_planes=4,
65 )58 )
6659
67 assert windows == [(4, 0, 5)]60 assert windows == [(4, 0, 5)]
6861
6962
70def test_segment_split_writer_streams_to_expected_npz(tmp_path: Path) -> None:63def test_segment_split_writer_streams_to_expected_npz(tmp_path: pathlib.Path) -> None:
71 writer = _SegmentSplitWriter(64 writer = sm._SegmentSplitWriter(
72 las_file=Path("Record001.las"),65 las_file=pathlib.Path("Record001.las"),
73 segments_base_dir=tmp_path / "lane_points",66 segments_base_dir=tmp_path / "lane_points",
74 points_suffix="_run3_points",67 points_suffix="_run3_points",
75 geoshift=np.array([10.0, 20.0, 30.0]),68 geoshift=np.array([10.0, 20.0, 30.0]),
76 point_count_by_segment={3: 3},69 point_count_by_segment={3: 3},
Importance #47: tests/test_segment_mapper_overflow.py @@ -179,9 +172,9 @@
179 (np.array([0.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0])),172 (np.array([0.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0])),
180 ]173 ]
181174
182 result = mapper._collect_las_split_attempt(175 result = mapper._collect_las_split_attempt(
183 las_file=Path("fake.las"),176 las_file=pathlib.Path("fake.las"),
184 division_planes_np=division_planes_np,177 division_planes_np=division_planes_np,
185 starting_plane_idx=0,178 starting_plane_idx=0,
186 angle_limit=None,179 angle_limit=None,
187 las_points_per_chunk=100,180 las_points_per_chunk=100,
Importance #48: tests/test_segment_mapper_overflow.py @@ -229,9 +222,9 @@
229 monkeypatch.setattr(sm.laspy, "open", lambda _path: reader)222 monkeypatch.setattr(sm.laspy, "open", lambda _path: reader)
230 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)223 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)
231224
232 result = mapper._collect_las_split_attempt(225 result = mapper._collect_las_split_attempt(
233 las_file=Path("fake.las"),226 las_file=pathlib.Path("fake.las"),
234 division_planes_np=[227 division_planes_np=[
235 (np.array([0.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),228 (np.array([0.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),
236 (np.array([100.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),229 (np.array([100.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),
237 ],230 ],
Importance #49: tests/test_number_of_returns.py @@ -1,8 +1,8 @@
1import dataclasses1import dataclasses
2import logging2import logging
3import pathlib
3import types4import types
4from pathlib import Path
55
6import laspy6import laspy
7import numpy as np7import numpy as np
8import pytest8import pytest
Importance #50: tests/test_number_of_returns.py @@ -46,9 +46,9 @@
4646
4747
48def _split_one_las(48def _split_one_las(
49 monkeypatch: pytest.MonkeyPatch,49 monkeypatch: pytest.MonkeyPatch,
50 tmp_path: Path,50 tmp_path: pathlib.Path,
51 number_of_returns: np.ndarray | None,51 number_of_returns: np.ndarray | None,
52) -> dict[str, np.ndarray]:52) -> dict[str, np.ndarray]:
53 monkeypatch.setattr(53 monkeypatch.setattr(
54 sm.laspy,54 sm.laspy,
Importance #51: tests/test_number_of_returns.py @@ -57,9 +57,9 @@
57 )57 )
58 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)58 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)
59 mapper.geoshift = np.zeros(3, dtype=np.float64)59 mapper.geoshift = np.zeros(3, dtype=np.float64)
60 logger = logging.getLogger("test")60 logger = logging.getLogger("test")
61 las_file = Path("Record001.las")61 las_file = pathlib.Path("Record001.las")
6262
63 result = mapper._collect_las_split_attempt(63 result = mapper._collect_las_split_attempt(
64 las_file=las_file,64 las_file=las_file,
65 division_planes_np=DIVISION_PLANES,65 division_planes_np=DIVISION_PLANES,
Importance #52: tests/test_number_of_returns.py @@ -90,9 +90,9 @@
9090
9191
92def test_number_of_returns_is_written_per_point(92def test_number_of_returns_is_written_per_point(
93 monkeypatch: pytest.MonkeyPatch,93 monkeypatch: pytest.MonkeyPatch,
94 tmp_path: Path,94 tmp_path: pathlib.Path,
95) -> None:95) -> None:
96 record = _split_one_las(96 record = _split_one_las(
97 monkeypatch,97 monkeypatch,
98 tmp_path,98 tmp_path,
Importance #53: tests/test_number_of_returns.py @@ -108,9 +108,9 @@
108108
109109
110def test_number_of_returns_falls_back_to_zeros_when_las_lacks_field(110def test_number_of_returns_falls_back_to_zeros_when_las_lacks_field(
111 monkeypatch: pytest.MonkeyPatch,111 monkeypatch: pytest.MonkeyPatch,
112 tmp_path: Path,112 tmp_path: pathlib.Path,
113) -> None:113) -> None:
114 record = _split_one_las(monkeypatch, tmp_path, None)114 record = _split_one_las(monkeypatch, tmp_path, None)
115115
116 assert record["number_of_returns"].dtype == np.uint8116 assert record["number_of_returns"].dtype == np.uint8
Importance #54: tests/test_number_of_returns.py @@ -194,9 +194,9 @@
194 with pytest.raises(TypeError):194 with pytest.raises(TypeError):
195 sm.DEFAULT_FIELD_DTYPES["points"] = np.dtype(np.float32) # type: ignore[index]195 sm.DEFAULT_FIELD_DTYPES["points"] = np.dtype(np.float32) # type: ignore[index]
196196
197197
198def test_npz_schema_matches_common_segment_points_io(tmp_path: Path) -> None:198def test_npz_schema_matches_common_segment_points_io(tmp_path: pathlib.Path) -> None:
199 """The duplicated run3 NPZ schema must not drift from the SSOT in common.199 """The duplicated run3 NPZ schema must not drift from the SSOT in common.
200200
201 Skips against an `iolabs-common` that predates `segment_points_io`, and201 Skips against an `iolabs-common` that predates `segment_points_io`, and
202 activates by itself once the floor is raised to a release that has it.202 activates by itself once the floor is raised to a release that has it.
Importance #55: tests/test_segment_mapper_overflow.py @@ -1,26 +1,19 @@
1import logging1import logging
2from pathlib import Path2import pathlib
33
4import numpy as np4import numpy as np
55
6from iolabs_point_cloud_segmentation_trajectory import segment_mapper as sm6from iolabs_point_cloud_segmentation_trajectory import segment_mapper as sm
7from iolabs_point_cloud_segmentation_trajectory.segment_mapper import (
8 MAX_OVERFLOW_RETRIES,
9 _SegmentSplitWriter,
10 classify_plane_bucket,
11 overflow_retry_windows,
12 should_retry_overflow,
13)
147
158
16def test_overflow_buckets_are_dropped() -> None:9def test_overflow_buckets_are_dropped() -> None:
17 before_segment_idx, before_overflow = classify_plane_bucket(10 before_segment_idx, before_overflow = sm.classify_plane_bucket(
18 0,11 0,
19 starting_plane_idx=55,12 starting_plane_idx=55,
20 plane_count=7,13 plane_count=7,
21 )14 )
22 after_segment_idx, after_overflow = classify_plane_bucket(15 after_segment_idx, after_overflow = sm.classify_plane_bucket(
23 7,16 7,
24 starting_plane_idx=55,17 starting_plane_idx=55,
25 plane_count=7,18 plane_count=7,
26 )19 )
Importance #56: tests/test_segment_mapper_overflow.py @@ -32,45 +25,45 @@
3225
3326
34def test_valid_buckets_map_to_bounded_segments() -> None:27def test_valid_buckets_map_to_bounded_segments() -> None:
35 mapped_segments = [28 mapped_segments = [
36 classify_plane_bucket(mask, starting_plane_idx=55, plane_count=7)[0]29 sm.classify_plane_bucket(mask, starting_plane_idx=55, plane_count=7)[0]
37 for mask in range(1, 7)30 for mask in range(1, 7)
38 ]31 ]
3932
40 assert mapped_segments == [55, 56, 57, 58, 59, 60]33 assert mapped_segments == [55, 56, 57, 58, 59, 60]
4134
4235
43def test_retry_trigger_threshold() -> None:36def test_retry_trigger_threshold() -> None:
44 assert should_retry_overflow(101, 0)37 assert sm.should_retry_overflow(101, 0)
45 assert should_retry_overflow(0, 101)38 assert sm.should_retry_overflow(0, 101)
46 assert not should_retry_overflow(100, 100)39 assert not sm.should_retry_overflow(100, 100)
4740
4841
49def test_retry_limit_stops_after_three_doublings() -> None:42def test_retry_limit_stops_after_three_doublings() -> None:
50 windows = overflow_retry_windows(43 windows = sm.overflow_retry_windows(
51 [59, 60],44 [59, 60],
52 plane_count=200,45 plane_count=200,
53 configured_extra_planes=4,46 configured_extra_planes=4,
54 )47 )
5548
56 assert len(windows) == MAX_OVERFLOW_RETRIES + 149 assert len(windows) == sm.MAX_OVERFLOW_RETRIES + 1
57 assert [window[0] for window in windows] == [4, 8, 16, 32]50 assert [window[0] for window in windows] == [4, 8, 16, 32]
5851
5952
60def test_plane_window_cap_stops_when_all_planes_selected() -> None:53def test_plane_window_cap_stops_when_all_planes_selected() -> None:
61 windows = overflow_retry_windows(54 windows = sm.overflow_retry_windows(
62 [2, 3],55 [2, 3],
63 plane_count=6,56 plane_count=6,
64 configured_extra_planes=4,57 configured_extra_planes=4,
65 )58 )
6659
67 assert windows == [(4, 0, 5)]60 assert windows == [(4, 0, 5)]
6861
6962
70def test_segment_split_writer_streams_to_expected_npz(tmp_path: Path) -> None:63def test_segment_split_writer_streams_to_expected_npz(tmp_path: pathlib.Path) -> None:
71 writer = _SegmentSplitWriter(64 writer = sm._SegmentSplitWriter(
72 las_file=Path("Record001.las"),65 las_file=pathlib.Path("Record001.las"),
73 segments_base_dir=tmp_path / "lane_points",66 segments_base_dir=tmp_path / "lane_points",
74 points_suffix="_run3_points",67 points_suffix="_run3_points",
75 geoshift=np.array([10.0, 20.0, 30.0]),68 geoshift=np.array([10.0, 20.0, 30.0]),
76 point_count_by_segment={3: 3},69 point_count_by_segment={3: 3},
Importance #57: tests/test_segment_mapper_overflow.py @@ -179,9 +172,9 @@
179 (np.array([0.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0])),172 (np.array([0.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0])),
180 ]173 ]
181174
182 result = mapper._collect_las_split_attempt(175 result = mapper._collect_las_split_attempt(
183 las_file=Path("fake.las"),176 las_file=pathlib.Path("fake.las"),
184 division_planes_np=division_planes_np,177 division_planes_np=division_planes_np,
185 starting_plane_idx=0,178 starting_plane_idx=0,
186 angle_limit=None,179 angle_limit=None,
187 las_points_per_chunk=100,180 las_points_per_chunk=100,
Importance #58: tests/test_segment_mapper_overflow.py @@ -229,9 +222,9 @@
229 monkeypatch.setattr(sm.laspy, "open", lambda _path: reader)222 monkeypatch.setattr(sm.laspy, "open", lambda _path: reader)
230 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)223 mapper = sm.SegmentMapper.__new__(sm.SegmentMapper)
231224
232 result = mapper._collect_las_split_attempt(225 result = mapper._collect_las_split_attempt(
233 las_file=Path("fake.las"),226 las_file=pathlib.Path("fake.las"),
234 division_planes_np=[227 division_planes_np=[
235 (np.array([0.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),228 (np.array([0.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),
236 (np.array([100.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),229 (np.array([100.0, 0.0, 0.0]), np.array([1.0, 0.0, 0.0])),
237 ],230 ],