feat(observatory): ship modular AI inference labs

This commit is contained in:
DCCONSTRUCTIONS
2026-09-04 17:59:05 +03:00
parent eff60e490a
commit cada687173
145 changed files with 17651 additions and 1667 deletions
+96 -70
View File
@@ -2,17 +2,20 @@
from __future__ import annotations
from collections import OrderedDict
from collections.abc import Callable
from contextlib import suppress
from threading import Lock
from typing import Any, Literal
from uuid import UUID
from uuid import UUID, uuid5
import rerun as rr
import rerun_bindings as bindings
from rerun import blueprint as rrb
from k1link.viewer.recorded_blueprint_lifecycle import (
BlueprintSessionReleased,
RecordedBlueprintSessions,
)
from k1link.viewer.rerun_bridge import RerunSceneSettings
APPLICATION_ID = "nodedc_mission_core_recorded"
@@ -48,8 +51,9 @@ class _RecordedBlueprintStream:
"""Keep one bounded SDK blueprint source for each browser viewport.
Upstream 0.36.3 activates a clone, not this source store. Explicit refresh
makes layer changes visible but cannot retain the clone's operator eye.
It is opt-in for portable replay pending native camera-state support.
makes layer changes visible. Ordinary layer updates deliberately omit eye
components so the active clone retains the operator's camera. Only an
explicit 3D/plan/follow transition may write a new spatial eye preset.
"""
def __init__(
@@ -57,8 +61,10 @@ class _RecordedBlueprintStream:
application_id: str,
recording_id: str,
*,
blueprint_session_id: str,
view_reset_generation: Literal[0, 1],
) -> None:
self.blueprint_session_id = blueprint_session_id
self.view_reset_generation = view_reset_generation
self._lock = Lock()
self._sequence = 0
@@ -81,7 +87,7 @@ class _RecordedBlueprintStream:
def render(
self,
blueprint_factory: Callable[[bool, bool], rrb.Blueprint],
blueprint_factory: Callable[[bool, bool, str], rrb.Blueprint],
*,
follow_trajectory: bool,
plan_view: bool,
@@ -94,10 +100,20 @@ class _RecordedBlueprintStream:
update_eye_controls = self._eye_contract != eye_contract
# Initial admission/reset uses native framing. Explicit presets
# apply to mode transitions, after the viewer has a source cursor.
blueprint = blueprint_factory(update_eye_controls, self._eye_contract is not None)
# Keep one view identity for the lifetime of this browser owner.
# Replacing the UUID on every layer toggle rebuilt both native
# viewports, delayed a simple visibility change, and discarded the
# operator's active interaction state. The explicit reset
# generation already owns the one intentional identity change.
view_instance_token = self.blueprint_session_id
blueprint = blueprint_factory(
update_eye_controls,
self._eye_contract is not None,
view_instance_token,
)
# Appending rows alone does not refresh upstream's active clone.
# Keep legacy admission unchanged; portable replay opts into
# working layer updates with an explicitly documented eye reset.
# Reactivation makes layer changes visible; eye components remain
# absent unless the explicit view-mode contract changed above.
make_active = self._sequence == 0 or reactivate_updates
self._blueprint_recording.set_time(
"blueprint",
@@ -127,40 +143,7 @@ class _RecordedBlueprintStream:
self._transport_recording.disconnect()
_MAX_RECORDED_BLUEPRINT_STREAMS = 32
_recorded_blueprint_streams_lock = Lock()
_recorded_blueprint_streams: OrderedDict[tuple[str, str, str], _RecordedBlueprintStream] = (
OrderedDict()
)
def _stable_recorded_blueprint_stream(
*,
application_id: str,
recording_id: str,
blueprint_session_id: str,
view_reset_generation: Literal[0, 1],
) -> _RecordedBlueprintStream:
key = (application_id, recording_id, blueprint_session_id)
with _recorded_blueprint_streams_lock:
stream = _recorded_blueprint_streams.get(key)
if stream is not None and stream.view_reset_generation != view_reset_generation:
stream.close()
del _recorded_blueprint_streams[key]
stream = None
if stream is None:
stream = _RecordedBlueprintStream(
application_id,
recording_id,
view_reset_generation=view_reset_generation,
)
_recorded_blueprint_streams[key] = stream
else:
_recorded_blueprint_streams.move_to_end(key)
while len(_recorded_blueprint_streams) > _MAX_RECORDED_BLUEPRINT_STREAMS:
_, stale = _recorded_blueprint_streams.popitem(last=False)
stale.close()
return stream
recorded_blueprint_sessions = RecordedBlueprintSessions[_RecordedBlueprintStream]()
def recorded_blueprint(
@@ -170,15 +153,21 @@ def recorded_blueprint(
active_view: RecordedView = "spatial",
view_reset_generation: Literal[0, 1] = 0,
unified_perception: bool = False,
unified_camera_share: float = 0.46,
semantic_layer: Literal["city", "vegetation"] | None = None,
plan_view: bool = False,
show_detections_2d: bool = False,
show_camera_image: bool = True,
show_segmentation: bool = False,
show_cuboids_3d: bool = False,
show_costmap: bool = False,
follow_trajectory: bool = False,
update_eye_controls: bool = True,
explicit_spatial_preset: bool = False,
eye_position: tuple[float, float, float] | None = None,
eye_look_target: tuple[float, float, float] | None = None,
eye_up: tuple[float, float, float] | None = None,
view_instance_token: str | None = None,
) -> rrb.Blueprint:
accumulation = max(0.0, settings.accumulation_seconds)
time_ranges: list[rr.VisibleTimeRange] | None = None
@@ -218,6 +207,14 @@ def recorded_blueprint(
)
spatial_eye_controls = (
rrb.EyeControls3D.from_fields(
kind=rrb.Eye3DKind.Orbital,
position=eye_position,
look_target=eye_look_target,
eye_up=eye_up,
tracking_entity="/world/sensor_pose" if follow_trajectory else "",
)
if eye_position is not None and eye_look_target is not None and eye_up is not None
else rrb.EyeControls3D.from_fields(
kind=rrb.Eye3DKind.Orbital,
position=[0.0, 0.0, 30.0],
look_target=[0.0, 0.0, 0.0],
@@ -267,15 +264,21 @@ def recorded_blueprint(
# alone does not preserve edits in upstream's activated blueprint clone.
eye_controls=spatial_eye_controls,
)
spatial_view.id = (
RECORDED_SPATIAL_RESET_VIEW_ID if view_reset_generation else RECORDED_SPATIAL_VIEW_ID
def instance_id(primary: UUID, reset: UUID) -> UUID:
base = reset if view_reset_generation else primary
return uuid5(base, view_instance_token) if view_instance_token is not None else base
spatial_view.id = instance_id(
RECORDED_SPATIAL_VIEW_ID,
RECORDED_SPATIAL_RESET_VIEW_ID,
)
camera_view = rrb.Spatial2DView(
origin="/perception/camera",
name="Оригинальное видео · слои AI",
background=[7, 8, 10, 255],
overrides={
"/perception/camera/image": rrb.EntityBehavior(visible=True),
"/perception/camera/image": rrb.EntityBehavior(visible=show_camera_image),
"/perception/camera/detections": rrb.EntityBehavior(
visible=show_detections_2d,
),
@@ -290,8 +293,9 @@ def recorded_blueprint(
),
},
)
camera_view.id = (
RECORDED_CAMERA_RESET_VIEW_ID if view_reset_generation else RECORDED_CAMERA_VIEW_ID
camera_view.id = instance_id(
RECORDED_CAMERA_VIEW_ID,
RECORDED_CAMERA_RESET_VIEW_ID,
)
perception_3d_view = rrb.Spatial3DView(
# Cuboids are expressed in the same calibrated world frame as the
@@ -325,10 +329,9 @@ def recorded_blueprint(
},
eye_controls=spatial_eye_controls,
)
perception_3d_view.id = (
RECORDED_PERCEPTION_3D_RESET_VIEW_ID
if view_reset_generation
else RECORDED_PERCEPTION_3D_VIEW_ID
perception_3d_view.id = instance_id(
RECORDED_PERCEPTION_3D_VIEW_ID,
RECORDED_PERCEPTION_3D_RESET_VIEW_ID,
)
metrics_view = rrb.TimeSeriesView(
origin="/metrics/device",
@@ -357,16 +360,16 @@ def recorded_blueprint(
spatial_view.visualizer_overrides["/world/perception/boxes3d"] = [
rrb.EntityBehavior(visible=show_cuboids_3d),
]
camera_share = min(0.9, max(0.1, unified_camera_share))
root_container = rrb.Horizontal(
camera_view,
spatial_view,
column_shares=[0.46, 0.54],
column_shares=[camera_share, 1.0 - camera_share],
name="Единая сцена восприятия",
)
root_container.id = (
RECORDED_UNIFIED_RESET_ROOT_CONTAINER_ID
if view_reset_generation
else RECORDED_UNIFIED_ROOT_CONTAINER_ID
root_container.id = instance_id(
RECORDED_UNIFIED_ROOT_CONTAINER_ID,
RECORDED_UNIFIED_RESET_ROOT_CONTAINER_ID,
)
else:
# Keep operator video and 3D cuboids as direct root views. Rerun's nested
@@ -384,7 +387,7 @@ def recorded_blueprint(
# replace it from a later blueprint message. Give every operator mode (and
# its explicit reset generation) a stable root identity so the requested
# child is authoritative instead of inheriting a previously visited tab.
root_container.id = {
base_root_id = {
("spatial", 0): RECORDED_ROOT_CONTAINER_ID,
("spatial", 1): RECORDED_SPATIAL_RESET_ROOT_CONTAINER_ID,
("perception", 0): RECORDED_PERCEPTION_ROOT_CONTAINER_ID,
@@ -394,6 +397,11 @@ def recorded_blueprint(
("metrics", 0): RECORDED_METRICS_ROOT_CONTAINER_ID,
("metrics", 1): RECORDED_METRICS_RESET_ROOT_CONTAINER_ID,
}[(active_view, view_reset_generation)]
root_container.id = (
uuid5(base_root_id, view_instance_token)
if view_instance_token is not None
else base_root_id
)
if include_initial_playback_state:
return rrb.Blueprint(
@@ -424,19 +432,26 @@ def recorded_blueprint_rrd(
active_view: RecordedView = "spatial",
view_reset_generation: Literal[0, 1] = 0,
unified_perception: bool = False,
unified_camera_share: float = 0.46,
semantic_layer: Literal["city", "vegetation"] | None = None,
plan_view: bool = False,
show_detections_2d: bool = False,
show_camera_image: bool = True,
show_segmentation: bool = False,
show_cuboids_3d: bool = False,
show_costmap: bool = False,
follow_trajectory: bool = False,
reactivate_updates: bool = False,
eye_position: tuple[float, float, float] | None = None,
eye_look_target: tuple[float, float, float] | None = None,
eye_up: tuple[float, float, float] | None = None,
) -> bytes:
"""Serialize a bounded active blueprint update without recorded data."""
def build_blueprint(
update_eye_controls: bool, use_spatial_preset: bool = False
update_eye_controls: bool,
use_spatial_preset: bool = False,
view_instance_token: str | None = None,
) -> rrb.Blueprint:
return recorded_blueprint(
settings,
@@ -444,32 +459,43 @@ def recorded_blueprint_rrd(
active_view=active_view,
view_reset_generation=view_reset_generation,
unified_perception=unified_perception,
unified_camera_share=unified_camera_share,
semantic_layer=semantic_layer,
plan_view=plan_view,
show_detections_2d=show_detections_2d,
show_camera_image=show_camera_image,
show_segmentation=show_segmentation,
show_cuboids_3d=show_cuboids_3d,
show_costmap=show_costmap,
follow_trajectory=follow_trajectory,
update_eye_controls=update_eye_controls,
explicit_spatial_preset=reactivate_updates and use_spatial_preset,
explicit_spatial_preset=use_spatial_preset and update_eye_controls,
eye_position=eye_position,
eye_look_target=eye_look_target,
eye_up=eye_up,
view_instance_token=view_instance_token,
)
payload: bytes | None
if blueprint_session_id is not None:
try:
payload = _stable_recorded_blueprint_stream(
application_id=application_id,
recording_id=recording_id,
blueprint_session_id=blueprint_session_id,
view_reset_generation=view_reset_generation,
).render(
build_blueprint,
follow_trajectory=follow_trajectory,
plan_view=plan_view,
reactivate_updates=reactivate_updates,
payload = recorded_blueprint_sessions.use(
(application_id, recording_id, blueprint_session_id),
view_reset_generation,
lambda: _RecordedBlueprintStream(
application_id,
recording_id,
blueprint_session_id=blueprint_session_id,
view_reset_generation=view_reset_generation,
),
lambda stream: stream.render(
build_blueprint,
follow_trajectory=follow_trajectory,
plan_view=plan_view,
reactivate_updates=reactivate_updates,
),
)
except RecordedBlueprintError:
except (RecordedBlueprintError, BlueprintSessionReleased):
raise
except Exception as exc:
raise RecordedBlueprintError("failed to serialize stable recorded blueprint") from exc
@@ -0,0 +1,129 @@
"""Ephemeral viewport resources: renew while mounted, release at termination."""
from __future__ import annotations
import time
from collections import OrderedDict
from collections.abc import Callable
from dataclasses import dataclass
from threading import RLock
from typing import Protocol
BlueprintKey = tuple[str, str, str]
class Closable(Protocol):
def close(self) -> None: ...
class BlueprintSessionReleased(RuntimeError):
"""A late update cannot resurrect an explicitly released viewport."""
@dataclass
class _Entry[Resource: Closable]:
resource: Resource
generation: int
last_used: float
class RecordedBlueprintSessions[Resource: Closable]:
"""No TTL applies while a render is running; idle owners renew separately.
The small registry lock also serializes release with render. Thus the
release acknowledgement means native resources have actually been closed,
and a previously queued update is fenced by a lightweight tombstone.
"""
def __init__(
self,
*,
ttl_seconds: float = 300.0,
max_entries: int = 32,
clock: Callable[[], float] = time.monotonic,
) -> None:
self._ttl = ttl_seconds
self._max_entries = max_entries
self._clock = clock
self._lock = RLock()
self._entries: OrderedDict[BlueprintKey, _Entry[Resource]] = OrderedDict()
self._released: OrderedDict[BlueprintKey, float] = OrderedDict()
def _expire(self, now: float) -> None:
for key, entry in list(self._entries.items()):
if now - entry.last_used >= self._ttl:
del self._entries[key]
entry.resource.close()
for key, expires in list(self._released.items()):
if expires <= now:
del self._released[key]
def use[Result](
self,
key: BlueprintKey,
generation: int,
create: Callable[[], Resource],
render: Callable[[Resource], Result],
) -> Result:
with self._lock:
now = self._clock()
self._expire(now)
if key in self._released:
raise BlueprintSessionReleased("recorded viewport has been released")
entry = self._entries.get(key)
if entry is not None and entry.generation != generation:
del self._entries[key]
entry.resource.close()
entry = None
if entry is None:
entry = _Entry(create(), generation, now)
self._entries[key] = entry
self._entries.move_to_end(key)
while len(self._entries) > self._max_entries:
_, stale = self._entries.popitem(last=False)
stale.resource.close()
try:
return render(entry.resource)
except BaseException:
# A failed operation must not retain its partial native store.
del self._entries[key]
entry.resource.close()
raise
finally:
entry.last_used = self._clock()
def renew(self, key: BlueprintKey) -> bool:
with self._lock:
now = self._clock()
self._expire(now)
entry = self._entries.get(key)
if entry is None:
return False
entry.last_used = now
self._entries.move_to_end(key)
return True
def release(self, key: BlueprintKey) -> None:
with self._lock:
now = self._clock()
self._expire(now)
entry = self._entries.pop(key, None)
self._released[key] = now + self._ttl
self._released.move_to_end(key)
# Tombstones contain identities only, never SDK resources or data.
while len(self._released) > 4096:
self._released.popitem(last=False)
if entry is not None:
entry.resource.close()
def expire(self) -> None:
with self._lock:
self._expire(self._clock())
def close(self) -> None:
with self._lock:
entries = list(self._entries.values())
self._entries.clear()
self._released.clear()
for entry in entries:
entry.resource.close()
+187
View File
@@ -0,0 +1,187 @@
"""Scene-derived orbital camera bounds for recorded Rerun views."""
from __future__ import annotations
import math
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
import numpy as np
import pyarrow as pa
import pyarrow.compute as pc
import rerun_bindings as bindings
SESSION_TIMELINE = "session_time"
MIN_ORBIT_DISTANCE = 0.02
MAX_ORBITAL_ZOOM_OUT_FACTOR = 5.0
@dataclass(frozen=True)
class _SpatialBoundsSeries:
times_ns: np.ndarray
lower: np.ndarray
upper: np.ndarray
@lru_cache(maxsize=8)
def _recorded_spatial_bounds_index(
path_text: str,
byte_length: int,
modified_ns: int,
) -> dict[str, _SpatialBoundsSeries]:
"""Build a small temporal bounds index for one immutable RRD generation.
Layer and follow buttons only change a blueprint. Re-decoding the complete
source RRD for every click made those controls wait on archive I/O. The
cache key includes the file generation fingerprint; cached values contain
only timestamps and six floats per sample, never point-cloud payloads.
"""
del byte_length, modified_ns # Generation identity is carried by the cache key.
reader = bindings.RrdReaderInternal(path_text)
recording = next(
(entry for entry in reader.store_entries() if entry.kind == "recording"),
None,
)
if recording is None:
raise ValueError("recorded camera source has no recording store")
rows: dict[str, list[tuple[int, np.ndarray, np.ndarray]]] = {
"/world/points": [],
"/world/trajectory": [],
}
for chunk in reader.stream(recording):
entity_path = str(chunk.entity_path)
if entity_path not in rows:
continue
batch = chunk.to_record_batch()
names = batch.column_names
if SESSION_TIMELINE not in names:
continue
component = (
"Points3D:positions" if entity_path == "/world/points" else "LineStrips3D:strips"
)
if component not in names:
continue
times = np.asarray(
batch.column(names.index(SESSION_TIMELINE)).cast(pa.int64()),
dtype=np.int64,
)
spatial = batch.column(names.index(component))
for index, timestamp in enumerate(times):
values = _spatial_values(
spatial.slice(index, 1),
nested=entity_path == "/world/trajectory",
)
if not values.size:
continue
finite = values[np.isfinite(values).all(axis=1)]
if not finite.size:
continue
rows[entity_path].append(
(
int(timestamp),
np.min(finite, axis=0),
np.max(finite, axis=0),
)
)
result: dict[str, _SpatialBoundsSeries] = {}
for entity_path, samples in rows.items():
if not samples:
continue
samples.sort(key=lambda sample: sample[0])
result[entity_path] = _SpatialBoundsSeries(
times_ns=np.asarray([sample[0] for sample in samples], dtype=np.int64),
lower=np.asarray([sample[1] for sample in samples], dtype=np.float32),
upper=np.asarray([sample[2] for sample in samples], dtype=np.float32),
)
return result
def recorded_orbital_radius_limit(
recording_path: Path,
*,
current_time_ns: int,
accumulation_seconds: float,
show_points: bool,
show_trajectory: bool,
) -> float | None:
"""Match Rerun 0.36.3's current scene-diagonal zoom-out limit.
The LAB 3D view roots its native mapping data at ``/world``. Points and
trajectory are the source entities whose visible-time query changes with
the operator's accumulation setting. Derived layers are deliberately not
folded into this source contract: they use the same calibrated world frame
and do not own navigation.
"""
if current_time_ns < 0 or not math.isfinite(accumulation_seconds) or accumulation_seconds < 0:
raise ValueError("invalid recorded camera query")
wanted = {
*(("/world/points",) if show_points else ()),
*(("/world/trajectory",) if show_trajectory else ()),
}
if not wanted:
return None
path = recording_path.expanduser().absolute()
if path.is_symlink() or not path.is_file():
raise ValueError("recorded camera source is unavailable")
stat = path.stat()
series_by_entity = _recorded_spatial_bounds_index(
str(path),
stat.st_size,
stat.st_mtime_ns,
)
lower_ns = current_time_ns - round(accumulation_seconds * 1_000_000_000)
lower = np.array([np.inf, np.inf, np.inf], dtype=np.float32)
upper = np.array([-np.inf, -np.inf, -np.inf], dtype=np.float32)
found = False
for entity_path in wanted:
series = series_by_entity.get(entity_path)
if series is None:
continue
if accumulation_seconds > 0:
selected = (series.times_ns >= lower_ns) & (series.times_ns <= current_time_ns)
else:
eligible_end = int(np.searchsorted(series.times_ns, current_time_ns, side="right"))
if eligible_end == 0:
continue
latest_time = series.times_ns[eligible_end - 1]
selected = series.times_ns == latest_time
if not selected.any():
continue
lower = np.minimum(lower, np.min(series.lower[selected], axis=0))
upper = np.maximum(upper, np.max(series.upper[selected], axis=0))
found = True
if not found:
return None
# macaw::BoundingBox and Rerun's eye controller operate in f32.
diagonal = np.float32(np.linalg.norm((upper - lower).astype(np.float32)))
if not np.isfinite(diagonal) or diagonal <= 0:
return None
return float(
max(
np.float32(MIN_ORBIT_DISTANCE),
np.float32(diagonal * np.float32(MAX_ORBITAL_ZOOM_OUT_FACTOR)),
)
)
def _spatial_values(column: pa.Array, *, nested: bool) -> np.ndarray:
flattened = pc.list_flatten(column)
if nested:
flattened = pc.list_flatten(flattened)
if not pa.types.is_fixed_size_list(flattened.type) or flattened.type.list_size != 3:
raise ValueError("recorded spatial component is not a 3D vector")
# Flatten through Arrow rather than reading ``FixedSizeListArray.values``:
# the latter exposes the complete backing buffer and ignores a sliced
# array's logical offset.
values = pc.list_flatten(flattened).to_numpy(zero_copy_only=False)
if values.size == 0:
return np.empty((0, 3), dtype=np.float32)
return np.asarray(values, dtype=np.float32).reshape((-1, 3))