Skip to content

Commit fa3defd

Browse files
committed
simplify
1 parent b45676a commit fa3defd

8 files changed

Lines changed: 44 additions & 123 deletions

File tree

dimos/e2e_tests/test_smartnav_replay.py

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
"""Integration test for the unitree_go2_smartnav blueprint using replay data.
1616
17-
Builds the smartnav pipeline (GO2Connection → OdomAdapter → PGO → CostMapper →
17+
Builds the smartnav pipeline (GO2Connection → PGO → CostMapper →
1818
ReplanningAStarPlanner) in replay mode and verifies that data flows end-to-end:
19-
- PGO receives scans and odom, publishes corrected_odometry + global_map
19+
- PGO receives scans and raw odom (PoseStamped), publishes corrected_odometry + global_map
2020
- CostMapper receives global_map, publishes global_costmap
2121
"""
2222

@@ -34,8 +34,7 @@
3434
from dimos.msgs.nav_msgs.Odometry import Odometry
3535
from dimos.msgs.nav_msgs.OccupancyGrid import OccupancyGrid
3636
from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2
37-
from dimos.navigation.smartnav.modules.odom_adapter.odom_adapter import odom_adapter
38-
from dimos.navigation.smartnav.modules.pgo.pgo import PGO
37+
from dimos.navigation.loop_closure.pgo import PGO
3938
from dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic import unitree_go2_basic
4039
from dimos.robot.unitree.go2.connection import GO2Connection
4140

@@ -55,13 +54,12 @@ def smartnav_coordinator():
5554
n_workers=1,
5655
)
5756

58-
# Minimal pipeline: GO2Connection → OdomAdapter → PGO → CostMapper
57+
# Minimal pipeline: GO2Connection → PGO → CostMapper
5958
# Skip ReplanningAStarPlanner and WavefrontFrontierExplorer to avoid
6059
# needing a goal and cmd_vel sink.
6160
bp = autoconnect(
6261
unitree_go2_basic,
6362
PGO.blueprint(),
64-
odom_adapter(),
6563
cost_mapper(),
6664
).global_config(
6765
n_workers=1,
@@ -187,35 +185,25 @@ def test_costmapper_produces_costmap(self, smartnav_coordinator):
187185
msg = collector.messages[0]
188186
assert isinstance(msg, OccupancyGrid), f"Expected OccupancyGrid, got {type(msg)}"
189187

190-
def test_odom_adapter_converts_bidirectionally(self, smartnav_coordinator):
191-
"""OdomAdapter should convert PoseStamped→Odometry and Odometry→PoseStamped."""
188+
def test_pgo_produces_corrected_pose_stamped(self, smartnav_coordinator):
189+
"""PGO should publish corrected pose as PoseStamped on the odom output."""
192190
coord = smartnav_coordinator
193191

194-
from dimos.navigation.smartnav.modules.odom_adapter.odom_adapter import OdomAdapter
195-
196-
adapter = None
192+
pgo_mod = None
197193
for mod in coord.all_modules:
198-
if isinstance(mod, OdomAdapter):
199-
adapter = mod
194+
if isinstance(mod, PGO):
195+
pgo_mod = mod
200196
break
201-
assert adapter is not None, "OdomAdapter not found in coordinator"
197+
assert pgo_mod is not None, "PGO module not found in coordinator"
202198

203-
# Collect outputs from both directions
204-
odom_out = _StreamCollector()
205-
ps_out = _StreamCollector()
206-
adapter.odometry._transport.subscribe(odom_out.callback)
207-
adapter.odom._transport.subscribe(ps_out.callback)
199+
collector = _StreamCollector()
200+
pgo_mod.odom._transport.subscribe(collector.callback)
208201

209202
coord.start()
210203

211-
# OdomAdapter.odometry (PoseStamped→Odometry) should fire from replay odom
212-
assert odom_out.wait(count=3, timeout=30), (
213-
f"OdomAdapter did not produce Odometry output (got {len(odom_out.messages)})"
214-
)
215-
assert isinstance(odom_out.messages[0], Odometry)
216-
217-
# OdomAdapter.odom (Odometry→PoseStamped) fires when PGO publishes corrected_odometry
218-
assert ps_out.wait(count=1, timeout=30), (
219-
f"OdomAdapter did not produce PoseStamped output (got {len(ps_out.messages)})"
204+
assert collector.wait(count=3, timeout=30), (
205+
f"PGO did not produce PoseStamped odom output (got {len(collector.messages)})"
220206
)
221-
assert isinstance(ps_out.messages[0], PoseStamped)
207+
msg = collector.messages[0]
208+
assert isinstance(msg, PoseStamped), f"Expected PoseStamped, got {type(msg)}"
209+
assert msg.frame_id == "map"

dimos/navigation/smartnav/modules/pgo/pgo.py renamed to dimos/navigation/loop_closure/pgo.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
from dimos.core.module import Module, ModuleConfig
3636
from dimos.core.stream import In, Out
37+
from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped
3738
from dimos.msgs.nav_msgs.Odometry import Odometry
3839
from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2
3940

@@ -362,16 +363,18 @@ class PGO(Module[PGOConfig]):
362363
363364
Ports:
364365
registered_scan (In[PointCloud2]): World-frame registered point cloud.
365-
odometry (In[Odometry]): Current pose estimate from SLAM.
366-
corrected_odometry (Out[Odometry]): Loop-closure-corrected pose.
366+
raw_odom (In[PoseStamped]): Raw pose from robot (PoseStamped).
367+
corrected_odometry (Out[Odometry]): Loop-closure-corrected pose (Odometry).
368+
odom (Out[PoseStamped]): Loop-closure-corrected pose (PoseStamped, for downstream).
367369
global_static_map (Out[PointCloud2]): Accumulated keyframe map.
368370
"""
369371

370372
default_config = PGOConfig
371373

372374
registered_scan: In[PointCloud2]
373-
odometry: In[Odometry]
375+
raw_odom: In[PoseStamped]
374376
corrected_odometry: Out[Odometry]
377+
odom: Out[PoseStamped]
375378
global_static_map: Out[PointCloud2]
376379

377380
def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def]
@@ -401,7 +404,7 @@ def __setstate__(self, state: dict) -> None:
401404

402405
def start(self) -> None:
403406
self._pgo = _SimplePGO(self.config)
404-
self.odometry._transport.subscribe(self._on_odom)
407+
self.raw_odom._transport.subscribe(self._on_raw_odom)
405408
self.registered_scan._transport.subscribe(self._on_scan)
406409
self._running = True
407410
self._thread = threading.Thread(target=self._publish_loop, daemon=True)
@@ -414,17 +417,17 @@ def stop(self) -> None:
414417
self._thread.join(timeout=3.0)
415418
super().stop()
416419

417-
def _on_odom(self, msg: Odometry) -> None:
420+
def _on_raw_odom(self, msg: PoseStamped) -> None:
418421
from scipy.spatial.transform import Rotation
419422

420423
q = [
421-
msg.pose.orientation.x,
422-
msg.pose.orientation.y,
423-
msg.pose.orientation.z,
424-
msg.pose.orientation.w,
424+
msg.orientation.x,
425+
msg.orientation.y,
426+
msg.orientation.z,
427+
msg.orientation.w,
425428
]
426429
r = Rotation.from_quat(q).as_matrix()
427-
t = np.array([msg.pose.position.x, msg.pose.position.y, msg.pose.position.z])
430+
t = np.array([msg.x, msg.y, msg.z])
428431
with self._lock:
429432
self._latest_r = r
430433
self._latest_t = t
@@ -484,6 +487,14 @@ def _publish_corrected_odom(self, r: np.ndarray, t: np.ndarray, ts: float) -> No
484487
)
485488
self.corrected_odometry._transport.publish(odom)
486489

490+
ps = PoseStamped(
491+
ts=ts,
492+
frame_id="map",
493+
position=[float(t[0]), float(t[1]), float(t[2])],
494+
orientation=[float(q[0]), float(q[1]), float(q[2]), float(q[3])],
495+
)
496+
self.odom._transport.publish(ps)
497+
487498
def _publish_loop(self) -> None:
488499
"""Periodically publish global map."""
489500
pgo = self._pgo

dimos/navigation/smartnav/modules/__init__.py

Whitespace-only changes.

dimos/navigation/smartnav/modules/odom_adapter/__init__.py

Whitespace-only changes.

dimos/navigation/smartnav/modules/odom_adapter/odom_adapter.py

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

dimos/navigation/smartnav/modules/pgo/__init__.py

Whitespace-only changes.

dimos/robot/unitree/go2/blueprints/smart/unitree_go2_smartnav.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616
"""Go2 SmartNav blueprint: PGO + CostMapper + ReplanningAStarPlanner.
1717
1818
Uses PGO for loop-closure-corrected odometry and global map from the Go2's
19-
world-frame lidar + drifted odom. OdomAdapter bridges PoseStamped <-> Odometry
20-
between GO2Connection and PGO.
19+
world-frame lidar + drifted odom. PGO accepts PoseStamped input directly and
20+
outputs both corrected Odometry and PoseStamped.
2121
2222
Data flow:
2323
GO2Connection.lidar (remapped → registered_scan) → PGO
24-
GO2Connection.odom (remapped → raw_odom) → OdomAdapter → PGO.odometry
25-
PGO.corrected_odometry → OdomAdapter → odom → ReplanningAStarPlanner
24+
GO2Connection.odom (remapped → raw_odom) → PGO
25+
PGO.corrected_odometry (Odometry)
26+
PGO.odom (PoseStamped) → ReplanningAStarPlanner
2627
PGO.global_map → CostMapper → ReplanningAStarPlanner
2728
ReplanningAStarPlanner.cmd_vel → GO2Connection
2829
"""
@@ -33,15 +34,13 @@
3334
wavefront_frontier_explorer,
3435
)
3536
from dimos.navigation.replanning_a_star.module import replanning_a_star_planner
36-
from dimos.navigation.smartnav.modules.odom_adapter.odom_adapter import odom_adapter
37-
from dimos.navigation.smartnav.modules.pgo.pgo import PGO
37+
from dimos.navigation.loop_closure.pgo import PGO
3838
from dimos.robot.unitree.go2.blueprints.basic.unitree_go2_basic import unitree_go2_basic
3939
from dimos.robot.unitree.go2.connection import GO2Connection
4040

4141
unitree_go2_smartnav = autoconnect(
4242
unitree_go2_basic,
4343
PGO.blueprint(),
44-
odom_adapter(),
4544
cost_mapper(),
4645
replanning_a_star_planner(),
4746
wavefront_frontier_explorer(),

0 commit comments

Comments
 (0)