feat(lidar): qualify Patchwork++ on GOOSE

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 15:22:28 +03:00
parent 951b40c870
commit 60ba64004b
15 changed files with 1082 additions and 256 deletions
+12 -102
View File
@@ -1,18 +1,14 @@
from __future__ import annotations
import hashlib
import importlib
import json
import math
import os
import platform
import re
import shutil
import time
from collections.abc import Mapping
from datetime import UTC, datetime
from pathlib import Path
from types import ModuleType
from typing import Any, Final
import numpy as np
@@ -25,9 +21,21 @@ from k1link.ground_segmentation import (
GroundSegmenter,
LocalPercentileGroundSegmenter,
)
from k1link.ground_segmentation import (
PATCHWORKPP_SOURCE_COMMIT as PATCHWORKPP_SOURCE_COMMIT,
)
from k1link.ground_segmentation import (
PATCHWORKPP_SOURCE_TAG as PATCHWORKPP_SOURCE_TAG,
)
from k1link.ground_segmentation import (
PATCHWORKPP_SOURCE_URL as PATCHWORKPP_SOURCE_URL,
)
from k1link.ground_segmentation import (
GroundSegmentationError as LidarGroundError,
)
from k1link.ground_segmentation import (
PatchworkPPGroundSegmenter as PatchworkPPGroundSegmenter,
)
from .lidar_contract import LidarContractError, sensor_frame_xyzi
from .lidar_replay import LidarReplayPackV2
@@ -42,107 +50,9 @@ LIDAR_GROUND_MANIFEST_NAME: Final = "manifest.json"
LIDAR_GROUND_ANNOTATION_LABELS_NAME: Final = "labels-template.npz"
MAX_GROUND_FRAME_POINTS: Final = 200_000
PATCHWORKPP_SOURCE_URL: Final = "https://github.com/url-kaist/patchwork-plusplus"
PATCHWORKPP_SOURCE_TAG: Final = "v1.4.1"
PATCHWORKPP_SOURCE_COMMIT: Final = "3e6903a1d5537a4cc2ace897b0bbb98a92d6014c"
_BENCHMARK_ID = re.compile(r"^ground-benchmark-[a-f0-9]{64}$")
_ANNOTATION_TEMPLATE_ID = re.compile(r"^ground-annotation-template-[a-f0-9]{64}$")
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
_GIT_SHA1 = re.compile(r"^[a-f0-9]{40}$")
class PatchworkPPGroundSegmenter:
"""Runtime-only adapter for the pinned official Patchwork++ Python binding."""
def __init__(
self,
module: ModuleType,
profile: GroundBenchmarkProfile,
*,
source_commit: str = PATCHWORKPP_SOURCE_COMMIT,
source_tag: str = PATCHWORKPP_SOURCE_TAG,
) -> None:
if _GIT_SHA1.fullmatch(source_commit) is None:
raise LidarGroundError("Patchwork++ source commit is invalid")
if source_tag != PATCHWORKPP_SOURCE_TAG:
raise LidarGroundError("Patchwork++ source tag is not admitted")
module_path_value = getattr(module, "__file__", None)
if not isinstance(module_path_value, str):
raise LidarGroundError("Patchwork++ module has no verifiable binary")
module_path = Path(module_path_value).resolve(strict=True)
params = module.Parameters()
params.sensor_height = profile.patchwork_sensor_height_proxy_m
params.min_range = profile.patchwork_minimum_range_m
params.max_range = profile.patchwork_maximum_range_m
params.enable_RNR = True
params.enable_RVPF = True
params.enable_TGR = True
params.verbose = False
self._estimator = module.patchworkpp(params)
self._identity = {
"provider_id": "patchworkpp/v1.4.1",
"source_url": PATCHWORKPP_SOURCE_URL,
"source_tag": source_tag,
"source_commit": source_commit,
"binding_version": str(getattr(module, "__version__", "unknown")),
"binary_sha256": _sha256(module_path),
"platform": platform.system().lower(),
"machine": platform.machine().lower(),
"ground_truth": False,
}
@classmethod
def load(
cls,
profile: GroundBenchmarkProfile = DEFAULT_GROUND_BENCHMARK_PROFILE,
*,
module_name: str = "pypatchworkpp",
source_commit: str = PATCHWORKPP_SOURCE_COMMIT,
source_tag: str = PATCHWORKPP_SOURCE_TAG,
) -> PatchworkPPGroundSegmenter:
try:
module = importlib.import_module(module_name)
except ImportError as exc:
raise LidarGroundError("Pinned Patchwork++ Python binding is unavailable") from exc
return cls(
module,
profile,
source_commit=source_commit,
source_tag=source_tag,
)
@property
def identity(self) -> Mapping[str, object]:
return self._identity
def segment(self, xyzi: npt.NDArray[np.float32]) -> GroundSegmentation:
points = np.ascontiguousarray(_xyzi(xyzi), dtype=np.float32)
started = time.perf_counter_ns()
self._estimator.estimateGround(points)
latency_ms = (time.perf_counter_ns() - started) / 1_000_000
ground_indices = np.asarray(
self._estimator.getGroundIndices(),
dtype=np.int64,
).reshape((-1,))
nonground_indices = np.asarray(
self._estimator.getNongroundIndices(),
dtype=np.int64,
).reshape((-1,))
_indices(ground_indices, points.shape[0], "Patchwork++ ground")
_indices(nonground_indices, points.shape[0], "Patchwork++ non-ground")
if np.intersect1d(ground_indices, nonground_indices).size:
raise LidarGroundError("Patchwork++ assigned one point twice")
ground = np.zeros(points.shape[0], dtype=np.bool_)
assigned = np.zeros(points.shape[0], dtype=np.bool_)
ground[ground_indices] = True
assigned[ground_indices] = True
assigned[nonground_indices] = True
return GroundSegmentation(
ground_mask=ground,
assigned_mask=assigned,
latency_ms=latency_ms,
)
class LidarGroundBenchmarkV1: