Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1f814c1
go2 detections
leshy Dec 27, 2025
c5e74d8
Add rerun visualization support (Lesh's approach)
Nabla7 Dec 28, 2025
72d2d65
Merge release_detections: Add detection support to Go2 blueprints
Nabla7 Dec 28, 2025
eadee24
Add rerun + controls web interface to WebsocketVisModule
Nabla7 Dec 28, 2025
eea3f57
Add Rerun web viewer integration with control dashboard
Nabla7 Dec 28, 2025
576af79
CI code cleanup
Nabla7 Dec 28, 2025
11b90f4
feat(dashboard): Add centralized RerunModule for visualization
Nabla7 Dec 28, 2025
2e79ea5
CI code cleanup
Nabla7 Dec 28, 2025
1bd4398
fix(rerun): camera frustum now tracks robot odometry
Nabla7 Dec 28, 2025
621970c
CI code cleanup
Nabla7 Dec 28, 2025
46376f3
remove garbage
Nabla7 Dec 29, 2025
281107f
feat: distributed Rerun logging with proper transform hierarchy
Nabla7 Dec 29, 2025
a02010a
CI code cleanup
Nabla7 Dec 29, 2025
a136c6e
feat: add camera/costmap panels and native viewer option
Nabla7 Dec 29, 2025
f70a4f4
CI code cleanup
Nabla7 Dec 29, 2025
dc1188e
feat: Out.to_rerun() API for declarative Rerun logging
Nabla7 Dec 29, 2025
39ee9a3
feat(rerun): Upgrade visualization with boxes, colormap, and dual cos…
Nabla7 Dec 29, 2025
41b0238
CI code cleanup
Nabla7 Dec 29, 2025
e46d97c
fix(rerun): Flip costmap 2D image axes to match world coordinates
Nabla7 Dec 29, 2025
6b25f4f
feat(web): Build command-center as standalone React app for unified d…
Nabla7 Dec 30, 2025
6e4629b
CI code cleanup
Nabla7 Dec 30, 2025
483703b
style(rerun): Set 3D view background to black
Nabla7 Dec 30, 2025
2de81e0
refactor(rerun): Address PR review feedback
Nabla7 Dec 30, 2025
762facf
CI code cleanup
Nabla7 Dec 30, 2025
4fba609
refactor(image): Fix PR feedback - shared to_rerun, DepthImage, simpl…
Nabla7 Dec 30, 2025
0f26381
fix(rerun): Costmap mesh only renders known cells, proper colors
Nabla7 Dec 30, 2025
7f2c72d
feat: add latency logging panels to Rerun viewer
Nabla7 Dec 30, 2025
14172ef
CI code cleanup
Nabla7 Dec 30, 2025
c488f5c
feat: add latency logging panels + vectorize mesh generation
Nabla7 Dec 30, 2025
ca278f3
CI code cleanup
Nabla7 Dec 30, 2025
0a6175b
fix(metrics): Use monotonic timestamps for accurate latency in replay
Nabla7 Dec 31, 2025
545075a
feat: Viewer backend toggle + async Rerun logging for 10Hz costmap
Nabla7 Dec 31, 2025
9f22f24
refactor: Consolidate to single VIEWER_BACKEND config
Nabla7 Dec 31, 2025
d203fd4
refactor: Modular Rerun blueprint composition
Nabla7 Dec 31, 2025
859e38d
fix:imports and conditional Rerun
Nabla7 Dec 31, 2025
d6fbb3e
refactor: Move CostMapper Rerun logging to async thread
Nabla7 Dec 31, 2025
d157b45
feat: Set rerun-native as default + improve costmap colors
Nabla7 Dec 31, 2025
03fe30f
Merge branch 'dev' into feat/rerun-latency-panels
Nabla7 Dec 31, 2025
94319e3
refactor: Address PR review feedback from Paul
Nabla7 Dec 31, 2025
dc8cb8f
fix: Address all mypy errors from CI
Nabla7 Jan 1, 2026
18b88b9
style: Apply ruff formatting from pre-commit hooks
Nabla7 Jan 1, 2026
ffe0665
fix: Fix remaining 3 mypy errors
Nabla7 Jan 2, 2026
c3de2db
Merge latest dev into feat/rerun-latency-panels
Nabla7 Jan 2, 2026
d84d649
style: Add license header from pre-commit hook
Nabla7 Jan 2, 2026
7d7931c
fix: Import Any in foxglove_bridge.py
Nabla7 Jan 2, 2026
0407ed9
fix: Add missing type annotations after dev merge
Nabla7 Jan 2, 2026
918ac21
fix: Add abstract to_rerun() method and missing Any import
Nabla7 Jan 2, 2026
ac59941
fix mypy
paul-nechifor Jan 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions dimos/core/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
from types import MappingProxyType
from typing import Any, Literal, get_args, get_origin, get_type_hints

import rerun as rr
import rerun.blueprint as rrb

from dimos.core.global_config import GlobalConfig
from dimos.core.module import Module
from dimos.core.module_coordinator import ModuleCoordinator
Expand Down Expand Up @@ -280,6 +283,50 @@ def _connect_rpc_methods(self, module_coordinator: ModuleCoordinator) -> None:
requested_method_name, rpc_methods_dot[requested_method_name]
)

def _init_rerun_blueprint(self, module_coordinator: ModuleCoordinator) -> None:
"""Compose and send Rerun blueprint from module contributions.

Collects rerun_views() from all modules and composes them into a unified layout.
"""
# Collect view contributions from all modules
side_panels = []
for blueprint in self.blueprints:
if hasattr(blueprint.module, "rerun_views"):
views = blueprint.module.rerun_views()
if views:
side_panels.extend(views)

# Always include latency panel if we have any panels
if side_panels:
side_panels.append(
rrb.TimeSeriesView(
name="Latency (ms)",
origin="/metrics",
contents=[
"+ /metrics/voxel_map/latency_ms",
"+ /metrics/costmap/latency_ms",
],
)
)

# Compose final layout
if side_panels:
composed_blueprint = rrb.Blueprint(
rrb.Horizontal(
rrb.Spatial3DView(
name="3D View",
origin="world",
background=[0, 0, 0],
),
rrb.Vertical(*side_panels, row_shares=[2] + [1] * (len(side_panels) - 1)),
column_shares=[3, 1],
),
rrb.TimePanel(state="collapsed"),
rrb.SelectionPanel(state="collapsed"),
rrb.BlueprintPanel(state="collapsed"),
)
rr.send_blueprint(composed_blueprint)

def build(
self,
global_config: GlobalConfig | None = None,
Expand All @@ -294,6 +341,17 @@ def build(
self._check_requirements()
self._verify_no_name_conflicts()

# Initialize Rerun server before deploying modules (if backend is Rerun)
if global_config.rerun_enabled and global_config.viewer_backend.startswith("rerun"):
try:
from dimos.dashboard.rerun_init import init_rerun_server

server_addr = init_rerun_server(viewer_mode=global_config.viewer_backend)
global_config = global_config.model_copy(update={"rerun_server_addr": server_addr})
logger.info("Rerun server initialized", addr=server_addr)
except Exception as e:
logger.warning(f"Failed to initialize Rerun server: {e}")

module_coordinator = ModuleCoordinator(global_config=global_config)
module_coordinator.start()

Expand All @@ -303,6 +361,10 @@ def build(

module_coordinator.start_all_modules()

# Compose and send Rerun blueprint from module contributions
if global_config.viewer_backend.startswith("rerun"):
self._init_rerun_blueprint(module_coordinator)

return module_coordinator


Expand Down
6 changes: 6 additions & 0 deletions dimos/core/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@

from functools import cached_property
import re
from typing import Literal, TypeAlias

from pydantic_settings import BaseSettings, SettingsConfigDict

from dimos.mapping.occupancy.path_map import NavigationStrategy
from dimos.navigation.global_planner.types import AStarAlgorithm

ViewerBackend: TypeAlias = Literal["rerun-web", "rerun-native", "foxglove"]


def _get_all_numbers(s: str) -> list[float]:
return [float(x) for x in re.findall(r"-?\d+\.?\d*", s)]
Expand All @@ -29,6 +32,9 @@ class GlobalConfig(BaseSettings):
robot_ip: str | None = None
simulation: bool = False
replay: bool = False
rerun_enabled: bool = True
rerun_server_addr: str | None = None
viewer_backend: ViewerBackend = "rerun-native"
n_dask_workers: int = 2
memory_limit: str = "auto"
mujoco_camera_position: str | None = None
Expand Down
76 changes: 73 additions & 3 deletions dimos/core/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import reactivex as rx
from reactivex import operators as ops
from reactivex.disposable import Disposable
import rerun as rr

import dimos.core.colors as colors
from dimos.core.resource import Resource
Expand Down Expand Up @@ -137,20 +138,21 @@ def __str__(self) -> str:
)


class Out(Stream[T]):
class Out(Stream[T], ObservableMixin[T]):
_transport: Transport # type: ignore[type-arg]

def __init__(self, *argv, **kwargs) -> None: # type: ignore[no-untyped-def]
super().__init__(*argv, **kwargs)
self._rerun_config: dict | None = None # type: ignore[type-arg]
self._rerun_last_log: float = 0.0

@property
def transport(self) -> Transport[T]:
return self._transport

@transport.setter
def transport(self, value: Transport[T]) -> None:
# just for type checking
...
self._transport = value

@property
def state(self) -> State:
Expand All @@ -173,8 +175,76 @@ def publish(self, msg) -> None: # type: ignore[no-untyped-def]
if not hasattr(self, "_transport") or self._transport is None:
logger.warning(f"Trying to publish on Out {self} without a transport")
return

# Log to Rerun directly if configured
if self._rerun_config is not None:
self._log_to_rerun(msg)

self._transport.broadcast(self, msg)

def subscribe(self, cb) -> Callable[[], None]: # type: ignore[no-untyped-def]
"""Subscribe to this output stream.

Args:
cb: Callback function to receive messages

Returns:
Unsubscribe function
"""
return self.transport.subscribe(cb, self) # type: ignore[arg-type, func-returns-value, no-any-return]

def autolog_to_rerun(
self,
entity_path: str,
rate_limit: float | None = None,
**rerun_kwargs: Any,
) -> None:
"""Configure this output to auto-log to Rerun (fire-and-forget).

Call once in start() - messages auto-logged when published.

Args:
entity_path: Rerun entity path (e.g., "world/map")
rate_limit: Max Hz to log (None = unlimited)
**rerun_kwargs: Passed to msg.to_rerun() for rendering config
(e.g., radii=0.02, colormap="turbo", colors=[255,0,0])

Example:
def start(self):
super().start()
# Just declare it - fire and forget!
self.global_map.autolog_to_rerun("world/map", rate_limit=5.0, radii=0.02)
"""
self._rerun_config = {
"entity_path": entity_path,
"rate_limit": rate_limit,
"rerun_kwargs": rerun_kwargs,
}
self._rerun_last_log = 0.0

def _log_to_rerun(self, msg: T) -> None:
"""Log message to Rerun with rate limiting."""
if not hasattr(msg, "to_rerun"):
return

if self._rerun_config is None:
return

import time

config = self._rerun_config

# Rate limiting
if config["rate_limit"] is not None:
now = time.monotonic()
min_interval = 1.0 / config["rate_limit"]
if now - self._rerun_last_log < min_interval:
return # Skip - too soon
self._rerun_last_log = now

rerun_data = msg.to_rerun(**config["rerun_kwargs"])
rr.log(config["entity_path"], rerun_data)


class RemoteStream(Stream[T]):
@property
Expand Down
34 changes: 34 additions & 0 deletions dimos/dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2025 Dimensional Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Dashboard module for visualization and monitoring.

Rerun Initialization:
Main process (e.g., blueprints.build) starts Rerun server automatically.
Worker modules connect to the server via connect_rerun().

Usage in modules:
import rerun as rr
from dimos.dashboard.rerun_init import connect_rerun

class MyModule(Module):
def start(self):
super().start()
connect_rerun() # Connect to Rerun server
rr.log("my/entity", my_data.to_rerun())
"""

from dimos.dashboard.rerun_init import connect_rerun, init_rerun_server, shutdown_rerun

__all__ = ["connect_rerun", "init_rerun_server", "shutdown_rerun"]
Binary file added dimos/dashboard/dimos.rbl
Binary file not shown.
Loading
Loading