fix(viewer): gate recorded perception readiness

This commit is contained in:
DCCONSTRUCTIONS
2026-07-23 12:27:48 +03:00
parent ee15160862
commit 9f712bf353
6 changed files with 347 additions and 40 deletions
+67 -15
View File
@@ -105,16 +105,10 @@ class _CuboidPresentationState:
colors: np.ndarray,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, list[str]] | None:
accepted = [
item
for item in objects
if str(item.get("cuboid_status", "")).startswith("accepted-")
item for item in objects if str(item.get("cuboid_status", "")).startswith("accepted-")
]
if not (
len(accepted)
== len(centers)
== len(half_sizes)
== len(quaternions)
== len(colors)
len(accepted) == len(centers) == len(half_sizes) == len(quaternions) == len(colors)
):
raise RecordedPerceptionOverlayError(
"integrated perception cuboid presentation arrays are inconsistent"
@@ -343,6 +337,8 @@ class IntegratedPerceptionOverlayStore:
self.cache_root = cache_root.expanduser().absolute()
self.ffmpeg_path = ffmpeg_path.expanduser().absolute()
self._lock = threading.Lock()
self._catalog_signature: tuple[Any, ...] | None = None
self._latest_by_session: dict[str, IntegratedPerceptionResult | None] = {}
def render(
self,
@@ -402,10 +398,21 @@ class IntegratedPerceptionOverlayStore:
return payload
def _latest(self, session_id: str) -> IntegratedPerceptionResult | None:
signature = _directory_catalog_signature(
self.jobs_root,
self.results_root,
self.lidar_packs_root,
)
if signature != self._catalog_signature:
self._catalog_signature = signature
self._latest_by_session.clear()
if session_id in self._latest_by_session:
return self._latest_by_session[session_id]
try:
jobs = sorted(self.jobs_root.iterdir())
candidates = sorted(self.results_root.iterdir())
except FileNotFoundError:
self._latest_by_session[session_id] = None
return None
if len(jobs) > MAX_SCAN or len(candidates) > MAX_SCAN:
raise RecordedPerceptionOverlayError("integrated perception catalog is outside bounds")
@@ -436,8 +443,57 @@ class IntegratedPerceptionOverlayStore:
):
matches.append(value)
if not matches:
self._latest_by_session[session_id] = None
return None
return max(matches, key=lambda value: (value.created_at_utc, value.result_id))
latest = max(matches, key=lambda value: (value.created_at_utc, value.result_id))
self._latest_by_session[session_id] = latest
return latest
def _directory_catalog_signature(*roots: Path) -> tuple[Any, ...]:
"""Track immutable lab catalogs without rehashing every large artifact per request.
Accepted result directories are append-only. Their directory mtimes still
change when an atomic manifest replacement occurs, while a new experiment
changes the parent listing. This cheap signature therefore invalidates the
process-local validation memo without weakening the first full integrity
validation of every catalog generation.
"""
signature: list[Any] = []
for root in roots:
try:
root_stat = root.stat()
entries = sorted(root.iterdir(), key=lambda path: path.name)
except FileNotFoundError:
signature.append((str(root), None))
continue
if len(entries) > MAX_SCAN:
raise RecordedPerceptionOverlayError("integrated perception catalog is outside bounds")
entry_signature = []
for entry in entries:
try:
metadata = entry.lstat()
except FileNotFoundError:
# A concurrent atomic publication will alter the parent mtime
# and be observed on the next request.
continue
entry_signature.append(
(
entry.name,
metadata.st_mode,
metadata.st_size,
metadata.st_mtime_ns,
)
)
signature.append(
(
str(root),
root_stat.st_mtime_ns,
tuple(entry_signature),
)
)
return tuple(signature)
def _render(
@@ -630,9 +686,7 @@ def _camera_proxy_file(
os.close(descriptor)
path = Path(name)
path.unlink(missing_ok=True)
frame_filter = (
f"select=between(n\\,{start_frame_index}\\,{end_frame_index}),setpts=N/(10*TB)"
)
frame_filter = f"select=between(n\\,{start_frame_index}\\,{end_frame_index}),setpts=N/(10*TB)"
try:
completed = subprocess.run(
[
@@ -900,9 +954,7 @@ def _canonical_json(value: object) -> bytes:
def _finite(value: object) -> TypeGuard[int | float]:
return (
isinstance(value, int | float)
and not isinstance(value, bool)
and bool(np.isfinite(value))
isinstance(value, int | float) and not isinstance(value, bool) and bool(np.isfinite(value))
)