Skip to content

Commit a7c7273

Browse files
authored
RerunBridge module and CLI (#1154)
* feat: add RerunBridge module and CLI tool - Add RerunBridgeModule that subscribes to all LCM messages and logs those with to_rerun() methods to Rerun viewer - Add SubscribeAllCapable protocol to pubsub spec for type-safe subscribe_all support - Add rerun-bridge CLI tool with --viewer-mode native|web|none - Add "none" option to ViewerBackend for headless operation - Add unitree_go2_bridge blueprint variant * rerun bridge blueprint * adding stop method to bridge * working on rerun conversion to bridge * small cleanup * supress treid warning * occupancygrid meshing rewrite * bridge inline imports * implemented to_observable for dimos standard callbacks * settled on the temporary bridge api * restructure of pubsub patterns, good bridge match api * planner uses occupancygrid now, bridge improvements * image pickle fix * paul good vis * renamed visual transforms to converters * small cleamnup, pulled image from dev * looks nice * camerainfo knows how to render itself in the space * typing for to_rerun calls * utils type fix * better transport comment * base blueprints defined * small restructure * old rerun kicked out * typing cleanup * blueprints update * type fixes, manip type ignores * occupancygrid is never an image * tf cleanup * bridge comments, box for go2 * small comments * turn off local rerun * fix mypy type errors in CameraInfo and drake_world * global config singleton, web view works * deleted base_blueprints * type fixes * rerun bridge typer CLI * Rename globalconfig singleton to global_config Use the singleton as the default kwarg value directly, eliminating the `global_config or globalconfig` fallback pattern. * Replace @cached_property with @Property in GlobalConfig Removes manual cache invalidation in update() since properties are now recomputed on access. Prevents stale cache bugs when new derived properties are added. * Rename debug_navigation to navigation_costmap Keep DEBUG_NAVIGATION env var gate as-is. * Fix broken rerun_init import, rename debug_navigation to navigation_costmap - Move RERUN_GRPC_PORT/RERUN_WEB_PORT constants to bridge.py, fixing import of deleted dimos.dashboard.rerun_init - Rename debug_navigation to navigation_costmap throughout the navigation stack and visual overrides - Remove redundant __init__ in RerunBridgeModule * test_blueprints fix * removed unused rerun config keys * Remove broken rerun_init import from teleop visualization RerunBridge handles initialization; teleop modules just need to log.
1 parent 020105c commit a7c7273

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1173
-1483
lines changed

dimos/core/blueprints.py

Lines changed: 6 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@
2323
from types import MappingProxyType
2424
from typing import Any, Literal, get_args, get_origin, get_type_hints
2525

26-
import rerun as rr
27-
import rerun.blueprint as rrb
28-
29-
from dimos.core.global_config import GlobalConfig
26+
from dimos.core.global_config import GlobalConfig, global_config
3027
from dimos.core.module import Module, is_module_type
3128
from dimos.core.module_coordinator import ModuleCoordinator
3229
from dimos.core.stream import In, Out
@@ -262,8 +259,8 @@ def _deploy_all_modules(
262259
for blueprint in self.blueprints:
263260
kwargs = {**blueprint.kwargs}
264261
sig = inspect.signature(blueprint.module.__init__)
265-
if "global_config" in sig.parameters:
266-
kwargs["global_config"] = global_config
262+
if "cfg" in sig.parameters:
263+
kwargs["cfg"] = global_config
267264
module_specs.append((blueprint.module, blueprint.args, kwargs))
268265

269266
module_coordinator.deploy_parallel(module_specs)
@@ -456,78 +453,18 @@ def _connect_rpc_methods(self, module_coordinator: ModuleCoordinator) -> None:
456453
requested_method_name, rpc_methods_dot[requested_method_name]
457454
)
458455

459-
def _init_rerun_blueprint(self, module_coordinator: ModuleCoordinator) -> None:
460-
"""Compose and send Rerun blueprint from module contributions.
461-
462-
Collects rerun_views() from all modules and composes them into a unified layout.
463-
"""
464-
# Collect view contributions from all modules
465-
side_panels = []
466-
for blueprint in self.blueprints:
467-
if hasattr(blueprint.module, "rerun_views"):
468-
views = blueprint.module.rerun_views()
469-
if views:
470-
side_panels.extend(views)
471-
472-
# Always include latency panel if we have any panels
473-
if side_panels:
474-
side_panels.append(
475-
rrb.TimeSeriesView(
476-
name="Latency (ms)",
477-
origin="/metrics",
478-
contents=[
479-
"+ /metrics/voxel_map/latency_ms",
480-
"+ /metrics/costmap/latency_ms",
481-
],
482-
)
483-
)
484-
485-
# Compose final layout
486-
if side_panels:
487-
composed_blueprint = rrb.Blueprint(
488-
rrb.Horizontal(
489-
rrb.Spatial3DView(
490-
name="3D View",
491-
origin="world",
492-
background=[0, 0, 0],
493-
),
494-
rrb.Vertical(*side_panels, row_shares=[2] + [1] * (len(side_panels) - 1)),
495-
column_shares=[3, 1],
496-
),
497-
rrb.TimePanel(state="collapsed"),
498-
rrb.SelectionPanel(state="collapsed"),
499-
rrb.BlueprintPanel(state="collapsed"),
500-
)
501-
rr.send_blueprint(composed_blueprint)
502-
503-
def _start_rerun(self, global_config: GlobalConfig) -> None:
504-
# Initialize Rerun server before deploying modules (if backend is Rerun)
505-
if global_config.rerun_enabled and global_config.viewer_backend.startswith("rerun"):
506-
try:
507-
from dimos.dashboard.rerun_init import init_rerun_server
508-
509-
server_addr = init_rerun_server(viewer_mode=global_config.viewer_backend)
510-
global_config.model_copy(update={"rerun_server_addr": server_addr})
511-
logger.info("Rerun server initialized", addr=server_addr)
512-
except Exception as e:
513-
logger.warning(f"Failed to initialize Rerun server: {e}")
514-
515456
def build(
516457
self,
517-
global_config: GlobalConfig | None = None,
518458
cli_config_overrides: Mapping[str, Any] | None = None,
519459
) -> ModuleCoordinator:
520-
if global_config is None:
521-
global_config = GlobalConfig()
522-
global_config = global_config.model_copy(update=dict(self.global_config_overrides))
460+
global_config.update(**dict(self.global_config_overrides))
523461
if cli_config_overrides:
524-
global_config = global_config.model_copy(update=dict(cli_config_overrides))
462+
global_config.update(**dict(cli_config_overrides))
525463

526464
self._check_requirements()
527465
self._verify_no_name_conflicts()
528-
self._start_rerun(global_config)
529466

530-
module_coordinator = ModuleCoordinator(global_config=global_config)
467+
module_coordinator = ModuleCoordinator(cfg=global_config)
531468
module_coordinator.start()
532469

533470
# all module constructors are called here (each of them setup their own)
@@ -538,10 +475,6 @@ def build(
538475

539476
module_coordinator.start_all_modules()
540477

541-
# Compose and send Rerun blueprint from module contributions
542-
if global_config.viewer_backend.startswith("rerun"):
543-
self._init_rerun_blueprint(module_coordinator)
544-
545478
return module_coordinator
546479

547480

dimos/core/docker_runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
from dimos.core.docker_build import build_image, image_exists
2929
from dimos.core.module import Module, ModuleConfig
3030
from dimos.core.rpc_client import RpcCall
31-
from dimos.dashboard.rerun_init import RERUN_GRPC_PORT, RERUN_WEB_PORT
3231
from dimos.protocol.rpc import LCMRPC
3332
from dimos.utils.logging_config import setup_logger
33+
from dimos.visualization.rerun.bridge import RERUN_GRPC_PORT, RERUN_WEB_PORT
3434

3535
if TYPE_CHECKING:
3636
from collections.abc import Callable

dimos/core/global_config.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from functools import cached_property
1615
import re
1716
from typing import Literal, TypeAlias
1817

1918
from pydantic_settings import BaseSettings, SettingsConfigDict
2019

2120
from dimos.mapping.occupancy.path_map import NavigationStrategy
2221

23-
ViewerBackend: TypeAlias = Literal["rerun-web", "rerun-native", "foxglove"]
22+
ViewerBackend: TypeAlias = Literal["rerun", "rerun-web", "foxglove", "none"]
2423

2524

2625
def _get_all_numbers(s: str) -> list[float]:
@@ -31,9 +30,7 @@ class GlobalConfig(BaseSettings):
3130
robot_ip: str | None = None
3231
simulation: bool = False
3332
replay: bool = False
34-
rerun_enabled: bool = True
35-
rerun_server_addr: str | None = None
36-
viewer_backend: ViewerBackend = "rerun-native"
33+
viewer_backend: ViewerBackend = "rerun"
3734
n_dask_workers: int = 2
3835
memory_limit: str = "auto"
3936
mujoco_camera_position: str | None = None
@@ -54,24 +51,33 @@ class GlobalConfig(BaseSettings):
5451
env_file=".env",
5552
env_file_encoding="utf-8",
5653
extra="ignore",
57-
frozen=True,
5854
)
5955

60-
@cached_property
56+
def update(self, **kwargs: object) -> None:
57+
"""Update config fields in place."""
58+
for key, value in kwargs.items():
59+
if not hasattr(self, key):
60+
raise AttributeError(f"GlobalConfig has no field '{key}'")
61+
setattr(self, key, value)
62+
63+
@property
6164
def unitree_connection_type(self) -> str:
6265
if self.replay:
6366
return "replay"
6467
if self.simulation:
6568
return "mujoco"
6669
return "webrtc"
6770

68-
@cached_property
71+
@property
6972
def mujoco_start_pos_float(self) -> tuple[float, float]:
7073
x, y = _get_all_numbers(self.mujoco_start_pos)
7174
return (x, y)
7275

73-
@cached_property
76+
@property
7477
def mujoco_camera_position_float(self) -> tuple[float, ...]:
7578
if self.mujoco_camera_position is None:
7679
return (-0.906, 0.008, 1.101, 4.931, 89.749, -46.378)
7780
return tuple(_get_all_numbers(self.mujoco_camera_position))
81+
82+
83+
global_config = GlobalConfig()

dimos/core/module_coordinator.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from dimos import core
2020
from dimos.core import DimosCluster
21-
from dimos.core.global_config import GlobalConfig
21+
from dimos.core.global_config import GlobalConfig, global_config
2222
from dimos.core.module import Module, ModuleT
2323
from dimos.core.resource import Resource
2424
from dimos.core.worker_manager import WorkerManager
@@ -37,9 +37,8 @@ class ModuleCoordinator(Resource): # type: ignore[misc]
3737
def __init__(
3838
self,
3939
n: int | None = None,
40-
global_config: GlobalConfig | None = None,
40+
cfg: GlobalConfig = global_config,
4141
) -> None:
42-
cfg = global_config or GlobalConfig()
4342
self._n = n if n is not None else cfg.n_dask_workers
4443
self._memory_limit = cfg.memory_limit
4544
self._global_config = cfg

dimos/core/test_blueprints.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
autoconnect,
2929
)
3030
from dimos.core.core import rpc
31-
from dimos.core.global_config import GlobalConfig
3231
from dimos.core.module import Module
3332
from dimos.core.module_coordinator import ModuleCoordinator
3433
from dimos.core.rpc_client import RpcCall
@@ -40,7 +39,7 @@
4039

4140
# Disable Rerun for tests (prevents viewer spawn and gRPC flush errors)
4241
_BUILD_WITHOUT_RERUN = {
43-
"global_config": GlobalConfig(rerun_enabled=False, viewer_backend="foxglove"),
42+
"cli_config_overrides": {"viewer_backend": "none"},
4443
}
4544

4645

dimos/core/transport.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@
3737

3838
T = TypeVar("T") # type: ignore[misc]
3939

40+
# TODO
41+
# Transports need to be rewritten and simplified,
42+
#
43+
# there is no need for them to get a reference to "a stream" on publish/subscribe calls
44+
# this is a legacy from dask transports.
45+
#
46+
# new transport should literally have 2 functions (next to start/stop)
47+
# "send(msg)" and "receive(callback)" and that's all
48+
#
49+
# we can also consider pubsubs conforming directly to Transport specs
50+
# and removing PubSubTransport glue entirely
51+
#
52+
# Why not ONLY pubsubs without Transport abstraction?
53+
#
54+
# General idea for transports (and why they exist at all)
55+
# is that they can be * anything * like
56+
#
57+
# a web camera rtsp stream for Image, audio stream from mic, etc
58+
# http binary streams, tcp connections etc
59+
4060

4161
class PubSubTransport(Transport[T]):
4262
topic: Any

dimos/dashboard/__init__.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

dimos/dashboard/dimos.rbl

-153 KB
Binary file not shown.

0 commit comments

Comments
 (0)