feat(perception): run PointPillars on RAVNOVES00

This commit is contained in:
DCCONSTRUCTIONS
2026-07-31 14:35:34 +03:00
parent b44c4b3265
commit 55b7821a5c
11 changed files with 1760 additions and 9 deletions
@@ -0,0 +1,135 @@
"""Deterministic helpers for the L3.1 PointPillars transfer on RAVNOVES00."""
from __future__ import annotations
import math
from collections.abc import Sequence
import numpy as np
import numpy.typing as npt
class L31PointPillarsRavnovesError(RuntimeError):
"""The RAVNOVES transfer input violates the frozen L3.1 contract."""
def nearest_pose_indices(
point_times_ns: npt.NDArray[np.int64],
pose_times_ns: npt.NDArray[np.int64],
) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.float64]]:
"""Bind each point frame to the nearest pose on the host monotonic clock."""
point_times = np.asarray(point_times_ns, dtype=np.int64)
pose_times = np.asarray(pose_times_ns, dtype=np.int64)
if (
point_times.ndim != 1
or pose_times.ndim != 1
or not point_times.size
or not pose_times.size
or np.any(np.diff(point_times) < 0)
or np.any(np.diff(pose_times) < 0)
):
raise L31PointPillarsRavnovesError("LiDAR/pose time axes are invalid")
right = np.searchsorted(pose_times, point_times, side="left")
right = np.clip(right, 0, pose_times.size - 1)
left = np.clip(right - 1, 0, pose_times.size - 1)
right_delta = np.abs(pose_times[right] - point_times)
left_delta = np.abs(point_times - pose_times[left])
indices = np.where(left_delta <= right_delta, left, right).astype(np.int64)
age_ms = (
np.abs(pose_times[indices] - point_times).astype(np.float64) / 1_000_000.0
)
if not np.isfinite(age_ms).all():
raise L31PointPillarsRavnovesError("LiDAR/pose binding age is invalid")
return indices, age_ms
def sensor_frame_xyzi(
points_map_xyz: npt.NDArray[np.float64],
intensities: npt.NDArray[np.uint8],
*,
position_map_xyz: npt.NDArray[np.float64],
orientation_map_from_lidar_xyzw: npt.NDArray[np.float64],
) -> npt.NDArray[np.float32]:
"""Convert verified map-frame K1 points into the model's sensor frame."""
points = np.asarray(points_map_xyz, dtype=np.float64)
intensity = np.asarray(intensities, dtype=np.uint8)
position = np.asarray(position_map_xyz, dtype=np.float64)
quaternion = np.asarray(orientation_map_from_lidar_xyzw, dtype=np.float64)
if (
points.ndim != 2
or points.shape[1:] != (3,)
or intensity.shape != (points.shape[0],)
or position.shape != (3,)
or quaternion.shape != (4,)
or not np.isfinite(points).all()
or not np.isfinite(position).all()
or not np.isfinite(quaternion).all()
):
raise L31PointPillarsRavnovesError("K1 point/pose arrays are invalid")
norm = float(np.linalg.norm(quaternion))
if not math.isfinite(norm) or not 0.99 <= norm <= 1.01:
raise L31PointPillarsRavnovesError("K1 pose quaternion is not normalized")
x, y, z, w = quaternion / norm
rotation_map_from_lidar = np.asarray(
[
[1 - 2 * (y * y + z * z), 2 * (x * y - z * w), 2 * (x * z + y * w)],
[2 * (x * y + z * w), 1 - 2 * (x * x + z * z), 2 * (y * z - x * w)],
[2 * (x * z - y * w), 2 * (y * z + x * w), 1 - 2 * (x * x + y * y)],
],
dtype=np.float64,
)
sensor_xyz = (points - position) @ rotation_map_from_lidar
result = np.empty((points.shape[0], 4), dtype=np.float32)
result[:, :3] = sensor_xyz.astype(np.float32)
result[:, 3] = intensity.astype(np.float32) / 255.0
if not np.isfinite(result).all():
raise L31PointPillarsRavnovesError("K1 sensor-frame XYZI is non-finite")
return result
def select_visual_frame_indices(
vehicle_counts: Sequence[int],
total_counts: Sequence[int],
*,
maximum_frames: int = 18,
) -> tuple[int, ...]:
"""Select route-wide evidence, preferring frames with Vehicle predictions."""
vehicles = tuple(vehicle_counts)
totals = tuple(total_counts)
if (
len(vehicles) != len(totals)
or not vehicles
or isinstance(maximum_frames, bool)
or maximum_frames < 1
or any(
isinstance(value, bool) or not isinstance(value, int) or value < 0
for value in (*vehicles, *totals)
)
or any(vehicle > total for vehicle, total in zip(vehicles, totals, strict=True))
):
raise L31PointPillarsRavnovesError("L3.1 visual selection input is invalid")
count = min(maximum_frames, len(vehicles))
boundaries = np.linspace(0, len(vehicles), count + 1, dtype=np.int64)
selected: list[int] = []
for bin_index in range(count):
start = int(boundaries[bin_index])
stop = int(boundaries[bin_index + 1])
if stop <= start:
continue
center = (start + stop - 1) / 2.0
chosen = max(
range(start, stop),
key=lambda index: (
vehicles[index],
totals[index],
-abs(index - center),
-index,
),
)
selected.append(chosen)
return tuple(selected)
+12 -2
View File
@@ -10,8 +10,6 @@ from typing import Any, Final
from fastapi import APIRouter, Query
from k1link.web.l3_pointpillars_visual_api import latest_l3_visual_identity
from k1link.compute.e31_source_qualification import (
E31SourceQualification,
E31SourceQualificationError,
@@ -57,6 +55,8 @@ from k1link.compute.e40_perception_product_gate import (
E40PerceptionProductGateError,
read_e40_perception_product_gate,
)
from k1link.web.l3_pointpillars_visual_api import latest_l3_visual_identity
from k1link.web.l31_pointpillars_ravnoves_api import latest_l31_identity
LABORATORY_ADVANCED_CATALOG_SCHEMA: Final = (
"missioncore.laboratory-advanced-catalog/v1"
@@ -828,6 +828,7 @@ def build_advanced_laboratory_router(
e39_root_provider: RootProvider = lambda: None,
e40_root_provider: RootProvider = lambda: None,
l3_visual_root_provider: RootProvider = lambda: None,
l31_ravnoves_root_provider: RootProvider = lambda: None,
) -> APIRouter:
router = APIRouter(prefix="/api/v1/laboratory", tags=["laboratory"])
@@ -909,6 +910,15 @@ def build_advanced_laboratory_router(
"access": "read-only",
}
)
l31_identity = latest_l31_identity(l31_ravnoves_root_provider)
if l31_identity is not None:
result["items"].append(
{
"work_id": "l31-pointpillars-ravnoves",
**l31_identity,
"access": "read-only",
}
)
return result
@router.get("/e31/results")
+24 -3
View File
@@ -34,9 +34,6 @@ from k1link.sessions import (
SessionStore,
)
from k1link.web.advanced_laboratory_api import build_advanced_laboratory_router
from k1link.web.l3_pointpillars_visual_api import (
build_l3_pointpillars_visual_router,
)
from k1link.web.artifact_health_api import build_artifact_health_router
from k1link.web.compute_contour_api import build_compute_contour_router
from k1link.web.device_plugin_composition import load_installed_device_plugins
@@ -45,6 +42,12 @@ from k1link.web.e30_human_review_api import build_e30_human_review_router
from k1link.web.e30_review_api import build_e30_review_router
from k1link.web.e40_case_review_api import build_e40_case_review_router
from k1link.web.environment_api import build_environment_router
from k1link.web.l3_pointpillars_visual_api import (
build_l3_pointpillars_visual_router,
)
from k1link.web.l31_pointpillars_ravnoves_api import (
build_l31_pointpillars_ravnoves_router,
)
from k1link.web.laboratory_api import build_laboratory_router
from k1link.web.lidar_api import build_lidar_router
from k1link.web.lidar_local_surface_service import K1LocalSurfaceReadService
@@ -609,6 +612,13 @@ app.include_router(
/ "l3"
/ "visual-audits"
),
l31_ravnoves_root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "l3"
/ "pointpillars-ravnoves"
),
)
)
app.include_router(
@@ -622,6 +632,17 @@ app.include_router(
)
)
)
app.include_router(
build_l31_pointpillars_ravnoves_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "l3"
/ "pointpillars-ravnoves"
)
)
)
app.include_router(
build_e30_review_router(
materialization_root_provider=lambda: (
@@ -0,0 +1,392 @@
"""Read-only projection of the sealed L3.1 PointPillars RAVNOVES evidence."""
from __future__ import annotations
import copy
import hashlib
import json
import re
from collections.abc import Callable
from pathlib import Path
from typing import Any, Final
from fastapi import APIRouter, HTTPException, Query
RootProvider = Callable[[], Path | None]
RESULT_SCHEMA: Final = "missioncore.l31-pointpillars-ravnoves/v1"
CATALOG_SCHEMA: Final = "missioncore.l31-pointpillars-ravnoves-catalog/v1"
VISUAL_FRAME_SCHEMA: Final = (
"missioncore.l31-pointpillars-ravnoves-visual-frame/v1"
)
RESULT_PROJECTION_SCHEMA: Final = (
"missioncore.l31-pointpillars-ravnoves-result/v1"
)
RESULT_CATALOG_SCHEMA: Final = (
"missioncore.l31-pointpillars-ravnoves-catalog-results/v1"
)
RESULT_ID: Final = re.compile(r"^l31-pointpillars-ravnoves-[a-f0-9]{64}$")
FRAME_ID: Final = re.compile(r"^[0-9]{6}$")
SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
MAX_JSON_BYTES: Final = 16 * 1024 * 1024
MAX_CANDIDATES: Final = 16
MAX_FRAMES: Final = 18
def build_l31_pointpillars_ravnoves_router(
*,
root_provider: RootProvider = lambda: None,
) -> APIRouter:
router = APIRouter(
prefix="/api/v1/laboratory/l31/pointpillars-ravnoves",
tags=["laboratory"],
)
@router.get("/results")
def list_results(
limit: int = Query(default=1, ge=1, le=10),
) -> dict[str, object]:
root = _configured_root(root_provider)
if root is None:
return _empty_catalog(False)
candidates = _candidates(root)
items: list[dict[str, object]] = []
invalid_total = 0
for candidate in candidates:
try:
items.append(_project_result(candidate))
except RuntimeError:
invalid_total += 1
items.sort(
key=lambda item: (
str(item["created_at_utc"]),
str(item["result_id"]),
),
reverse=True,
)
return {
"schema_version": RESULT_CATALOG_SCHEMA,
"configured": True,
"items": items[:limit],
"candidate_total": len(candidates),
"invalid_total": invalid_total,
"access": "read-only",
}
@router.get("/{result_id}/frames/{frame_id}")
def get_frame(result_id: str, frame_id: str) -> dict[str, object]:
if not RESULT_ID.fullmatch(result_id) or not FRAME_ID.fullmatch(frame_id):
raise HTTPException(status_code=404, detail="L3.1 frame not found")
root = _configured_root(root_provider)
if root is None:
raise HTTPException(status_code=404, detail="L3.1 result not found")
candidate = root / result_id
try:
result = _load_result(candidate)
descriptor = next(
item
for item in result["catalog"]["frames"]
if item["frame_id"] == frame_id
)
relative = descriptor["detail_path"]
if relative != f"visual-frames/{frame_id}.json":
raise RuntimeError("L3.1 visual path changed")
path = candidate / relative
payload = _read_json(path)
if (
payload.get("schema_version") != VISUAL_FRAME_SCHEMA
or payload.get("frame_id") != frame_id
or descriptor["detail_sha256"] != _sha256(path)
or descriptor["detail_byte_length"] != path.stat().st_size
or not _valid_visual_payload(payload)
):
raise RuntimeError("L3.1 visual identity changed")
except (RuntimeError, StopIteration):
raise HTTPException(
status_code=404,
detail="L3.1 frame not found",
) from None
return {**copy.deepcopy(payload), "access": "read-only"}
return router
def latest_l31_identity(
root_provider: RootProvider,
) -> dict[str, str] | None:
root = _configured_root(root_provider)
if root is None:
return None
valid: list[dict[str, object]] = []
for candidate in _candidates(root):
try:
valid.append(_project_result(candidate))
except RuntimeError:
continue
if not valid:
return None
latest = max(
valid,
key=lambda item: (
str(item["created_at_utc"]),
str(item["result_id"]),
),
)
return {
"result_id": str(latest["result_id"]),
"created_at_utc": str(latest["created_at_utc"]),
}
def _project_result(candidate: Path) -> dict[str, object]:
result = _load_result(candidate)
manifest = result["manifest"]
identity = manifest["identity"]
return {
"schema_version": RESULT_PROJECTION_SCHEMA,
"result_id": manifest["result_id"],
"created_at_utc": manifest["created_at_utc"],
"status": "cross-domain-transfer-measured-visual-review-required",
"source_session_id": identity["source_session_id"],
"source_pack_id": identity["source_pack_id"],
"source_logical_content_sha256": identity[
"source_logical_content_sha256"
],
"model": copy.deepcopy(identity["model"]),
"execution": copy.deepcopy(identity["execution"]),
"metrics": copy.deepcopy(manifest["metrics"]),
"frames": copy.deepcopy(result["catalog"]["frames"]),
"limitations": copy.deepcopy(manifest["limitations"]),
"authority": copy.deepcopy(manifest["authority"]),
"access": "read-only",
}
def _load_result(candidate: Path) -> dict[str, Any]:
if (
not candidate.is_dir()
or candidate.is_symlink()
or not RESULT_ID.fullmatch(candidate.name)
):
raise RuntimeError("L3.1 candidate is invalid")
manifest = _read_json(candidate / "manifest.json")
identity = manifest.get("identity")
authority = manifest.get("authority")
catalog_descriptor = manifest.get("catalog")
limitations = manifest.get("limitations")
metrics = manifest.get("metrics")
if (
manifest.get("schema_version") != RESULT_SCHEMA
or manifest.get("result_id") != candidate.name
or manifest.get("status")
!= "k1-cross-domain-transfer-measured-visual-review-required"
or not isinstance(manifest.get("created_at_utc"), str)
or not isinstance(identity, dict)
or identity.get("source_session_id")
!= "20260720T065719Z_viewer_live"
or not isinstance(authority, dict)
or authority.get("shadow_only") is not True
or authority.get("commands_enabled") is not False
or authority.get("navigation_or_safety_accepted") is not False
or authority.get("accuracy_accepted") is not False
or manifest.get("identity_sha256")
!= hashlib.sha256(_canonical_json(identity)).hexdigest()
or candidate.name
!= f"l31-pointpillars-ravnoves-{manifest.get('identity_sha256')}"
or not isinstance(metrics, dict)
or metrics.get("frame_count") != 4570
or metrics.get("input_admission_fraction") != 1.0
or metrics.get("output_schema_valid_fraction") != 1.0
or not isinstance(limitations, list)
or not limitations
or not isinstance(catalog_descriptor, dict)
or catalog_descriptor.get("path") != "catalog.json"
or catalog_descriptor.get("kind") != "visual-frame-catalog"
):
raise RuntimeError("L3.1 manifest is invalid")
catalog_path = candidate / "catalog.json"
if (
catalog_descriptor.get("sha256") != _sha256(catalog_path)
or catalog_descriptor.get("byte_length") != catalog_path.stat().st_size
):
raise RuntimeError("L3.1 catalog changed")
catalog = _read_json(catalog_path)
frames = catalog.get("frames")
if (
catalog.get("schema_version") != CATALOG_SCHEMA
or catalog.get("result_id") != candidate.name
or catalog.get("source_session_id") != identity["source_session_id"]
or not isinstance(frames, list)
or not 1 <= len(frames) <= MAX_FRAMES
or catalog.get("frame_count") != len(frames)
or len({item.get("frame_id") for item in frames if isinstance(item, dict)})
!= len(frames)
or any(not _valid_frame_descriptor(item) for item in frames)
):
raise RuntimeError("L3.1 catalog is invalid")
return {"manifest": manifest, "catalog": catalog}
def _valid_frame_descriptor(value: object) -> bool:
if not isinstance(value, dict):
return False
frame_id = value.get("frame_id")
class_counts = value.get("class_counts")
return (
isinstance(frame_id, str)
and FRAME_ID.fullmatch(frame_id) is not None
and value.get("detail_path") == f"visual-frames/{frame_id}.json"
and isinstance(value.get("detail_sha256"), str)
and SHA256.fullmatch(value["detail_sha256"]) is not None
and isinstance(value.get("detail_byte_length"), int)
and 0 < value["detail_byte_length"] <= MAX_JSON_BYTES
and isinstance(value.get("frame_index"), int)
and value["frame_index"] >= 0
and isinstance(value.get("source_point_count"), int)
and value["source_point_count"] > 0
and isinstance(value.get("prediction_count"), int)
and value["prediction_count"] >= 0
and isinstance(value.get("session_seconds"), (int, float))
and value["session_seconds"] >= 0
and isinstance(value.get("inference_ms"), (int, float))
and 0 < value["inference_ms"] < 60_000
and isinstance(class_counts, dict)
and set(class_counts) == {"Vehicle", "Pedestrian", "Cyclist"}
and all(
isinstance(count, int) and not isinstance(count, bool) and count >= 0
for count in class_counts.values()
)
)
def _valid_visual_payload(value: dict[str, Any]) -> bool:
points = value.get("points")
boxes = value.get("prediction_boxes")
interpretation = value.get("interpretation")
if (
not isinstance(points, dict)
or points.get("layout") != "flat-xyzi"
or not isinstance(boxes, list)
or len(boxes) > 512
or not isinstance(interpretation, dict)
or interpretation.get("ground_truth_available") is not False
or interpretation.get("boxes_are_model_hypotheses") is not True
or interpretation.get("accuracy_claim_allowed") is not False
):
return False
values = points.get("values")
sampled = points.get("sampled_point_count")
return (
isinstance(values, list)
and isinstance(sampled, int)
and 0 < sampled <= 12_000
and len(values) == sampled * 4
and all(
isinstance(item, (int, float))
and not isinstance(item, bool)
and -1_000_000 < item < 1_000_000
for item in values
)
and all(_valid_box(box) for box in boxes)
)
def _valid_box(value: object) -> bool:
if not isinstance(value, dict):
return False
numeric = (
"x_m",
"y_m",
"z_m",
"length_m",
"width_m",
"height_m",
"yaw_rad",
"score",
)
return (
value.get("model_class") in {"Vehicle", "Pedestrian", "Cyclist"}
and isinstance(value.get("class_id"), int)
and value["class_id"] in {0, 1, 2}
and all(
isinstance(value.get(key), (int, float))
and not isinstance(value.get(key), bool)
and math_is_finite(float(value[key]))
for key in numeric
)
and value["length_m"] > 0
and value["width_m"] > 0
and value["height_m"] > 0
and 0.1 <= value["score"] <= 1.0
)
def math_is_finite(value: float) -> bool:
return value == value and value not in (float("inf"), float("-inf"))
def _configured_root(provider: RootProvider) -> Path | None:
value = provider()
if value is None:
return None
root = value.expanduser().absolute()
if not root.is_dir() or root.is_symlink():
return None
return root
def _candidates(root: Path) -> list[Path]:
candidates = [
path
for path in root.iterdir()
if path.is_dir()
and not path.is_symlink()
and RESULT_ID.fullmatch(path.name)
]
if len(candidates) > MAX_CANDIDATES:
raise RuntimeError("L3.1 candidate bound exceeded")
return candidates
def _empty_catalog(configured: bool) -> dict[str, object]:
return {
"schema_version": RESULT_CATALOG_SCHEMA,
"configured": configured,
"items": [],
"candidate_total": 0,
"invalid_total": 0,
"access": "read-only",
}
def _read_json(path: Path) -> dict[str, Any]:
try:
if (
not path.is_file()
or path.is_symlink()
or not 0 < path.stat().st_size <= MAX_JSON_BYTES
):
raise RuntimeError(f"{path.name} is unavailable")
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise RuntimeError(f"{path.name} is invalid") from exc
if not isinstance(payload, dict):
raise RuntimeError(f"{path.name} is not an object")
return payload
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _canonical_json(payload: object) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")