Feat/ivan/go2stairs#2283
Conversation
Replaces the monolithic pgo_then_voxels with four primitives over mem2 Streams and Transforms: pgo_keyframes(lidar) -> Stream[Keyframe] keyframes_to_corrections(kfs) -> Stream[Transform] (world_corrected <- world_raw) make_interpolator(corrections) -> (ts) -> Transform (SLERP + linear, endpoint-clipped) apply_corrections(stream, corr) -> Stream[T] (shuffles obs.pose) Drift correction is now a first-class, reusable Stream[Transform] that any pose-stamped consumer can apply — same math as before (rigid T_corr = T_global @ T_local^-1 per keyframe, SLERP + linear interpolation between bracketing keyframes), just composable. dimos map --pgo migrates to the new primitives; pgo_then_voxels is deleted. The internal _SimplePGO / PGOConfig / _KeyPose machinery stays in pgo.py and is imported by pgo2.
Renames: pgo.py -> pgo_internals.py (gtsam/ICP machinery) pgo2.py -> pgo.py (public Stream-shaped API) `dimos.mapping.relocalization.pgo` is now the canonical import path (`pgo_keyframes`, `keyframes_to_corrections`, `make_interpolator`, `apply_corrections`, `correction_at`, `Keyframe`). The internal _SimplePGO / PGOConfig / _KeyPose / _icp / _voxel_downsample helpers live in pgo_internals.py and are imported lazily inside pgo.py to keep gtsam off the public-API import path.
Adds test_pgo.py covering pose normalization on Observation, the Transform↔Pose3 conversion helpers, the interpolator edge cases, apply_corrections behavior, and a real-recording smoke test (skipped when data/go2_short.db is absent). Annotates the two map.py helpers so they pass mypy. Removes leftover section divider comments to satisfy the no-section-markers project rule.
The colors-copy block in `PointCloud2.transform` calls `self.pointcloud` to check `has_colors()`, which forces a tensor->legacy conversion on every invocation. With `pgo --full-pgo` rebuilding from hundreds of lidar frames, that hidden allocation dominates the per-frame cost. The colors path was lidar-irrelevant (lidar clouds have no colors anyway) and the feature it was meant to support never landed; remove it so transform() stays a clean numpy round-trip.
…s into feat/ivan/pgo_rewrite
# Conflicts: # dimos/mapping/relocalization/pgo.py # dimos/robot/cli/dimos.py # dimos/utils/cli/map.py
Replaces the three hand-rolled VoxelGrid loops (pgo pass 2, full_pgo, raw) with a small _accumulate() helper that drives VoxelMapTransformer in batch mode. Same behavior, less repetition, and disposal is managed by the transformer.
The outer `pose = obs.pose` (Pose | None) was being shadowed by `pose = estimate_marker_pose(...)` (tuple[rvec, tvec] | None), which mypy flags as an incompatible reassignment. Renamed the inner one to `marker_pose`.
NearFilter now holds a typed `Vector3` instead of an `Any` named `pose`. `Stream.near()` does the one-time normalization at construction (Pose/ PoseStamped → .position, then Vector3(...) which accepts tuple/ndarray/ Vector3). `matches()` uses Vector3 subtraction + length_squared(); the sqlite filter compiler reads f.position.x/.y/.z directly. The _xyz() helper is gone.
Greptile SummaryAdds
Confidence Score: 4/5Safe to merge after fixing the broken The test in dimos/msgs/sensor_msgs/test_PointCloud2.py — the Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["map_rrd.py main()"] --> B["resolve_named_path()"]
B --> C["SqliteStore.open()"]
C --> D["store.summary()"]
D --> E{streams present?}
E --> F["lidar → _log_clouds(world/lidar)"]
E --> G["fastlio_lidar → _log_clouds(world/fastlio_lidar)"]
F --> H{--map flag?}
G --> H
H -->|yes| I["VoxelMapTransformer → world/lidar_voxels"]
H -->|yes| J["VoxelMapTransformer → world/fastlio_voxels"]
E --> K{fastlio_odometry?}
K -->|yes| L["pose loop → rr.Transform3D (world/fastlio)"]
L --> M["_log_path (world/fastlio_path)"]
E --> N{odom?}
N -->|yes| O["pose loop → rr.Transform3D (world/odom)"]
O --> P["_log_path (world/odom_path)"]
E --> Q["color_image (throttled)"]
Q --> R{image_pose_from?}
R -->|own| S["img_obs.pose_stamped"]
R -->|fastlio_odom| T["pose_authority.at(img_obs.ts)"]
S --> U["rr.Transform3D (world/camera)"]
T --> U
U --> V["rr.log(world/camera/image)"]
V --> W["rr.save → .rrd file"]
Reviews (3): Last reviewed commit: "Merge origin/main into feat/ivan/go2stai..." | Re-trigger Greptile |
❌ 2 Tests Failed:
View the top 2 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
Integrate origin/main (PR #2242 loop_closure rewrite, #2278 aruco Detection3D, #2316 docs/coding-agents rename, mem2 time windowing, etc.). Took origin's marker-free PGO/PoseGraph rewrite for loop_closure (eval, pgo, test_pgo, markers_rrd), marker_transformer, and the map CLI; kept branch map_rrd. Stripped remaining # ---- section markers from map_rrd.
| # 1. Check number of points | ||
| original_points, _ = lidar_msg.as_numpy() | ||
| decoded_points, _ = decoded.as_numpy() | ||
| original_points = lidar_msg.as_numpy() | ||
| decoded_points = decoded.as_numpy() |
There was a problem hiding this comment.
as_numpy() still returns tuple[np.ndarray, np.ndarray | None], so assigning the result directly to original_points and decoded_points silently breaks the test. len(original_points) now returns 2 (the tuple length) rather than the number of points, making the count assertion trivially pass for any pair of clouds. The subsequent np.testing.assert_allclose then receives a 2-element object array [points_array, None] instead of just the point positions — this will either raise a TypeError from numpy's ufunc or produce a meaningless comparison. Note that test_lcm_intensity_round_trip and test_lcm_no_intensity_round_trip in the same file both still use the correct tuple-unpacking pattern.
| # 1. Check number of points | |
| original_points, _ = lidar_msg.as_numpy() | |
| decoded_points, _ = decoded.as_numpy() | |
| original_points = lidar_msg.as_numpy() | |
| decoded_points = decoded.as_numpy() | |
| # 1. Check number of points | |
| original_points, _ = lidar_msg.as_numpy() | |
| decoded_points, _ = decoded.as_numpy() |
|
Superseded — #2306 includes this recording now. |
forked from crappy branch just to give you this map_rrd thing, no need for actual work, feel free to copy just the db file