NODEDC_MISSION_CORE/tests/test_goose_qualification.py

129 lines
4.3 KiB
Python

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"]