feat(perception): split vegetation evidence layers

This commit is contained in:
DCCONSTRUCTIONS
2026-08-28 15:37:10 +03:00
parent a2c3385062
commit e10b96b546
26 changed files with 868 additions and 233 deletions
@@ -0,0 +1,139 @@
"""Seal a benchmark-only vegetation result into its archival LAB namespace."""
from __future__ import annotations
import argparse
import copy
import hashlib
import json
import shutil
import tempfile
from pathlib import Path, PurePosixPath
from typing import Any, Final
from k1link.laboratory.evidence_registry import LaboratoryEvidenceDefinition
from k1link.laboratory.evidence_report import verify_laboratory_evidence_result
from k1link.laboratory.vegetation_shadow_lab import LAB_SCHEMA
_SOURCE_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-shadow",
runtime_relative_root=PurePosixPath("lab-v1-vegetation/results"),
result_id_prefix="lab-v1-vegetation-shadow",
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
_ARCHIVE_DEFINITION: Final = LaboratoryEvidenceDefinition(
work_id="lab-v1-vegetation-benchmark",
runtime_relative_root=PurePosixPath("lab-v1-vegetation-benchmark/results"),
result_id_prefix="lab-v1-vegetation-benchmark",
document_name="result.json",
result_schema_version=LAB_SCHEMA,
)
class VegetationBenchmarkArchiveError(ValueError):
"""The source result is not a valid benchmark-only immutable result."""
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
def _object(value: object, label: str) -> dict[str, Any]:
if not isinstance(value, dict):
raise VegetationBenchmarkArchiveError(f"{label} is invalid")
return value
def seal_vegetation_benchmark_archive(
*,
source_result_root: Path,
output_root: Path,
) -> Path:
source = source_result_root.resolve(strict=True)
verify_laboratory_evidence_result(_SOURCE_DEFINITION, source)
manifest = _object(
json.loads((source / "result.json").read_text("utf-8")),
"source result",
)
if manifest.get("route_video") is not None:
raise VegetationBenchmarkArchiveError("benchmark archive source contains route video")
artifacts = manifest.get("artifacts")
if not isinstance(artifacts, list):
raise VegetationBenchmarkArchiveError("source artifacts are invalid")
identity = copy.deepcopy(_object(manifest.get("identity"), "source identity"))
identity.update(
{
"lab_id": "lab-v1-vegetation-benchmark-archive",
"archived_from_result_id": source.name,
}
)
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"lab-v1-vegetation-benchmark-{identity_sha256}"
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
destination = output_root / result_id
if destination.exists():
verify_laboratory_evidence_result(_ARCHIVE_DEFINITION, destination)
return destination
temporary = Path(tempfile.mkdtemp(prefix=".vegetation-benchmark-", dir=output_root))
try:
for raw in artifacts:
descriptor = _object(raw, "artifact descriptor")
relative_text = descriptor.get("path")
if not isinstance(relative_text, str):
raise VegetationBenchmarkArchiveError("artifact path is invalid")
relative = PurePosixPath(relative_text)
if relative.is_absolute() or any(part in {"", ".", ".."} for part in relative.parts):
raise VegetationBenchmarkArchiveError("artifact path is unsafe")
source_path = source.joinpath(*relative.parts)
destination_path = temporary.joinpath(*relative.parts)
destination_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
shutil.copyfile(source_path, destination_path)
archived = copy.deepcopy(manifest)
archived.update(
{
"result_id": result_id,
"identity": identity,
"identity_sha256": identity_sha256,
"archived_from_result_id": source.name,
}
)
(temporary / "result.json").write_bytes(_canonical_json(archived) + b"\n")
temporary.rename(destination)
verify_laboratory_evidence_result(_ARCHIVE_DEFINITION, destination)
return destination
except Exception:
shutil.rmtree(temporary, ignore_errors=True)
raise
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--source-result-root", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
print(
seal_vegetation_benchmark_archive(
source_result_root=args.source_result_root,
output_root=args.output_root,
)
)
if __name__ == "__main__":
main()
__all__ = [
"VegetationBenchmarkArchiveError",
"seal_vegetation_benchmark_archive",
]
@@ -112,6 +112,7 @@ def seal_vegetation_policy_review(
mission_policy_path: Path,
provider_label_map_path: Path,
m49_tgs_full_shadow_root: Path,
valid_fov_mask_path: Path,
output_root: Path,
created_at_utc: str | None = None,
) -> Path:
@@ -142,6 +143,7 @@ def seal_vegetation_policy_review(
raise VegetationPolicyReviewError("fine mask archive identity changed")
raw_archive_path = base_root / "video" / "ddrnet-semantic-masks.zip"
fine_taxonomy = _object(base_route.get("taxonomy"), "fine taxonomy")
valid_fov_source = valid_fov_mask_path.resolve(strict=True)
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = Path(tempfile.mkdtemp(prefix=".lab-v1-policy-", dir=output_root))
@@ -152,11 +154,23 @@ def seal_vegetation_policy_review(
artifacts=base.get("artifacts"),
)
policy_archive = temporary / "video" / "coarse-material-policy-masks.zip"
valid_fov_destination = temporary / "video" / "valid-fov-mask.png"
valid_fov_destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
shutil.copyfile(valid_fov_source, valid_fov_destination)
valid_fov_proof = {
"role": "route-camera-valid-fov-mask",
"path": "video/valid-fov-mask.png",
"byte_length": valid_fov_destination.stat().st_size,
"sha256": sha256_path(valid_fov_destination),
"media_type": "image/png",
}
artifacts.append(valid_fov_proof)
policy_counts = build_policy_mask_archive(
source_archive=raw_archive_path,
destination_archive=policy_archive,
fine_taxonomy=fine_taxonomy,
provider_label_map=provider_map,
valid_fov_mask=valid_fov_destination,
)
policy_archive_proof = {
"role": "route-coarse-material-mask-archive",
@@ -180,6 +194,11 @@ def seal_vegetation_policy_review(
"taxonomy": policy_taxonomy(),
"aggregate_prediction_pixels": policy_counts,
"linked_tgs_result_id": tgs.result_id,
"valid_fov": {
"mask_path": valid_fov_proof["path"],
"mask_sha256": valid_fov_proof["sha256"],
"outside_valid_fov_class_id": 9,
},
"policy": {
"profile_id": mission_policy["profile_id"],
"profile_sha256": sha256_path(mission_policy_path),
@@ -196,6 +215,7 @@ def seal_vegetation_policy_review(
"spatial_safety_veto_layer": "M4.9 full TGS gravity-local costmap",
"temporal_consensus_owner": "TGS causal rolling 1 s and metric obstacle tracks",
"camera_semantic_temporal_filter": "none",
"camera_valid_fov_filter": "sealed exact KB4 valid-FOV mask",
"reason": "No admitted TGS-to-camera pixel projection exists.",
},
}
@@ -238,7 +258,7 @@ def seal_vegetation_policy_review(
"Vegetation semantics never clears YOLOX, LiDAR, metric obstacle "
"or TGS vetoes."
),
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
"Pixels outside the exact KB4 valid FOV are transparent UNOBSERVED evidence.",
(
"TGS remains in gravity-local space; no uncalibrated pixel "
"projection is fabricated."
@@ -269,6 +289,7 @@ def main() -> None:
parser.add_argument("--mission-policy-path", type=Path, required=True)
parser.add_argument("--provider-label-map-path", type=Path, required=True)
parser.add_argument("--m49-tgs-full-shadow-root", type=Path, required=True)
parser.add_argument("--valid-fov-mask-path", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
print(seal_vegetation_policy_review(**vars(args)))
@@ -90,6 +90,14 @@ POLICY_CLASSES: Final = (
"material_class": "vegetation_unknown",
"evidence_state": "VEGETATION_UNKNOWN",
},
{
"class_id": 9,
"label": "OUTSIDE VALID FOV · NO SENSOR EVIDENCE",
"color_rgb": [0, 0, 0],
"disposition": "undefined",
"material_class": None,
"evidence_state": "UNOBSERVED",
},
)
_MATERIAL_TO_CLASS: Final = {
@@ -155,10 +163,18 @@ def build_policy_mask_archive(
destination_archive: Path,
fine_taxonomy: dict[str, object],
provider_label_map: dict[str, Any],
valid_fov_mask: Path,
) -> list[int]:
"""Map every fine mask to coarse evidence; safety vetoes remain separate layers."""
lut = fine_to_policy_lut(fine_taxonomy, provider_label_map)
try:
with Image.open(valid_fov_mask) as image:
valid_fov = np.asarray(image.convert("L"), dtype=np.uint8) > 0
except OSError as exc:
raise VegetationPolicyVideoError("valid-FOV mask is unreadable") from exc
if valid_fov.shape != (HEIGHT, WIDTH) or not np.any(valid_fov) or np.all(valid_fov):
raise VegetationPolicyVideoError("valid-FOV mask geometry is invalid")
counts = np.zeros(len(POLICY_CLASSES), dtype=np.int64)
destination_archive.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
try:
@@ -175,6 +191,7 @@ def build_policy_mask_archive(
f"fine mask {member} has shape {fine.shape}, expected {(HEIGHT, WIDTH)}"
)
coarse = lut[fine]
coarse[~valid_fov] = 9
counts += np.bincount(
coarse.reshape(-1),
minlength=len(POLICY_CLASSES),
+28 -1
View File
@@ -327,6 +327,7 @@ def seal_vegetation_shadow_lab(
mission_policy_path: Path | None = None,
provider_label_map_path: Path | None = None,
m49_tgs_full_shadow_root: Path | None = None,
valid_fov_mask_path: Path | None = None,
) -> Path:
roots = {
("ddrnet", "goose"): ddrnet_goose_root.resolve(),
@@ -349,6 +350,7 @@ def seal_vegetation_shadow_lab(
mission_policy_path,
provider_label_map_path,
m49_tgs_full_shadow_root,
valid_fov_mask_path,
)
if any(value is not None for value in policy_inputs) and not all(
value is not None for value in policy_inputs
@@ -385,6 +387,7 @@ def seal_vegetation_shadow_lab(
mission_policy_path is not None
and provider_label_map_path is not None
and m49_tgs_full_shadow_root is not None
and valid_fov_mask_path is not None
and route_video is not None
):
repository_root = mission_policy_path.resolve().parents[2]
@@ -543,13 +546,25 @@ def seal_vegetation_shadow_lab(
and linked_tgs_result_id is not None
and mission_policy_path is not None
and provider_label_map_path is not None
and valid_fov_mask_path is not None
):
policy_archive = temporary / "video" / "coarse-material-policy-masks.zip"
valid_fov_destination = temporary / "video" / "valid-fov-mask.png"
shutil.copyfile(valid_fov_mask_path.resolve(strict=True), valid_fov_destination)
valid_fov_descriptor = {
"role": "route-camera-valid-fov-mask",
"path": "video/valid-fov-mask.png",
"byte_length": valid_fov_destination.stat().st_size,
"sha256": sha256_path(valid_fov_destination),
"media_type": "image/png",
}
artifacts.append(valid_fov_descriptor)
policy_counts = build_policy_mask_archive(
source_archive=route_video_archive,
destination_archive=policy_archive,
fine_taxonomy=_object(route_video["taxonomy"], "fine video taxonomy"),
provider_label_map=provider_label_map,
valid_fov_mask=valid_fov_destination,
)
policy_descriptor = {
"role": "route-coarse-material-mask-archive",
@@ -571,6 +586,11 @@ def seal_vegetation_shadow_lab(
"taxonomy": policy_taxonomy(),
"aggregate_prediction_pixels": policy_counts,
"linked_tgs_result_id": linked_tgs_result_id,
"valid_fov": {
"mask_path": valid_fov_descriptor["path"],
"mask_sha256": valid_fov_descriptor["sha256"],
"outside_valid_fov_class_id": 9,
},
"policy": {
"profile_id": mission_policy["profile_id"],
"profile_sha256": sha256_path(mission_policy_path),
@@ -591,6 +611,7 @@ def seal_vegetation_shadow_lab(
"TGS causal rolling 1 s and metric obstacle tracks"
),
"camera_semantic_temporal_filter": "none",
"camera_valid_fov_filter": "sealed exact KB4 valid-FOV mask",
"reason": "No admitted TGS-to-camera pixel projection exists.",
},
}
@@ -680,7 +701,11 @@ def seal_vegetation_shadow_lab(
)
),
"Vegetation semantics never clears rigid LiDAR/TGS occupancy.",
"Undefined pixels outside the 600x600 center crop remain fail-closed.",
(
"Pixels outside the exact KB4 valid FOV are transparent UNOBSERVED evidence."
if mission_policy is not None
else "Undefined pixels outside the 600x600 center crop remain fail-closed."
),
*(
[
(
@@ -723,6 +748,7 @@ def _parse_args() -> argparse.Namespace:
parser.add_argument("--mission-policy-path", type=Path)
parser.add_argument("--provider-label-map-path", type=Path)
parser.add_argument("--m49-tgs-full-shadow-root", type=Path)
parser.add_argument("--valid-fov-mask-path", type=Path)
return parser.parse_args()
@@ -739,6 +765,7 @@ def main() -> None:
mission_policy_path=args.mission_policy_path,
provider_label_map_path=args.provider_label_map_path,
m49_tgs_full_shadow_root=args.m49_tgs_full_shadow_root,
valid_fov_mask_path=args.valid_fov_mask_path,
)
print(destination)