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,123 @@
# L3.1 · PointPillars на RAVNOVES00 — 2026-07-31
## Решение
`L3.1` — это полный transfer-прогон текущего PointPillars-кандидата по
реальной записи нашего сканера `RAVNOVES00`. Внешний KITTI в эту лабораторную
работу не входит: прежний `L3` сохранён отдельно как публичный benchmark для
проверки измерителя и идеального box-truth контура.
Полный runtime-контракт на RAVNOVES00 выполнен, но модель не допускается в
operational detector. У записи нет независимых ориентированных 3D truth-боксов,
поэтому полученные боксы являются только визуально проверяемыми гипотезами.
Кроме того, повтор выбранных кадров совпал только в `50%` случаев.
Неизменяемый результат:
`l31-pointpillars-ravnoves-80a9715f64ea397222fbcfd700803f9152009e3751caf87e2e9dc5ec6fc01b72`
## Источник
- запись: `RAVNOVES00`;
- канонический session id: `20260720T065719Z_viewer_live`;
- replay pack:
`lidar-replay-pack-8fc0fb418578b8ee2ac88d502d2acbc63ae533437a9da14f1a9f9d8916f613ce`;
- logical content SHA-256:
`8d5642e5f394a48f5e07322e813384c601c96880f1ec349075f064a2d077b4ac`;
- LiDAR-кадров: `4,570`;
- pose-кадров: `4,598`;
- исходных точек: `10,751,258`.
Каждая порция point cloud была связана с ближайшей pose. Точки, сохранённые в
map frame, детерминированно преобразованы обратно в sensor-frame XYZI,
ожидаемый моделью. Возраст pose binding не превысил `73.797625 ms`.
## Исполнение
Прогон выполнен на Worker 006 внутри уже работающего канонического контейнера
`ndc-mission-core-triton`:
- второй контейнер и второй serving stack не создавались;
- существующий Triton не перезапускался;
- один последовательный worker;
- source pacing отключён;
- Synology не использовался;
- модель ONNX SHA-256:
`2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1`;
- TensorRT engine SHA-256:
`12005d972a4632d56342a5da44442b632c1dcc5144fa3c70b162dec334532481`;
- worker package:
`l3-pointpillars-worker-package-6799b6f4ad776a4a959dc2d6366c6bcdf8ddb922990571c46841acf186e28c9b`.
## Полный результат
| Метрика | Значение |
| --- | ---: |
| Принято входных кадров | `4,570 / 4,570` (`100%`) |
| Валидных выходных контрактов | `4,570 / 4,570` (`100%`) |
| Кадров с гипотезами | `4,570` |
| Кадров с Vehicle-гипотезами | `4,559` |
| Всего гипотез | `48,005` |
| Vehicle | `28,097` |
| Pedestrian | `1,095` |
| Cyclist | `18,813` |
| Inference mean | `27.857662 ms` |
| Inference p50 | `26.324656 ms` |
| Inference p95 | `39.919944 ms` |
| Inference max | `143.260391 ms` |
| Повтор выбранных визуальных кадров | `9 / 18` (`50%`) |
Идентичность полного набора per-frame результатов:
`9e4bb0fe1fc7a31d77bbc7d4de08e9e42c1456d28728ae25f9cc88598f0ebac0`
## Визуальное доказательство
Из полного маршрута детерминированно выбраны `18` кадров: маршрут разделён на
равные временные окна, внутри каждого приоритет имеет число Vehicle-гипотез,
затем общее число гипотез и близость к центру окна.
Для каждого выбранного кадра опубликованы:
- ограниченная равномерная выборка реальных XYZI-точек RAVNOVES00;
- ориентированные 3D-боксы PointPillars;
- классы и confidence;
- исходная latency;
- результат повторного запуска того же кадра.
Mission Core загружает только выбранный кадр и показывает его в общем
лабораторном viewer в режимах `3D` и `BEV`. В интерфейсе боксы явно обозначены
как гипотезы модели, а не ground truth, TP/FP/FN или подтверждённые объекты.
## Что доказано
- реальный RAVNOVES00 проходит через текущий PointPillars input/output seam без
пропуска кадров и нарушения схемы;
- существующий Worker 006 и Triton способны последовательно обработать полный
маршрут;
- задержка inference измерена на всём наборе, а не на коротком smoke;
- фактические точки и гипотезы можно проверять визуально в Mission Core.
## Что не доказано
- точность, полнота и классовая корректность детектора;
- TP/FP/FN и пригодность боксов для продукта;
- source-paced queue/drop/deadline поведение;
- навигация, команды или safety;
- замена camera-first семантики LiDAR-only моделью.
Текущий source — vendor map increment без ring, firing time, scan geometry и
IMU. Модель обучена на другом proprietary solid-state LiDAR domain.
## Следующий gate
1. Разобрать визуально `18` опубликованных кадров и локализовать характер
доменного расхождения.
2. Выяснить причину `50%` повторяемости боксов при неизменном входе и sealed
engine.
3. Зафиксировать небольшой независимый RAVNOVES box-truth набор до любого
обучения или подбора порога.
4. Только после этого допускать новый train/adaptation candidate на Worker 006.
5. Основную продуктовую архитектуру сохранять как
`camera-first semantics + LiDAR metric geometry`; LiDAR-native detector
остаётся отдельным доказательным кандидатом.
@@ -1,5 +1,11 @@
# L3 PointPillars transfer report — 2026-07-31
> Контур размещения: это отдельный публичный benchmark на KITTI. Он сохранён
> для проверки измерителя, box-truth и cross-domain ideal/reference path, но не
> является основной лабораторной работой нашего сканера. Реальный прогон
> RAVNOVES00 оформлен отдельно как `L3.1`; см.
> `L31_POINTPILLARS_RAVNOVES_REPORT_2026-07-31.md`.
## Decision
The LiDAR-native detector seam is operational on Worker 006, but the frozen
@@ -230,10 +236,10 @@ the sensor-domain mismatch.
4. Export a new ONNX, build its TensorRT engine on Worker 006 and evaluate
through the already proven canonical Triton seam.
5. Require a useful public box-truth result before K1 accuracy language.
6. Run `RAVNOVES01` as a K1 representation/stability transfer after the public
candidate is useful. Because that session has no camera and no independent
3D cuboids, it can prove deterministic replay, output stability, latency,
queue/drop behavior and visual plausibility, but not detector accuracy.
6. Keep the public KITTI benchmark outside the primary scanner profile.
RAVNOVES transfer is now the separate `L3.1` laboratory work; its completed
baseline and limitations are recorded in
`L31_POINTPILLARS_RAVNOVES_REPORT_2026-07-31.md`.
The adaptation task is a new admission. This report does not authorize a
training container, a second serving stack, K1 quality claims or navigation
@@ -26,6 +26,9 @@ _SOURCE_FILES = {
"runtime/k1link/compute/pointpillars_postprocess.py": (
"src/k1link/compute/pointpillars_postprocess.py"
),
"runtime/k1link/compute/l31_pointpillars_ravnoves.py": (
"src/k1link/compute/l31_pointpillars_ravnoves.py"
),
"runtime/k1link/datasets/kitti_3d_admission.py": (
"src/k1link/datasets/kitti_3d_admission.py"
),
@@ -41,6 +44,9 @@ _SOURCE_FILES = {
"runtime/run_l3_pointpillars_public_baseline.py": (
"experiments/perception/worker/run_l3_pointpillars_public_baseline.py"
),
"runtime/run_l31_pointpillars_ravnoves.py": (
"experiments/perception/worker/run_l31_pointpillars_ravnoves.py"
),
"runtime/smoke_l3_pointpillars_live.py": (
"experiments/perception/worker/smoke_l3_pointpillars_live.py"
),
@@ -0,0 +1,777 @@
#!/usr/bin/env python3
"""Run PointPillars sequentially over the sealed RAVNOVES00 K1 replay."""
from __future__ import annotations
import argparse
import hashlib
import json
import math
import os
import tempfile
import time
import urllib.request
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import numpy as np
from k1link.compute.l31_pointpillars_ravnoves import (
nearest_pose_indices,
select_visual_frame_indices,
sensor_frame_xyzi,
)
from k1link.compute.pointpillars_postprocess import (
POINTPILLARS_EMBEDDED_SCORE_THRESHOLD,
POINTPILLARS_MODEL_POINT_CLOUD_RANGE,
PointPillarsBox,
decode_pointpillars_output,
)
RUN_SCHEMA = "missioncore.l31-pointpillars-ravnoves/v1"
FRAME_SCHEMA = "missioncore.l31-pointpillars-ravnoves-frame/v1"
CATALOG_SCHEMA = "missioncore.l31-pointpillars-ravnoves-catalog/v1"
VISUAL_FRAME_SCHEMA = "missioncore.l31-pointpillars-ravnoves-visual-frame/v1"
WORKER_PACKAGE_SCHEMA = "missioncore.l3-pointpillars-worker-package/v1"
REPLAY_SCHEMA = "missioncore.lidar-replay-pack/v2"
EXPECTED_PACK_ID = (
"lidar-replay-pack-"
"8fc0fb418578b8ee2ac88d502d2acbc63ae533437a9da14f1a9f9d8916f613ce"
)
EXPECTED_REPLAY_SHA256 = (
"cbb75341ca0d82aea6e59bec26636d06aa58d9c300e6fb5555033ab35b2bed7e"
)
EXPECTED_SESSION_ID = "20260720T065719Z_viewer_live"
EXPECTED_MODEL_SHA256 = (
"2dcabddc3a365e9608a112d7bbbb7db769a6dddeeaa59aa03611a83113326da1"
)
EXPECTED_ENGINE_SHA256 = (
"12005d972a4632d56342a5da44442b632c1dcc5144fa3c70b162dec334532481"
)
MODEL_NAME = "pointpillars"
MAXIMUM_POINTS = 204_800
MAXIMUM_VISUAL_BOXES = 512
MAXIMUM_VISUAL_FRAMES = 18
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--replay-pack", type=Path, required=True)
parser.add_argument("--worker-package", type=Path, required=True)
parser.add_argument("--triton-url", default="http://127.0.0.1:8000")
parser.add_argument(
"--output-root",
type=Path,
default=Path(
"/mnt/d/NDC_MISSIONCORE/runtime/experiments/l3/"
"pointpillars-ravnoves"
),
)
args = parser.parse_args()
replay_root, replay_manifest, arrays = _read_replay(args.replay_pack)
package = _read_worker_package(args.worker_package)
_require_triton_ready(args.triton_url)
point_times = arrays["point_received_monotonic_ns"]
pose_indices, pose_age_ms = nearest_pose_indices(
point_times,
arrays["pose_received_monotonic_ns"],
)
if float(np.max(pose_age_ms)) > 100.0:
raise RuntimeError("RAVNOVES00 contains a pose binding older than 100 ms")
identity = {
"schema_version": RUN_SCHEMA,
"source_pack_id": replay_manifest["pack_id"],
"source_pack_identity_sha256": replay_manifest["identity_sha256"],
"source_logical_content_sha256": replay_manifest["identity"][
"logical_content_sha256"
],
"source_session_id": EXPECTED_SESSION_ID,
"point_frame_count": int(point_times.size),
"model": {
"name": MODEL_NAME,
"source_model_sha256": EXPECTED_MODEL_SHA256,
"engine_sha256": EXPECTED_ENGINE_SHA256,
"embedded_score_threshold": POINTPILLARS_EMBEDDED_SCORE_THRESHOLD,
"point_cloud_range": list(POINTPILLARS_MODEL_POINT_CLOUD_RANGE),
},
"worker_package_id": package["package_id"],
"worker_package_identity_sha256": package["identity_sha256"],
"producer_sha256": _sha256(Path(__file__)),
"execution": {
"worker_host_id": "worker-006",
"sequential": True,
"parallel_workers": 1,
"source_paced": False,
"existing_triton_only": True,
},
"authority": {
"shadow_only": True,
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"accuracy_accepted": False,
},
}
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
result_id = f"l31-pointpillars-ravnoves-{identity_sha256}"
result_root = args.output_root.expanduser().absolute() / result_id
summaries_root = result_root / "frame-results"
visual_root = result_root / "visual-frames"
summaries_root.mkdir(mode=0o700, parents=True, exist_ok=True)
visual_root.mkdir(mode=0o700, parents=True, exist_ok=True)
_write_once(result_root / "identity.json", identity)
point_offsets = arrays["point_offsets"]
first_time_ns = int(point_times[0])
summaries: list[dict[str, Any]] = []
for completed, frame_index in enumerate(range(point_times.size), start=1):
xyzi = _frame_xyzi(arrays, point_offsets, pose_indices, frame_index)
input_sha256 = hashlib.sha256(
np.ascontiguousarray(xyzi, dtype=np.float32).tobytes()
).hexdigest()
frame_path = summaries_root / f"{frame_index:06d}.json"
if frame_path.exists():
summary = _read_frame_summary(
frame_path,
frame_index=frame_index,
input_sha256=input_sha256,
)
else:
boxes, elapsed_ms = _run_frame(args.triton_url, xyzi)
summary = _frame_summary(
frame_index=frame_index,
session_seconds=(int(point_times[frame_index]) - first_time_ns)
/ 1_000_000_000.0,
source_point_count=int(xyzi.shape[0]),
pose_index=int(pose_indices[frame_index]),
pose_binding_age_ms=float(pose_age_ms[frame_index]),
input_sha256=input_sha256,
inference_ms=elapsed_ms,
boxes=boxes,
)
_atomic_json(frame_path, summary)
summaries.append(summary)
if completed == 1 or completed % 100 == 0:
print(
json.dumps(
{
"result_id": result_id,
"completed": completed,
"total": int(point_times.size),
"frame_index": frame_index,
},
sort_keys=True,
),
flush=True,
)
selected_indices = select_visual_frame_indices(
[int(row["class_counts"]["Vehicle"]) for row in summaries],
[int(row["prediction_count"]) for row in summaries],
maximum_frames=MAXIMUM_VISUAL_FRAMES,
)
visual_descriptors: list[dict[str, Any]] = []
deterministic_matches = 0
for frame_index in selected_indices:
xyzi = _frame_xyzi(arrays, point_offsets, pose_indices, frame_index)
boxes, elapsed_ms = _run_frame(args.triton_url, xyzi)
observed_sha256 = _boxes_sha256(boxes)
if observed_sha256 == summaries[frame_index]["boxes_sha256"]:
deterministic_matches += 1
visual_payload = _visual_frame(
summary=summaries[frame_index],
xyzi=xyzi,
boxes=boxes,
replay_inference_ms=elapsed_ms,
deterministic_replay=(
observed_sha256 == summaries[frame_index]["boxes_sha256"]
),
)
visual_path = visual_root / f"{frame_index:06d}.json"
_atomic_json(visual_path, visual_payload)
descriptor = _artifact(
visual_path,
"visual-frame",
relative_to=result_root,
)
visual_descriptors.append(
{
"frame_id": f"{frame_index:06d}",
"frame_index": frame_index,
"session_seconds": summaries[frame_index]["session_seconds"],
"source_point_count": summaries[frame_index]["source_point_count"],
"prediction_count": summaries[frame_index]["prediction_count"],
"class_counts": summaries[frame_index]["class_counts"],
"inference_ms": summaries[frame_index]["inference_ms"],
"detail_path": descriptor["path"],
"detail_sha256": descriptor["sha256"],
"detail_byte_length": descriptor["byte_length"],
}
)
metrics = _metrics(
summaries,
deterministic_matches=deterministic_matches,
deterministic_total=len(selected_indices),
)
catalog = {
"schema_version": CATALOG_SCHEMA,
"result_id": result_id,
"source_session_id": EXPECTED_SESSION_ID,
"selection": {
"strategy": (
"18 equal route-time bins; prefer Vehicle count, then total "
"prediction count, then bin-centre proximity"
),
"maximum_frames": MAXIMUM_VISUAL_FRAMES,
"maximum_boxes_per_frame": MAXIMUM_VISUAL_BOXES,
},
"frame_count": len(visual_descriptors),
"frames": visual_descriptors,
}
_atomic_json(result_root / "catalog.json", catalog)
manifest = {
"schema_version": RUN_SCHEMA,
"result_id": result_id,
"identity_sha256": identity_sha256,
"identity": identity,
"created_at_utc": datetime.now(UTC)
.isoformat(timespec="milliseconds")
.replace("+00:00", "Z"),
"status": "k1-cross-domain-transfer-measured-visual-review-required",
"metrics": metrics,
"catalog": _artifact(
result_root / "catalog.json",
"visual-frame-catalog",
relative_to=result_root,
),
"frame_results": {
"count": len(summaries),
"identity_sha256": _directory_identity(summaries_root),
},
"limitations": [
"RAVNOVES00 has no independent oriented 3D cuboid ground truth.",
"Predicted boxes are model hypotheses, not TP/FP/FN or accepted objects.",
"The K1 source is a vendor map increment without ring, firing time, "
"scan geometry, or IMU.",
"Execution is offline sequential and does not establish source-paced "
"drop or deadline behaviour.",
"The model was trained on another proprietary solid-state LiDAR domain.",
],
"authority": identity["authority"],
}
_atomic_json(result_root / "manifest.json", manifest)
print(
json.dumps(
{
"result_id": result_id,
"frame_count": len(summaries),
"frames_with_predictions": metrics["frames_with_predictions"],
"vehicle_predictions": metrics["class_counts"]["Vehicle"],
"inference_p95_ms": metrics["inference_latency_ms"]["p95"],
"deterministic_replay_fraction": metrics[
"deterministic_replay_fraction"
],
},
sort_keys=True,
),
flush=True,
)
del replay_root
return 0
def _read_replay(
path: Path,
) -> tuple[Path, dict[str, Any], dict[str, np.ndarray]]:
root = path.expanduser().resolve(strict=True)
manifest = _read_json(root / "manifest.json")
identity = manifest.get("identity")
artifacts = manifest.get("artifacts")
if (
not root.is_dir()
or root.is_symlink()
or root.name != EXPECTED_PACK_ID
or manifest.get("schema_version") != REPLAY_SCHEMA
or manifest.get("pack_id") != EXPECTED_PACK_ID
or manifest.get("identity_sha256") != EXPECTED_PACK_ID.removeprefix(
"lidar-replay-pack-"
)
or not isinstance(identity, dict)
or identity.get("session_id") != EXPECTED_SESSION_ID
or identity.get("point_frame_count") != 4570
or identity.get("pose_frame_count") != 4598
or not isinstance(artifacts, list)
):
raise RuntimeError("RAVNOVES00 replay identity is invalid")
descriptor = next(
(
row
for row in artifacts
if isinstance(row, dict) and row.get("path") == "lidar-replay.npz"
),
None,
)
replay_path = root / "lidar-replay.npz"
if (
not isinstance(descriptor, dict)
or descriptor.get("sha256") != EXPECTED_REPLAY_SHA256
or descriptor.get("byte_length") != replay_path.stat().st_size
or _sha256(replay_path) != EXPECTED_REPLAY_SHA256
):
raise RuntimeError("RAVNOVES00 replay artifact changed")
expected = {
"point_offsets",
"point_xyz_map",
"point_intensity",
"point_received_monotonic_ns",
"pose_positions_map",
"pose_quaternions_map_from_lidar",
"pose_received_monotonic_ns",
}
with np.load(replay_path, allow_pickle=False) as archive:
if not expected.issubset(archive.files):
raise RuntimeError("RAVNOVES00 replay arrays changed")
# Load each required member once. Reopening a compressed NPZ member for
# every frame would repeatedly inflate the complete 10.7M-point array.
arrays = {name: archive[name] for name in expected}
if (
arrays["point_offsets"].shape != (4571,)
or arrays["point_offsets"].dtype != np.int64
or arrays["point_xyz_map"].shape != (10_751_258, 3)
or arrays["point_xyz_map"].dtype != np.float64
or arrays["point_intensity"].shape != (10_751_258,)
or arrays["point_intensity"].dtype != np.uint8
or arrays["point_received_monotonic_ns"].shape != (4570,)
or arrays["point_received_monotonic_ns"].dtype != np.int64
or arrays["pose_positions_map"].shape != (4598, 3)
or arrays["pose_positions_map"].dtype != np.float64
or arrays["pose_quaternions_map_from_lidar"].shape != (4598, 4)
or arrays["pose_quaternions_map_from_lidar"].dtype != np.float64
or arrays["pose_received_monotonic_ns"].shape != (4598,)
or arrays["pose_received_monotonic_ns"].dtype != np.int64
or int(arrays["point_offsets"][0]) != 0
or int(arrays["point_offsets"][-1]) != 10_751_258
or np.any(np.diff(arrays["point_offsets"]) <= 0)
):
raise RuntimeError("RAVNOVES00 replay array contract changed")
return root, manifest, arrays
def _frame_xyzi(
arrays: dict[str, np.ndarray],
offsets: np.ndarray,
pose_indices: np.ndarray,
frame_index: int,
) -> np.ndarray:
start = int(offsets[frame_index])
stop = int(offsets[frame_index + 1])
pose_index = int(pose_indices[frame_index])
xyzi = sensor_frame_xyzi(
arrays["point_xyz_map"][start:stop],
arrays["point_intensity"][start:stop],
position_map_xyz=arrays["pose_positions_map"][pose_index],
orientation_map_from_lidar_xyzw=arrays[
"pose_quaternions_map_from_lidar"
][pose_index],
)
if not 1 <= xyzi.shape[0] <= MAXIMUM_POINTS:
raise RuntimeError("RAVNOVES00 frame violates PointPillars input bounds")
return xyzi
def _run_frame(
triton_url: str,
native: np.ndarray,
) -> tuple[tuple[PointPillarsBox, ...], float]:
points = np.zeros((1, MAXIMUM_POINTS, 4), dtype=np.float32)
points[0, : native.shape[0]] = native
num_points = np.asarray([native.shape[0]], dtype=np.int32)
output_boxes, output_count, elapsed_ms = _infer(
triton_url,
points,
num_points,
)
return decode_pointpillars_output(output_boxes, output_count), elapsed_ms
def _infer(
triton_url: str,
points: np.ndarray,
num_points: np.ndarray,
) -> tuple[np.ndarray, np.ndarray, float]:
points_binary = np.ascontiguousarray(points, dtype=np.float32).tobytes()
count_binary = np.ascontiguousarray(num_points, dtype=np.int32).tobytes()
header = {
"inputs": [
{
"name": "points",
"shape": [1, MAXIMUM_POINTS, 4],
"datatype": "FP32",
"parameters": {"binary_data_size": len(points_binary)},
},
{
"name": "num_points",
"shape": [1],
"datatype": "INT32",
"parameters": {"binary_data_size": len(count_binary)},
},
],
"outputs": [
{"name": "output_boxes", "parameters": {"binary_data": True}},
{"name": "num_boxes", "parameters": {"binary_data": True}},
],
}
encoded = _canonical_json(header)
request = urllib.request.Request(
f"{triton_url.rstrip('/')}/v2/models/{MODEL_NAME}/infer",
data=encoded + points_binary + count_binary,
headers={
"Content-Type": "application/octet-stream",
"Inference-Header-Content-Length": str(len(encoded)),
},
method="POST",
)
started = time.perf_counter()
with urllib.request.urlopen(request, timeout=120) as response:
payload = response.read()
header_length = int(response.headers["Inference-Header-Content-Length"])
elapsed_ms = (time.perf_counter() - started) * 1000.0
response_header = json.loads(payload[:header_length])
outputs = response_header.get("outputs")
if not isinstance(outputs, list) or len(outputs) != 2:
raise RuntimeError("Triton PointPillars output set changed")
offset = header_length
decoded: dict[str, np.ndarray] = {}
for descriptor in outputs:
if not isinstance(descriptor, dict):
raise RuntimeError("Triton output descriptor is invalid")
name = descriptor.get("name")
parameters = descriptor.get("parameters")
if not isinstance(name, str) or not isinstance(parameters, dict):
raise RuntimeError("Triton output descriptor is invalid")
byte_length = parameters.get("binary_data_size")
if isinstance(byte_length, bool) or not isinstance(byte_length, int):
raise RuntimeError("Triton output byte length is invalid")
binary = payload[offset : offset + byte_length]
offset += byte_length
if name == "output_boxes" and descriptor.get("datatype") == "FP32":
decoded[name] = np.frombuffer(binary, dtype="<f4").reshape(
1, 393_216, 9
)
elif name == "num_boxes" and descriptor.get("datatype") == "INT32":
decoded[name] = np.frombuffer(binary, dtype="<i4").reshape(1)
else:
raise RuntimeError("Triton PointPillars output contract changed")
if offset != len(payload) or set(decoded) != {"output_boxes", "num_boxes"}:
raise RuntimeError("Triton PointPillars output payload is invalid")
return decoded["output_boxes"], decoded["num_boxes"], elapsed_ms
def _box_payload(box: PointPillarsBox) -> dict[str, object]:
return {
"x_m": box.x_m,
"y_m": box.y_m,
"z_m": box.z_m,
"length_m": box.length_m,
"width_m": box.width_m,
"height_m": box.height_m,
"yaw_rad": box.yaw_rad,
"class_id": box.class_id,
"model_class": box.model_class,
"score": box.score,
}
def _boxes_sha256(boxes: tuple[PointPillarsBox, ...]) -> str:
return hashlib.sha256(
_canonical_json([_box_payload(box) for box in boxes])
).hexdigest()
def _frame_summary(
*,
frame_index: int,
session_seconds: float,
source_point_count: int,
pose_index: int,
pose_binding_age_ms: float,
input_sha256: str,
inference_ms: float,
boxes: tuple[PointPillarsBox, ...],
) -> dict[str, object]:
class_counts = {
name: sum(box.model_class == name for box in boxes)
for name in ("Vehicle", "Pedestrian", "Cyclist")
}
return {
"schema_version": FRAME_SCHEMA,
"frame_id": f"{frame_index:06d}",
"frame_index": frame_index,
"session_seconds": session_seconds,
"source_point_count": source_point_count,
"pose_index": pose_index,
"pose_binding_age_ms": pose_binding_age_ms,
"input_xyzi_sha256": input_sha256,
"inference_ms": inference_ms,
"prediction_count": len(boxes),
"class_counts": class_counts,
"boxes_sha256": _boxes_sha256(boxes),
}
def _read_frame_summary(
path: Path,
*,
frame_index: int,
input_sha256: str,
) -> dict[str, Any]:
payload = _read_json(path)
if (
payload.get("schema_version") != FRAME_SCHEMA
or payload.get("frame_id") != f"{frame_index:06d}"
or payload.get("frame_index") != frame_index
or payload.get("input_xyzi_sha256") != input_sha256
or not isinstance(payload.get("class_counts"), dict)
or not isinstance(payload.get("boxes_sha256"), str)
):
raise RuntimeError("cached L3.1 frame summary is invalid")
return payload
def _visual_frame(
*,
summary: dict[str, Any],
xyzi: np.ndarray,
boxes: tuple[PointPillarsBox, ...],
replay_inference_ms: float,
deterministic_replay: bool,
) -> dict[str, object]:
minimum_x, minimum_y, minimum_z, maximum_x, maximum_y, maximum_z = (
POINTPILLARS_MODEL_POINT_CLOUD_RANGE
)
mask = (
(xyzi[:, 0] >= minimum_x)
& (xyzi[:, 0] <= maximum_x)
& (xyzi[:, 1] >= minimum_y)
& (xyzi[:, 1] <= maximum_y)
& (xyzi[:, 2] >= minimum_z)
& (xyzi[:, 2] <= maximum_z)
)
visible_points = xyzi[mask]
visible_boxes = boxes[:MAXIMUM_VISUAL_BOXES]
return {
"schema_version": VISUAL_FRAME_SCHEMA,
"frame_id": summary["frame_id"],
"summary": {
**summary,
"replay_inference_ms": replay_inference_ms,
"deterministic_replay": deterministic_replay,
"visual_box_count": len(visible_boxes),
"visual_box_truncated": len(boxes) > len(visible_boxes),
},
"points": {
"layout": "flat-xyzi",
"source_point_count": int(xyzi.shape[0]),
"model_range_point_count": int(visible_points.shape[0]),
"sampled_point_count": int(visible_points.shape[0]),
"values": visible_points.reshape(-1).tolist(),
},
"prediction_boxes": [_box_payload(box) for box in visible_boxes],
"interpretation": {
"ground_truth_available": False,
"boxes_are_model_hypotheses": True,
"accuracy_claim_allowed": False,
},
}
def _metrics(
summaries: list[dict[str, Any]],
*,
deterministic_matches: int,
deterministic_total: int,
) -> dict[str, object]:
latency = np.asarray([row["inference_ms"] for row in summaries], dtype=np.float64)
pose_age = np.asarray(
[row["pose_binding_age_ms"] for row in summaries],
dtype=np.float64,
)
class_counts = {
name: sum(int(row["class_counts"][name]) for row in summaries)
for name in ("Vehicle", "Pedestrian", "Cyclist")
}
predictions = [int(row["prediction_count"]) for row in summaries]
return {
"frame_count": len(summaries),
"input_admission_fraction": 1.0,
"output_schema_valid_fraction": 1.0,
"frames_with_predictions": sum(value > 0 for value in predictions),
"frames_with_vehicle_predictions": sum(
int(row["class_counts"]["Vehicle"]) > 0 for row in summaries
),
"prediction_count": sum(predictions),
"class_counts": class_counts,
"predictions_per_frame": _distribution(np.asarray(predictions)),
"inference_latency_ms": _distribution(latency),
"pose_binding_age_ms": _distribution(pose_age),
"deterministic_replay_fraction": (
deterministic_matches / deterministic_total
if deterministic_total
else 0.0
),
"deterministic_replay_frames": deterministic_total,
"source_paced": False,
"input_drop_count": 0,
}
def _distribution(values: np.ndarray) -> dict[str, float]:
return {
"minimum": float(np.min(values)),
"p50": float(np.percentile(values, 50)),
"p95": float(np.percentile(values, 95)),
"maximum": float(np.max(values)),
"mean": float(np.mean(values)),
}
def _read_worker_package(path: Path) -> dict[str, Any]:
root = path.expanduser().resolve(strict=True)
manifest = _read_json(root / "manifest.json")
identity = manifest.get("identity")
artifacts = manifest.get("artifacts")
identity_sha256 = manifest.get("identity_sha256")
package_id = manifest.get("package_id")
if (
root.is_symlink()
or manifest.get("schema_version") != WORKER_PACKAGE_SCHEMA
or not isinstance(identity, dict)
or not isinstance(artifacts, list)
or not isinstance(identity_sha256, str)
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
or package_id != f"l3-pointpillars-worker-package-{identity_sha256}"
or root.name != package_id
or Path(__file__).resolve(strict=True).parents[1] != root
):
raise RuntimeError("L3.1 worker package identity is invalid")
expected = set(identity.get("artifact_paths", []))
actual = {
item.relative_to(root).as_posix()
for item in root.rglob("*")
if item.is_file()
}
if actual != expected | {"manifest.json"}:
raise RuntimeError("L3.1 worker package file set changed")
for descriptor in artifacts:
if not isinstance(descriptor, dict):
raise RuntimeError("L3.1 worker package artifact is invalid")
relative = descriptor.get("path")
member = root / str(relative)
if (
not isinstance(relative, str)
or relative not in expected
or not member.is_file()
or member.is_symlink()
or descriptor.get("byte_length") != member.stat().st_size
or descriptor.get("sha256") != _sha256(member)
):
raise RuntimeError("L3.1 worker package artifact changed")
return manifest
def _require_triton_ready(url: str) -> None:
for endpoint in ("/v2/health/ready", f"/v2/models/{MODEL_NAME}/ready"):
request = urllib.request.Request(f"{url.rstrip('/')}{endpoint}", method="GET")
try:
with urllib.request.urlopen(request, timeout=10) as response:
if response.status != 200:
raise RuntimeError("canonical Triton is not ready")
except OSError as exc:
raise RuntimeError("canonical Triton or PointPillars is not ready") from exc
def _artifact(path: Path, kind: str, *, relative_to: Path) -> dict[str, object]:
return {
"kind": kind,
"path": path.relative_to(relative_to).as_posix(),
"byte_length": path.stat().st_size,
"sha256": _sha256(path),
}
def _directory_identity(root: Path) -> str:
digest = hashlib.sha256()
for path in sorted(root.glob("*.json"), key=lambda item: item.name):
digest.update(path.name.encode("ascii"))
digest.update(b"\0")
digest.update(bytes.fromhex(_sha256(path)))
return digest.hexdigest()
def _canonical_json(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
def _read_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise RuntimeError(f"invalid JSON artifact: {path.name}") from exc
if not isinstance(value, dict):
raise RuntimeError(f"invalid JSON artifact: {path.name}")
return value
def _write_once(path: Path, payload: object) -> None:
encoded = _canonical_json(payload)
if path.exists():
if path.read_bytes() != encoded:
raise RuntimeError(f"immutable artifact changed: {path.name}")
return
_atomic_bytes(path, encoded)
def _atomic_json(path: Path, payload: object) -> None:
_atomic_bytes(path, _canonical_json(payload))
def _atomic_bytes(path: Path, payload: bytes) -> None:
path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
descriptor, temporary = tempfile.mkstemp(
prefix=f".{path.name}.",
suffix=".tmp",
dir=path.parent,
)
try:
with os.fdopen(descriptor, "wb") as handle:
handle.write(payload)
handle.flush()
os.fsync(handle.fileno())
os.replace(temporary, path)
except BaseException:
try:
os.unlink(temporary)
except FileNotFoundError:
pass
raise
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
if __name__ == "__main__":
raise SystemExit(main())
@@ -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")
+30
View File
@@ -53,6 +53,36 @@ def test_advanced_index_is_empty_when_not_configured() -> None:
}
def test_advanced_index_publishes_l31_ravnoves_identity(
tmp_path: Path,
monkeypatch: MonkeyPatch,
) -> None:
result_id = f"l31-pointpillars-ravnoves-{'b' * 64}"
def fake_latest(provider: object) -> dict[str, str]:
assert callable(provider)
assert provider() == tmp_path # type: ignore[operator]
return {
"result_id": result_id,
"created_at_utc": "2026-07-31T10:56:45.861Z",
}
monkeypatch.setattr(advanced_api, "latest_l31_identity", fake_latest)
router = build_advanced_laboratory_router(
l31_ravnoves_root_provider=lambda: tmp_path,
)
route = _endpoint(router, "/api/v1/laboratory/advanced-index")
assert route()["items"] == [ # type: ignore[index,operator]
{
"work_id": "l31-pointpillars-ravnoves",
"result_id": result_id,
"created_at_utc": "2026-07-31T10:56:45.861Z",
"access": "read-only",
}
]
def test_advanced_index_reads_only_bounded_identity_documents(
tmp_path: Path,
) -> None:
+49
View File
@@ -0,0 +1,49 @@
from __future__ import annotations
import numpy as np
import pytest
from k1link.compute.l31_pointpillars_ravnoves import (
L31PointPillarsRavnovesError,
nearest_pose_indices,
select_visual_frame_indices,
sensor_frame_xyzi,
)
def test_nearest_pose_binding_prefers_earlier_pose_on_tie() -> None:
indices, age_ms = nearest_pose_indices(
np.asarray([10, 20, 31], dtype=np.int64) * 1_000_000,
np.asarray([5, 15, 30], dtype=np.int64) * 1_000_000,
)
assert indices.tolist() == [0, 1, 2]
assert age_ms.tolist() == [5.0, 5.0, 1.0]
def test_sensor_frame_xyzi_inverts_map_pose() -> None:
points = np.asarray([[11.0, 20.0, 30.0], [10.0, 22.0, 30.0]])
intensity = np.asarray([255, 0], dtype=np.uint8)
result = sensor_frame_xyzi(
points,
intensity,
position_map_xyz=np.asarray([10.0, 20.0, 30.0]),
orientation_map_from_lidar_xyzw=np.asarray([0.0, 0.0, 0.0, 1.0]),
)
np.testing.assert_allclose(
result,
np.asarray([[1.0, 0.0, 0.0, 1.0], [0.0, 2.0, 0.0, 0.0]]),
)
def test_visual_selection_prefers_vehicles_with_route_coverage() -> None:
selected = select_visual_frame_indices(
[0, 3, 1, 0, 0, 2],
[5, 3, 9, 0, 4, 2],
maximum_frames=3,
)
assert selected == (1, 2, 5)
def test_visual_selection_rejects_impossible_class_count() -> None:
with pytest.raises(L31PointPillarsRavnovesError):
select_visual_frame_indices([2], [1])
+202
View File
@@ -0,0 +1,202 @@
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from fastapi import APIRouter
from fastapi.routing import APIRoute
from pytest import raises
from k1link.web.l31_pointpillars_ravnoves_api import (
build_l31_pointpillars_ravnoves_router,
)
def _endpoint(router: APIRouter, path: str) -> object:
for route in router.routes:
if (
isinstance(route, APIRoute)
and route.path == path
and route.methods is not None
and "GET" in route.methods
):
return route.endpoint
raise AssertionError(f"GET {path} route is missing")
def _canonical(payload: object) -> bytes:
return json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
def _write(path: Path, payload: object) -> dict[str, object]:
content = _canonical(payload)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(content)
return {
"sha256": hashlib.sha256(content).hexdigest(),
"byte_length": len(content),
}
def _result(root: Path) -> tuple[str, str]:
frame_id = "000075"
identity = {
"source_session_id": "20260720T065719Z_viewer_live",
"source_pack_id": f"lidar-replay-pack-{'1' * 64}",
"source_logical_content_sha256": "2" * 64,
"model": {"name": "pointpillars", "source_model_sha256": "3" * 64},
"execution": {
"worker_host_id": "worker-006",
"sequential": True,
"parallel_workers": 1,
},
"authority": {
"shadow_only": True,
"commands_enabled": False,
"navigation_or_safety_accepted": False,
"accuracy_accepted": False,
},
}
identity_sha = hashlib.sha256(_canonical(identity)).hexdigest()
result_id = f"l31-pointpillars-ravnoves-{identity_sha}"
candidate = root / result_id
detail = {
"schema_version": "missioncore.l31-pointpillars-ravnoves-visual-frame/v1",
"frame_id": frame_id,
"points": {
"layout": "flat-xyzi",
"sampled_point_count": 1,
"values": [1.0, 2.0, 3.0, 0.5],
},
"prediction_boxes": [
{
"x_m": 1.0,
"y_m": 2.0,
"z_m": 0.5,
"length_m": 4.0,
"width_m": 2.0,
"height_m": 1.5,
"yaw_rad": 0.1,
"class_id": 0,
"model_class": "Vehicle",
"score": 0.8,
}
],
"interpretation": {
"ground_truth_available": False,
"boxes_are_model_hypotheses": True,
"accuracy_claim_allowed": False,
},
}
detail_descriptor = _write(
candidate / "visual-frames" / f"{frame_id}.json",
detail,
)
frame = {
"frame_id": frame_id,
"frame_index": 75,
"session_seconds": 7.5,
"source_point_count": 2143,
"prediction_count": 1,
"class_counts": {"Vehicle": 1, "Pedestrian": 0, "Cyclist": 0},
"inference_ms": 26.3,
"detail_path": f"visual-frames/{frame_id}.json",
"detail_sha256": detail_descriptor["sha256"],
"detail_byte_length": detail_descriptor["byte_length"],
}
catalog = {
"schema_version": "missioncore.l31-pointpillars-ravnoves-catalog/v1",
"result_id": result_id,
"source_session_id": identity["source_session_id"],
"frame_count": 1,
"frames": [frame],
}
catalog_descriptor = _write(candidate / "catalog.json", catalog)
manifest = {
"schema_version": "missioncore.l31-pointpillars-ravnoves/v1",
"result_id": result_id,
"identity_sha256": identity_sha,
"identity": identity,
"created_at_utc": "2026-07-31T10:00:00Z",
"status": "k1-cross-domain-transfer-measured-visual-review-required",
"metrics": {
"frame_count": 4570,
"input_admission_fraction": 1.0,
"output_schema_valid_fraction": 1.0,
},
"catalog": {
"path": "catalog.json",
"kind": "visual-frame-catalog",
**catalog_descriptor,
},
"limitations": ["Independent 3D ground truth is unavailable."],
"authority": identity["authority"],
}
_write(candidate / "manifest.json", manifest)
return result_id, frame_id
def test_l31_catalog_and_visual_frame_are_read_only(tmp_path: Path) -> None:
result_id, frame_id = _result(tmp_path)
router = build_l31_pointpillars_ravnoves_router(
root_provider=lambda: tmp_path,
)
catalog_route = _endpoint(
router,
"/api/v1/laboratory/l31/pointpillars-ravnoves/results",
)
frame_route = _endpoint(
router,
(
"/api/v1/laboratory/l31/pointpillars-ravnoves/"
"{result_id}/frames/{frame_id}"
),
)
catalog = catalog_route(limit=1) # type: ignore[operator]
assert catalog["configured"] is True
assert catalog["invalid_total"] == 0
assert catalog["items"][0]["result_id"] == result_id
assert catalog["items"][0]["status"] == (
"cross-domain-transfer-measured-visual-review-required"
)
assert catalog["items"][0]["frames"][0]["frame_id"] == frame_id
frame = frame_route( # type: ignore[operator]
result_id=result_id,
frame_id=frame_id,
)
assert frame["prediction_boxes"][0]["model_class"] == "Vehicle"
assert frame["interpretation"]["ground_truth_available"] is False
assert frame["access"] == "read-only"
def test_l31_visual_frame_fails_closed_after_mutation(tmp_path: Path) -> None:
result_id, frame_id = _result(tmp_path)
router = build_l31_pointpillars_ravnoves_router(
root_provider=lambda: tmp_path,
)
frame_route = _endpoint(
router,
(
"/api/v1/laboratory/l31/pointpillars-ravnoves/"
"{result_id}/frames/{frame_id}"
),
)
(tmp_path / result_id / "visual-frames" / f"{frame_id}.json").write_text(
"{}",
encoding="utf-8",
)
with raises(Exception) as caught:
frame_route( # type: ignore[operator]
result_id=result_id,
frame_id=frame_id,
)
assert getattr(caught.value, "status_code", None) == 404