feat(polygon): qualify GOOSE ground providers
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import struct
|
||||
import zipfile
|
||||
from collections.abc import Mapping
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import pytest
|
||||
|
||||
from k1link.datasets import goose_qualification
|
||||
from k1link.datasets.goose_qualification import (
|
||||
DegradationProfile,
|
||||
qualify_goose_ground,
|
||||
)
|
||||
from k1link.ground_segmentation import GroundSegmentation
|
||||
from k1link.simulation import QualificationRunStore, RunState
|
||||
|
||||
|
||||
class _NeverGround:
|
||||
@property
|
||||
def identity(self) -> Mapping[str, object]:
|
||||
return {"provider_id": "test-current", "revision": "1"}
|
||||
|
||||
def segment(self, xyzi: npt.NDArray[np.float32]) -> GroundSegmentation:
|
||||
count = xyzi.shape[0]
|
||||
return GroundSegmentation(
|
||||
ground_mask=np.zeros(count, dtype=np.bool_),
|
||||
assigned_mask=np.ones(count, dtype=np.bool_),
|
||||
latency_ms=2.0,
|
||||
)
|
||||
|
||||
|
||||
class _NegativeZGround:
|
||||
@property
|
||||
def identity(self) -> Mapping[str, object]:
|
||||
return {"provider_id": "test-patchwork", "revision": "1"}
|
||||
|
||||
def segment(self, xyzi: npt.NDArray[np.float32]) -> GroundSegmentation:
|
||||
count = xyzi.shape[0]
|
||||
return GroundSegmentation(
|
||||
ground_mask=np.asarray(xyzi[:, 2] < 0, dtype=np.bool_),
|
||||
assigned_mask=np.ones(count, dtype=np.bool_),
|
||||
latency_ms=3.0,
|
||||
)
|
||||
|
||||
|
||||
def _frame_bytes(offset: float) -> tuple[bytes, bytes]:
|
||||
points = (
|
||||
(-1.0 + offset, 0.0, -2.0, 0.1),
|
||||
(0.0 + offset, 0.0, -2.0, 0.2),
|
||||
(1.0 + offset, 0.0, 2.0, 0.3),
|
||||
(2.0 + offset, 0.0, 2.0, 0.4),
|
||||
)
|
||||
labels = (1, 3, 2, 2)
|
||||
return (
|
||||
b"".join(struct.pack("<ffff", *point) for point in points),
|
||||
b"".join(struct.pack("<I", label) for label in labels),
|
||||
)
|
||||
|
||||
|
||||
def _dataset(tmp_path: Path) -> Path:
|
||||
root = tmp_path / "datasets"
|
||||
archive_sha = "a" * 64
|
||||
archive = root / "goose-3d/v2025-08-22/archives/goose_3d_val.zip"
|
||||
archive.parent.mkdir(parents=True)
|
||||
with zipfile.ZipFile(archive, "w") as target:
|
||||
for index in range(2):
|
||||
frame_id = f"2022-01-01_flight__0001_{index:019d}"
|
||||
points, labels = _frame_bytes(index * 0.1)
|
||||
target.writestr(f"goose/lidar/val/{frame_id}_vls128.bin", points)
|
||||
target.writestr(f"goose/labels/val/{frame_id}_goose.label", labels)
|
||||
state = root / "state/goose-3d-v2025-08-22.json"
|
||||
state.parent.mkdir(parents=True)
|
||||
state.write_text(json.dumps({"archive": {"sha256": archive_sha}}), encoding="utf-8")
|
||||
install = root / "goose-3d/v2025-08-22/installs" / archive_sha
|
||||
install.mkdir(parents=True)
|
||||
(install / "goose_label_mapping.csv").write_text(
|
||||
"class_name,label_key,hex\nasphalt,1,#111111\nrock,2,#222222\nsoil,3,#333333\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return root
|
||||
|
||||
|
||||
def test_full_split_qualification_is_sealed_and_resumable(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
root = _dataset(tmp_path)
|
||||
runs_root = tmp_path / "runs"
|
||||
monkeypatch.setattr(goose_qualification, "_is_worker_dataset_root", lambda _: True)
|
||||
|
||||
report = qualify_goose_ground(
|
||||
root,
|
||||
runs_root,
|
||||
mission_core_commit="1" * 40,
|
||||
parallel_workers=1,
|
||||
expected_frame_count=2,
|
||||
current_segmenter=_NeverGround(),
|
||||
patchwork_segmenter=_NegativeZGround(),
|
||||
degradations=(DegradationProfile("range-20m", "maximum-range-m", 20.0),),
|
||||
)
|
||||
|
||||
assert report["frame_count"] == 2
|
||||
assert report["decision"]["status"] == "shadow-candidate"
|
||||
assert report["aggregates"]["current"]["micro"]["ground_iou"] == 0
|
||||
assert report["aggregates"]["patchworkpp"]["micro"]["ground_iou"] == 1
|
||||
assert len(report["frames"]) == 2
|
||||
run = QualificationRunStore(runs_root, read_only=True).load(report["run_id"])
|
||||
assert run.state is RunState.COMPLETED
|
||||
assert {artifact.kind for artifact in run.artifacts} == {
|
||||
"goose-ground-qualification-report",
|
||||
"goose-ground-qualification-failure-preview",
|
||||
}
|
||||
|
||||
resumed = qualify_goose_ground(
|
||||
root,
|
||||
runs_root,
|
||||
mission_core_commit="1" * 40,
|
||||
parallel_workers=1,
|
||||
expected_frame_count=2,
|
||||
current_segmenter=_NeverGround(),
|
||||
patchwork_segmenter=_NegativeZGround(),
|
||||
degradations=(DegradationProfile("range-20m", "maximum-range-m", 20.0),),
|
||||
)
|
||||
assert resumed["identity_sha256"] == report["identity_sha256"]
|
||||
Reference in New Issue
Block a user