feat(viewer): add recorded point colors and trajectory follow

This commit is contained in:
DCCONSTRUCTIONS
2026-07-23 13:33:08 +03:00
parent 9f712bf353
commit ecd95a22f0
16 changed files with 1218 additions and 36 deletions
+156
View File
@@ -0,0 +1,156 @@
from __future__ import annotations
import hashlib
import json
import struct
import subprocess
import sys
from pathlib import Path
from k1link.device_plugins.xgrids_k1.mqtt.capture import FRAME_HEADER, RAW_MAGIC
from k1link.device_plugins.xgrids_k1.recorded_point_colors import (
RecordedPointColorOverlayStore,
)
from k1link.sessions import ReplayArtifact, ReplayCommand
def _point_payload(x: float, intensity: int) -> bytes:
return struct.pack("<III", 16, 0, 0) + struct.pack(
"<fffBBBB",
x,
-2.0,
3.0,
10,
20,
30,
intensity,
)
def _command(tmp_path: Path) -> ReplayCommand:
source = tmp_path / "mqtt.raw.k1mqtt"
metadata_path = tmp_path / "mqtt.metadata.jsonl"
raw = bytearray(RAW_MAGIC)
metadata: list[dict[str, object]] = []
epoch_origin_ns = 1_784_124_315_000_000_000
monotonic_origin_ns = 9_000_000_000
topic = "RealtimePointcloud"
for sequence in range(1, 7):
payload = _point_payload(float(sequence), sequence * 20)
topic_bytes = topic.encode("utf-8")
frame_offset = len(raw)
raw.extend(FRAME_HEADER.pack(len(topic_bytes), len(payload)))
raw.extend(topic_bytes)
payload_offset = len(raw)
raw.extend(payload)
metadata.append(
{
"record_type": "message",
"sequence": sequence,
"received_at_epoch_ns": epoch_origin_ns + sequence * 100_000_000,
"received_monotonic_ns": monotonic_origin_ns + sequence * 100_000_000,
"topic": topic,
"payload_bytes": len(payload),
"payload_sha256": hashlib.sha256(payload).hexdigest(),
"raw_frame_offset": frame_offset,
"raw_payload_offset": payload_offset,
"raw_frame_bytes": len(raw) - frame_offset,
}
)
source.write_bytes(raw)
metadata_path.write_text(
"".join(json.dumps(item, separators=(",", ":")) + "\n" for item in metadata),
encoding="utf-8",
)
artifacts = (
ReplayArtifact(
artifact_id="raw-transport-primary",
path=source,
media_type="application/x-nodedc-k1mqtt",
file_byte_length=source.stat().st_size,
replay_byte_length=source.stat().st_size,
expected_sha256=hashlib.sha256(source.read_bytes()).hexdigest(),
),
ReplayArtifact(
artifact_id="raw-transport-index",
path=metadata_path,
media_type="application/x-ndjson",
file_byte_length=metadata_path.stat().st_size,
replay_byte_length=metadata_path.stat().st_size,
expected_sha256=hashlib.sha256(metadata_path.read_bytes()).hexdigest(),
),
)
return ReplayCommand(
session_id="recorded-color-test",
plugin_id="nodedc.device.xgrids-lixelkity-k1",
allowed_root=tmp_path,
session_root=tmp_path,
primary_artifact_id="raw-transport-primary",
artifacts=artifacts,
timeline_origin_epoch_ns=epoch_origin_ns,
timeline_origin_monotonic_ns=monotonic_origin_ns,
speed=1.0,
loop=False,
)
def test_recorded_color_overlay_logs_only_colors_at_operator_frame_cadence(
tmp_path: Path,
) -> None:
store = RecordedPointColorOverlayStore()
payload = store.render(
_command(tmp_path),
application_id="nodedc_mission_core_recorded",
recording_id="recording-001",
color_mode="height",
palette="viridis",
custom_color="#35d7c1",
)
output = tmp_path / "colors.rrd"
output.write_bytes(payload)
printed = subprocess.run(
[
sys.executable,
"-m",
"rerun_cli",
"rrd",
"print",
"-vvv",
"--entity",
"/world/points",
str(output),
],
check=True,
capture_output=True,
text=True,
).stdout
assert payload.startswith(b"RRF2")
assert printed.count("Points3D:colors") == 2
assert "Points3D:positions" not in printed
assert "session_time" in printed
def test_recorded_color_overlay_reuses_index_and_identical_payload(
tmp_path: Path,
) -> None:
store = RecordedPointColorOverlayStore()
command = _command(tmp_path)
first = store.render(
command,
application_id="nodedc_mission_core_recorded",
recording_id="recording-001",
color_mode="distance",
palette="plasma",
custom_color="#35d7c1",
)
second = store.render(
command,
application_id="nodedc_mission_core_recorded",
recording_id="recording-001",
color_mode="distance",
palette="plasma",
custom_color="#35d7c1",
)
assert second is first
+11
View File
@@ -247,12 +247,23 @@ def test_palettes_are_deterministic_and_custom_color_is_exact() -> None:
None,
RerunSceneSettings(color_mode="class", custom_color="#102030"),
)
custom_over_rgb = _point_colors(
positions,
intensities,
np.asarray([[255, 0, 0], [0, 255, 0]], dtype=np.uint8),
RerunSceneSettings(
color_mode="rgb",
palette="custom",
custom_color="#102030",
),
)
assert height.shape == (2, 3)
assert height.dtype == np.uint8
assert height[0].tolist() == [68, 1, 84]
assert height[1].tolist() == [253, 231, 37]
assert custom.tolist() == [[16, 32, 48], [16, 32, 48]]
assert custom_over_rgb.tolist() == [[16, 32, 48], [16, 32, 48]]
def test_runtime_reuses_one_bridge_across_sequential_sessions(tmp_path: Path) -> None:
+37
View File
@@ -37,6 +37,9 @@ from k1link.device_plugins.xgrids_k1.rrd_export import (
export_k1mqtt_to_rrd,
recorded_blueprint_rrd,
)
from k1link.viewer.recorded import (
RECORDED_SPATIAL_FOLLOW_VIEW_ID,
)
from k1link.viewer.recorded import (
recorded_blueprint as viewer_recorded_blueprint,
)
@@ -450,6 +453,40 @@ def test_viewer_blueprint_reset_is_bounded_and_recreates_render_views() -> None:
assert perception_behavior.visible.as_arrow_array().to_pylist() == [False]
def test_recorded_follow_mode_tracks_sensor_pose_with_an_independent_orbital_eye() -> None:
normal = viewer_recorded_blueprint(
RerunSceneSettings(),
include_initial_playback_state=False,
)
followed = viewer_recorded_blueprint(
RerunSceneSettings(),
include_initial_playback_state=False,
follow_trajectory=True,
)
followed_again = viewer_recorded_blueprint(
RerunSceneSettings(show_grid=False),
include_initial_playback_state=False,
follow_trajectory=True,
)
normal_view = normal.root_container.contents[0]
followed_view = followed.root_container.contents[0]
followed_eye = followed_view.properties["EyeControls3D"]
followed_components = {
str(batch.component_descriptor()): batch.as_arrow_array().to_pylist()
for batch in followed_eye.as_component_batches()
}
assert "EyeControls3D" not in normal_view.properties
assert followed_view.id == RECORDED_SPATIAL_FOLLOW_VIEW_ID
assert followed_again.root_container.contents[0].id == RECORDED_SPATIAL_FOLLOW_VIEW_ID
assert followed_view.id != normal_view.id
assert followed_components == {
"EyeControls3D:kind": [2],
"EyeControls3D:tracking_entity": ["/world/sensor_pose"],
}
def test_recorded_blueprint_layer_updates_preserve_store_until_explicit_reset(
monkeypatch: pytest.MonkeyPatch,
) -> None:
+95
View File
@@ -37,6 +37,7 @@ from k1link.web.session_api import (
LayoutPutRequest,
RecordedBlueprintRequest,
RecordedPerceptionRequest,
RecordedPointColorsRequest,
ReplayRequest,
build_session_router,
)
@@ -1083,6 +1084,7 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
show_trajectory=True,
show_grid=False,
point_size=6.25,
color_mode="height",
palette="custom",
custom_color="#112233",
active_view="perception",
@@ -1091,6 +1093,7 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
show_detections_2d=True,
show_segmentation=True,
show_cuboids_3d=True,
follow_trajectory=True,
),
)
)
@@ -1104,6 +1107,7 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
assert len(observed_settings) == 1
assert observed_settings[0].show_points is False
assert observed_settings[0].show_trajectory is True
assert observed_settings[0].color_mode == "height"
assert observed_kwargs[0]["blueprint_session_id"] == "a" * 32
assert observed_kwargs[0]["active_view"] == "perception"
assert observed_kwargs[0]["view_reset_generation"] == 1
@@ -1111,6 +1115,7 @@ def test_recorded_blueprint_endpoint_is_small_strict_and_session_scoped(
assert observed_kwargs[0]["show_detections_2d"] is True
assert observed_kwargs[0]["show_segmentation"] is True
assert observed_kwargs[0]["show_cuboids_3d"] is True
assert observed_kwargs[0]["follow_trajectory"] is True
with pytest.raises(ValueError):
RecordedBlueprintRequest.model_validate(
@@ -1201,6 +1206,96 @@ def test_recorded_perception_endpoint_returns_one_complete_optional_overlay(
assert empty.status_code == 204
def test_recorded_point_color_endpoint_is_strict_and_forwards_confined_command(
tmp_path: Path,
) -> None:
repository = tmp_path / "repo"
sessions = repository / "sessions"
session = make_legacy_session(sessions, "20260716T205632Z_viewer_live")
store = SessionStore(repository, data_dir=tmp_path / "data")
store.reconcile_archive(xgrids_k1_archive_source(sessions))
calls: list[tuple[str, str, str, str, str, str]] = []
class Provider:
def __call__(
self,
command: ReplayCommand,
*,
application_id: str,
recording_id: str,
color_mode: str,
palette: str,
custom_color: str,
) -> bytes:
calls.append(
(
command.session_id,
application_id,
recording_id,
color_mode,
palette,
custom_color,
)
)
return b"RRF2colors"
router = build_session_router(
store,
point_color_renderers={"nodedc.device.xgrids-lixelkity-k1": Provider()},
)
color_route = endpoint(
router,
"/api/v1/observation-sessions/{session_id}/point-colors.rrd",
"POST",
)
response = asyncio.run(
color_route(
session_id=session.name,
request=RecordedPointColorsRequest(
application_id="nodedc_mission_core_recorded",
recording_id="recording-001",
color_mode="distance",
palette="viridis",
custom_color="#112233",
),
)
)
assert response.body == b"RRF2colors"
assert response.media_type == "application/vnd.rerun.rrd"
assert response.headers["content-length"] == str(len(response.body))
assert len(calls) == 1
assert calls[0] == (
session.name,
"nodedc_mission_core_recorded",
"recording-001",
"distance",
"viridis",
"#112233",
)
unavailable_router = build_session_router(store)
unavailable_route = endpoint(
unavailable_router,
"/api/v1/observation-sessions/{session_id}/point-colors.rrd",
"POST",
)
with pytest.raises(HTTPException) as unavailable:
asyncio.run(
unavailable_route(
session_id=session.name,
request=RecordedPointColorsRequest(
application_id="nodedc_mission_core_recorded",
recording_id="recording-001",
color_mode="intensity",
palette="turbo",
custom_color="#35d7c1",
),
)
)
assert unavailable.value.status_code == 409
def test_session_router_exposes_opaque_recorded_media_manifest_and_ranges(
tmp_path: Path,
) -> None: