perf(lab): stream sealed spatial playback tracks

This commit is contained in:
DCCONSTRUCTIONS
2026-08-27 15:18:45 +03:00
parent b70dc4298a
commit bf76304e00
10 changed files with 911 additions and 50 deletions
+21
View File
@@ -344,6 +344,27 @@ class RecordedGeometryStore:
points.setflags(write=False)
return points
def playback_points_map(self) -> FloatArray:
"""Expose the sealed contiguous map-point track for binary LAB playback.
The returned array is the exact source-pack point index space. It is
read-only and deliberately excludes any UI projection or resampling so
the browser can retain it once and derive the current increment by the
verified offsets below.
"""
points = np.asarray(self._source["cloud_points_map"], dtype=np.dtype("<f4"))
if not points.flags.c_contiguous:
raise GeometryProviderError("source playback point track is not contiguous")
points.setflags(write=False)
return points
def playback_point_offsets(self) -> tuple[int, ...]:
"""Return immutable offsets into :meth:`playback_points_map`."""
offsets = np.asarray(self._source["cloud_offsets"], dtype=np.int64)
return tuple(int(value) for value in offsets)
def point_step_candidates_for_frame(self, frame_index: int) -> UInt8Array | None:
"""Expose the sealed low-step diagnostic in the source point index space.
+28 -12
View File
@@ -150,14 +150,23 @@ class RecordedThreatTimeline:
"access": "read-only-bounded-recorded-replay",
}
def chunk(self, *, start_sequence: int, frame_count: int) -> dict[str, object]:
def chunk(
self,
*,
start_sequence: int,
frame_count: int,
include_points: bool = True,
) -> dict[str, object]:
if not 0 <= start_sequence < len(self.index.offsets):
raise RecordedThreatTimelineError("recorded timeline chunk start is invalid")
if not 1 <= frame_count <= RECORDED_SPATIAL_MAX_CHUNK_FRAMES:
raise RecordedThreatTimelineError("recorded timeline chunk size is invalid")
stop = min(len(self.index.offsets), start_sequence + frame_count)
with self._lock:
frames = [self._project_frame(sequence) for sequence in range(start_sequence, stop)]
frames = [
self._project_frame(sequence, include_points=include_points)
for sequence in range(start_sequence, stop)
]
return {
"schema_version": RECORDED_SPATIAL_CHUNK_SCHEMA,
"result_id": self.result.result_id,
@@ -170,7 +179,12 @@ class RecordedThreatTimeline:
"access": "read-only-bounded-recorded-replay",
}
def _project_frame(self, sequence: int) -> dict[str, object]:
def _project_frame(
self,
sequence: int,
*,
include_points: bool,
) -> dict[str, object]:
row = _read_frame_at(self.frames_path, self.index, sequence)
frame_id = row.get("frame_id")
if not isinstance(frame_id, str) or not frame_id:
@@ -193,11 +207,13 @@ class RecordedThreatTimeline:
raise RecordedThreatTimelineError(
"recorded timeline current increment binding changed"
)
point_cloud, point_source_count = sample_points_in_body_frame(
points,
body_frame,
point_limit=RECORDED_SPATIAL_POINT_LIMIT,
)
point_source_count = int(points.shape[0])
if include_points:
point_cloud, point_source_count = sample_points_in_body_frame(
points,
body_frame,
point_limit=RECORDED_SPATIAL_POINT_LIMIT,
)
metric_visuals = project_metric_obstacles_to_body(
_mapping_array(row.get("metric_obstacles"), "metric obstacles"),
body_frame,
@@ -219,13 +235,13 @@ class RecordedThreatTimeline:
if body_frame is None
else {
"origin_map_xyz_m": list(body_frame.origin_map_xyz_m),
"basis_map_from_body": [
list(row) for row in body_frame.basis_map_from_body
],
"basis_map_from_body": [list(row) for row in body_frame.basis_map_from_body],
},
"point_cloud_body_xyz_m": point_cloud,
"point_cloud_source_count": point_source_count,
"point_cloud_sample_count": len(point_cloud),
"point_cloud_sample_count": point_source_count
if not include_points
else len(point_cloud),
"point_cloud_layer": "current-increment",
"rolling_map_component_count": sum(
item.get("state") == "retained" for item in metric_visuals
+125 -10
View File
@@ -13,17 +13,44 @@ from typing import Final
import numpy as np
from fastapi import APIRouter, HTTPException, Query, Response
from fastapi.responses import FileResponse
from k1link.laboratory.m49_tgs_full_shadow import (
PREFIX,
M49TgsFullShadowError,
M49TgsFullShadowResult,
PREFIX,
read_m49_tgs_full_shadow,
)
RootProvider = Callable[[], Path | None]
RESULT_ID: Final = re.compile(rf"^{re.escape(PREFIX)}[a-f0-9]{{64}}$")
ENDPOINT_ROOT: Final = "/api/v1/laboratory/m49/tgs-full-shadow"
PLAYBACK_TRACKS: Final = {
"centers": (
"costmap-cell-centers-xy-m.npy",
"application/x-npy",
"<f4",
[2244, 2],
),
"states": (
"costmap-states.npy",
"application/x-npy",
"|u1",
[4489, 2244],
),
"z-bounds": (
"costmap-z-bounds-m.npy",
"application/x-npy",
"<f4",
[4489, 2244, 2],
),
"frames": (
"frames.ndjson",
"application/x-ndjson",
"ndjson",
[4489],
),
}
def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: None) -> APIRouter:
@@ -55,16 +82,46 @@ def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: No
if not candidate.is_dir() or RESULT_ID.fullmatch(candidate.name) is None:
continue
try:
results.append(_project(_read_cached(str(candidate.resolve()), _signature(candidate))))
results.append(
_project(_read_cached(str(candidate.resolve()), _signature(candidate)))
)
except (M49TgsFullShadowError, OSError, ValueError):
invalid += 1
results.sort(key=lambda row: (str(row["created_at_utc"]), str(row["result_id"])), reverse=True)
results.sort(
key=lambda row: (str(row["created_at_utc"]), str(row["result_id"])), reverse=True
)
return _catalog(results[:limit], configured=True, invalid_total=invalid)
@router.get("/{result_id}")
def get_result(result_id: str) -> dict[str, object]:
return _project(sealed(result_id))
@router.get("/{result_id}/playback/manifest")
def get_playback_manifest(result_id: str) -> dict[str, object]:
return _playback_manifest(sealed(result_id))
@router.get("/{result_id}/playback/tracks/{track_id}")
def get_playback_track(result_id: str, track_id: str) -> FileResponse:
result = sealed(result_id)
descriptor = PLAYBACK_TRACKS.get(track_id)
if descriptor is None:
raise HTTPException(status_code=404, detail="M49 playback track not found")
name, media_type, _dtype, _shape = descriptor
artifact = _artifact_descriptor(result, name)
return FileResponse(
result.root / name,
media_type=media_type,
headers={
"Cache-Control": "private, max-age=31536000, immutable",
# These playback artifacts are consumed on the local control
# station. Avoid spending multiple seconds recompressing
# already compact numeric tracks on every cold open.
"Content-Encoding": "identity",
"ETag": f'"{artifact["sha256"]}"',
"X-Content-Type-Options": "nosniff",
},
)
@router.get("/{result_id}/frames/{source_sequence}/spatial")
def get_spatial(result_id: str, source_sequence: int) -> Response:
if source_sequence < 0 or source_sequence >= 4489:
@@ -75,11 +132,16 @@ def build_m49_tgs_full_shadow_router(*, root_provider: RootProvider = lambda: No
str(result.root), result_id, source_sequence, _evidence_signature(result.root)
)
except (OSError, ValueError, KeyError, json.JSONDecodeError):
raise HTTPException(status_code=503, detail="M49 TGS full-shadow spatial evidence failed verification") from None
raise HTTPException(
status_code=503, detail="M49 TGS full-shadow spatial evidence failed verification"
) from None
return Response(
content=content,
media_type="application/json",
headers={"Cache-Control": "private, max-age=31536000, immutable", "X-Content-Type-Options": "nosniff"},
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"X-Content-Type-Options": "nosniff",
},
)
@router.get("/{result_id}/spatial/chunk")
@@ -134,7 +196,9 @@ def _frame_json_cached(
root_path = Path(root)
frame_signature = (signature[-2], signature[-1])
frame = _frames(root, frame_signature)[source_sequence]
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False)
centers = np.load(
root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False
)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
states = states_all[source_sequence]
@@ -169,13 +233,20 @@ def _frame_json_cached(
],
},
"metrics": copy.deepcopy(frame),
"state_codes": {"UNOBSERVED": 0, "GROUND_SUPPORT": 1, "NONGROUND_OCCUPIED": 2, "UNKNOWN_REJECTED": 3},
"state_codes": {
"UNOBSERVED": 0,
"GROUND_SUPPORT": 1,
"NONGROUND_OCCUPIED": 2,
"UNKNOWN_REJECTED": 3,
},
"aos_used": False,
"gpu_used": False,
"authority": {"navigation_or_safety_accepted": False, "visual_quality_accepted": False},
"access": "read-only",
}
return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False).encode("utf-8")
return json.dumps(
payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
).encode("utf-8")
@lru_cache(maxsize=16)
@@ -189,7 +260,9 @@ def _chunk_json_cached(
root_path = Path(root)
frame_signature = (signature[-2], signature[-1])
frames = _frames(root, frame_signature)
centers = np.load(root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False)
centers = np.load(
root_path / "costmap-cell-centers-xy-m.npy", mmap_mode="r", allow_pickle=False
)
states_all = np.load(root_path / "costmap-states.npy", mmap_mode="r", allow_pickle=False)
z_all = np.load(root_path / "costmap-z-bounds-m.npy", mmap_mode="r", allow_pickle=False)
if (
@@ -258,6 +331,46 @@ def _chunk_json_cached(
).encode("utf-8")
def _artifact_descriptor(
result: M49TgsFullShadowResult,
name: str,
) -> dict[str, object]:
for artifact in result.manifest["artifacts"]:
if artifact.get("path") == name:
return artifact
raise ValueError(f"full-shadow artifact is not declared: {name}")
def _playback_manifest(result: M49TgsFullShadowResult) -> dict[str, object]:
tracks: list[dict[str, object]] = []
total_bytes = 0
for track_id, (name, media_type, dtype, shape) in PLAYBACK_TRACKS.items():
artifact = _artifact_descriptor(result, name)
byte_length = int(artifact["byte_length"])
total_bytes += byte_length
tracks.append(
{
"id": track_id,
"url": (f"{ENDPOINT_ROOT}/{result.result_id}/playback/tracks/{track_id}"),
"media_type": media_type,
"dtype": dtype,
"shape": shape,
"byte_length": byte_length,
"sha256": artifact["sha256"],
}
)
return {
"schema_version": "missioncore.m49-tgs-full-shadow-playback/v1",
"result_id": result.result_id,
"coordinate_frame": "map-gravity-local",
"frame_count": 4489,
"cell_count": 2244,
"total_byte_length": total_bytes,
"tracks": tracks,
"access": "read-only",
}
def _project(result: M49TgsFullShadowResult) -> dict[str, object]:
return {
**copy.deepcopy(result.report),
@@ -269,7 +382,9 @@ def _project(result: M49TgsFullShadowResult) -> dict[str, object]:
}
def _catalog(items: list[dict[str, object]], *, configured: bool, invalid_total: int) -> dict[str, object]:
def _catalog(
items: list[dict[str, object]], *, configured: bool, invalid_total: int
) -> dict[str, object]:
return {
"schema_version": "missioncore.m49-tgs-full-shadow-catalog/v1",
"configured": configured,
+83 -3
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import copy
import hashlib
import json
import re
from collections.abc import Callable, Iterator
@@ -11,6 +12,7 @@ from pathlib import Path
from typing import Final
from fastapi import APIRouter, HTTPException, Query, Response
from fastapi.responses import StreamingResponse
from k1link.perception.threat_replay import (
THREAT_REPLAY_FRAME_SCHEMA,
@@ -166,7 +168,7 @@ def build_m4_threat_replay_router(
def get_timeline(result_id: str) -> dict[str, object]:
return copy.deepcopy(timeline(result_id).metadata())
@router.get("/results/{result_id}/timeline/chunk")
@router.get("/results/{result_id}/timeline/chunk", response_model=None)
def get_timeline_chunk(
result_id: str,
start: int = Query(default=0, ge=0),
@@ -175,14 +177,87 @@ def build_m4_threat_replay_router(
ge=1,
le=RECORDED_SPATIAL_MAX_CHUNK_FRAMES,
),
) -> dict[str, object]:
include_points: bool = Query(default=True),
) -> dict[str, object] | Response:
try:
return timeline(result_id).chunk(start_sequence=start, frame_count=count)
payload = timeline(result_id).chunk(
start_sequence=start,
frame_count=count,
include_points=include_points,
)
except RecordedThreatTimelineError:
raise HTTPException(
status_code=404,
detail="M4.6 timeline chunk не найден",
) from None
if include_points:
return payload
return Response(
content=json.dumps(
payload,
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
),
media_type="application/json",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"Content-Encoding": "identity",
"X-Content-Type-Options": "nosniff",
},
)
@router.get("/results/{result_id}/timeline/playback")
def get_timeline_playback(result_id: str) -> dict[str, object]:
projected = timeline(result_id)
points = projected.store.playback_points_map()
offsets = projected.store.playback_point_offsets()
content_sha256 = hashlib.sha256(memoryview(points).cast("B")).hexdigest()
return {
"schema_version": "missioncore.recorded-spatial-playback/v1",
"result_id": result_id,
"frame_count": len(offsets) - 1,
"point_count": int(points.shape[0]),
"point_offsets": list(offsets),
"track": {
"id": "points-map-f32",
"url": (
f"/api/v1/laboratory/m4-threat/results/{result_id}"
"/timeline/playback/tracks/points-map-f32"
),
"media_type": "application/octet-stream",
"dtype": "<f4",
"shape": [int(points.shape[0]), 3],
"bytes": int(points.nbytes),
"sha256": content_sha256,
},
"source_pack_sha256": projected.profile.source_pack_sha256,
"coordinate_frame": "map",
"ground_truth": False,
"authority": "replay-simulated",
"access": "read-only-sealed-binary-playback",
}
@router.get(
"/results/{result_id}/timeline/playback/tracks/points-map-f32",
response_class=StreamingResponse,
)
def get_timeline_playback_points(result_id: str) -> StreamingResponse:
projected = timeline(result_id)
points = projected.store.playback_points_map()
source_digest = projected.profile.source_pack_sha256
return StreamingResponse(
_binary_chunks(memoryview(points).cast("B")),
media_type="application/octet-stream",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"Content-Encoding": "identity",
"Content-Length": str(points.nbytes),
"ETag": f'"{source_digest}-points-map-f32"',
"X-Content-Type-Options": "nosniff",
"X-Uncompressed-Content-Length": str(points.nbytes),
},
)
@router.get("/results/{result_id}/timeline/frames/{sequence}/camera")
def get_timeline_camera(result_id: str, sequence: int) -> Response:
@@ -196,6 +271,11 @@ def build_m4_threat_replay_router(
return router
def _binary_chunks(view: memoryview, chunk_size: int = 1024 * 1024) -> Iterator[bytes]:
for start in range(0, view.nbytes, chunk_size):
yield bytes(view[start : start + chunk_size])
@lru_cache(maxsize=4)
def _read_threat_result_cached(
root_value: str,