Back to report index

Step 3 segmentationtrajectory 30dc2f2: AI3D-382 Document the schema-driven NPZ field tuple

Miroslav Simko <ms@iolabs.ch> 2026-09-01T14:54:28+02:00

Commit #97 · 1 snippets

 CLAUDE.md | 2 ++
 1 file changed, 2 insertions(+)
Importance #1: CLAUDE.md @@ -34,8 +34,10 @@
34 6. If `save_points_between_planes`, call `divide_las_file_by_planes` for each LAS via a `ThreadPoolExecutor(max_workers=max_parallel_las_files)` (capped at `os.cpu_count()`). Per-segment `.npz` files land under `<segments_base_dir>/segment_NNN/` (3-digit zero-padded index, matching the `point{i:03d}`/`normal{i:03d}` keys in `run3_planes.npz`) and `save_version_json` drops a `run3_versions.json` next to them.34 6. If `save_points_between_planes`, call `divide_las_file_by_planes` for each LAS via a `ThreadPoolExecutor(max_workers=max_parallel_las_files)` (capped at `os.cpu_count()`). Per-segment `.npz` files land under `<segments_base_dir>/segment_NNN/` (3-digit zero-padded index, matching the `point{i:03d}`/`normal{i:03d}` keys in `run3_planes.npz`) and `save_version_json` drops a `run3_versions.json` next to them.
3535
36- **`divide_las_file_by_planes`** streams the LAS via `laspy.open(...).chunk_iterator(las_points_per_chunk)`, never loading the whole file. Per chunk: optional `|scan_angle| < angle_limit` filter, then a sign-count mask against all selected planes assigns each point to a segment bucket. Scan-angle field is auto-detected (`scan_angle_rank` legacy vs `scan_angle` newer); RGB and intensity are required and the code raises if missing. Each per-segment `.npz` also carries `number_of_returns` (uint8, AI3D-382); LAS files without that field degrade to zeros, which the run3 NPZ contract reads as "unknown" (0 is not a legal LAS return count). Output points are written **geoshift-relative**.36- **`divide_las_file_by_planes`** streams the LAS via `laspy.open(...).chunk_iterator(las_points_per_chunk)`, never loading the whole file. Per chunk: optional `|scan_angle| < angle_limit` filter, then a sign-count mask against all selected planes assigns each point to a segment bucket. Scan-angle field is auto-detected (`scan_angle_rank` legacy vs `scan_angle` newer); RGB and intensity are required and the code raises if missing. Each per-segment `.npz` also carries `number_of_returns` (uint8, AI3D-382); LAS files without that field degrade to zeros, which the run3 NPZ contract reads as "unknown" (0 is not a legal LAS return count). Output points are written **geoshift-relative**.
3737
38 The per-point ancillary arrays travel as a single `dict[str, np.ndarray]` keyed by the module-level `ANCILLARY_FIELD_NAMES` tuple (`points` stays separate as the `(N, 3)` geometry array; `SEGMENT_NPZ_FIELD_NAMES = ("points", *ANCILLARY_FIELD_NAMES)` fixes the NPZ member order). Extraction, the angle-limit mask, dtype registration, `_SegmentSplitWriter.write`, the memmap allocation and the archive member list all iterate that tuple, so **adding an NPZ field = one entry in `ANCILLARY_FIELD_NAMES` + one extraction line in `SegmentMapper._chunk_field_arrays`** (plus a default in `DEFAULT_FIELD_DTYPES` if the field can be absent from the source LAS).
39
38- **`_config.py`** — strict whitelist validation. `ALLOWED_SEGMENT_MAPPER_CONFIG_KEYS` and `ALLOWED_SEGMENT_MAPPER_FILE_NAMING_KEYS` are enforced; unknown keys raise `SegmentMapperConfigError`. `normalize_segment_mapper_config` fills defaults; `build_segment_mapper_config(overrides=..., config_path=...)` does deep-merge over `segment_mapper.default.json`. **When adding a new config key you must update both the whitelist set and `normalize_segment_mapper_config`'s `setdefault` block, and add a default in `segment_mapper.default.json`.**40- **`_config.py`** — strict whitelist validation. `ALLOWED_SEGMENT_MAPPER_CONFIG_KEYS` and `ALLOWED_SEGMENT_MAPPER_FILE_NAMING_KEYS` are enforced; unknown keys raise `SegmentMapperConfigError`. `normalize_segment_mapper_config` fills defaults; `build_segment_mapper_config(overrides=..., config_path=...)` does deep-merge over `segment_mapper.default.json`. **When adding a new config key you must update both the whitelist set and `normalize_segment_mapper_config`'s `setdefault` block, and add a default in `segment_mapper.default.json`.**
3941
40- **`segment_mapper.default.json`** — bundled defaults. It is force-included into the wheel via `[tool.hatch.build.targets.wheel.force-include]` in `pyproject.toml`; if you rename or move it, update that mapping or the installed package will be missing the file at runtime.42- **`segment_mapper.default.json`** — bundled defaults. It is force-included into the wheel via `[tool.hatch.build.targets.wheel.force-include]` in `pyproject.toml`; if you rename or move it, update that mapping or the installed package will be missing the file at runtime.
4143