GO2 MCAP/DDS integration as memory2 backend#2314
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
- add dimos/memory2/cli (app + render): `dimos mem rerun <store>` renders any memory2 store (mcap or .db) to rerun; no __init__.py (namespace package) - cdr: type the spec param as Any so the generic decoder is mypy + ruff clean
The go2dds store/reader decode Go2 DDS mcap recordings via the `mcap` package; add it so CI (and installs) that pull the unitree extra have it.
- move mcap from the unitree extra to unitree-dds (its real go2 DDS home) - add dimos[unitree-dds] to the tests-self-hosted group so the go2dds store tests run on the self-hosted (ROS/cyclonedds) runner; drop the now-redundant unitree extra (unitree-dds implies it) - memory2/store/mcap.py: import mcap lazily so the store module imports without the optional dep (keeps non-self-hosted test collection mcap-free) - test_store.py: skip if mcap isn't installed
The pre-commit "insert license" hook prepended the full Apache header to files that already carried the short 3-line form, leaving a stray second block. Remove the duplicate from the 16 affected go2dds files.
The full `unitree-dds` extra pulls `cyclonedds`, whose wheel needs a CycloneDDS C lib the ros-dev image doesn't expose at build time (and `uv sync` runs before ROS is sourced). The go2dds store tests only decode mcap (pure-Python), so depend on `mcap` directly and keep `unitree` for the rest. cyclonedds stays in the `unitree-dds` extra for live-DDS use.
- render_store: skip observations whose payload decoded to None (e.g. a truncated/corrupt JPEG from decode_compressed_image) instead of crashing on obs.data.to_rerun() (Greptile P1). - go2dds render: log_lidar annotated PointCloud2, not PoseStamped (Greptile P2). - CdrStructCodec.decode: assert the decode consumed all bytes — leftover bytes mean a wrong fixed-layout spec; fail loud rather than decode garbage. Verified end == len against the real recording (lowstate/sportmodestate).
Greptile SummaryThis PR introduces a full MCAP/DDS integration as a
Confidence Score: 5/5The core MCAP store, CDR decoder, codec registry, and stream pipeline all work correctly for well-formed Go2 recordings; robustness gaps only surface on malformed or unclosed MCAP files. All findings are confined to edge cases that do not occur in normal Go2 recordings. The happy path is sound. dimos/robot/unitree/go2dds/ros.py (decode_pointcloud2 alignment and field guards) and dimos/memory2/store/mcap.py (silent empty store on missing statistics). Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as render.py
participant Store as Go2McapStore
participant McapStore as McapStore
participant Backend as Backend
participant Obs as McapObservationStore
participant MCAP as MCAP file
CLI->>Store: Go2McapStore(path)
Store->>McapStore: super().__init__(codecs, streams)
McapStore->>MCAP: open + get_summary()
MCAP-->>McapStore: channels, statistics
McapStore-->>Store: _available, _stream_topic
CLI->>Store: store.streams.sportmodestate.to_time(seconds)
Store->>Backend: stream() returns Backend
Backend->>Obs: "McapObservationStore(count=N)"
CLI->>Obs: count() returns N index fast-path
CLI->>Obs: query(q) returns iter
Obs->>MCAP: open + iter_messages(topic)
MCAP-->>Obs: schema, channel, message
Obs-->>CLI: Observation with lazy _loader
CLI->>CLI: obs.data triggers CDR decode
CLI->>CLI: tap map_data throttle accumulate_path drain
CLI->>CLI: rr.log for each observation
Reviews (7): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile |
- camera pipeline behind --image (off by default), throttled to --image-hz (default 10). Throttle runs before obs.data so thinned frames skip the jpeg decode; failed decodes (None) are skipped. - world_lidar pipeline: per-cloud pose interpolation (_interp_pose) over the leg-odom trajectory, transformed lidar -> base -> world into a global voxel map. Hoists rerun/Transform imports to module scope.
Add from_time/to_time (relative to the first observation) and from_timestamp/to_timestamp (absolute epoch seconds) for windowing a stream by time. A trailing to_time is a duration measured from the current start, so from_time(2).to_time(30) reads as "skip 2s, take the following 30s"; frames mix freely (from_timestamp(ts).to_time(30)). Shared base for the stream-alignment (#2306) and go2dds (#2314) branches, which both need this windowing API.
Add from_time/to_time (relative to the first observation) and from_timestamp/to_timestamp (absolute epoch seconds) for windowing a stream by time. A trailing to_time is a duration measured from the current start, so from_time(2).to_time(30) reads as "skip 2s, take the following 30s"; frames mix freely (from_timestamp(ts).to_time(30)). Shared base for the stream-alignment (#2306) and go2dds (#2314) branches, which both need this windowing API.
# Conflicts: # dimos/memory2/stream.py # dimos/memory2/test_stream.py
Return rr.TransformAxes3D(axis_length) instead of a rotation-only Transform3D, which draws nothing on its own.
implements MCAP as memory2 Store, allows for standard memory2 queries
cd dimos/robot/unitree/go2dds python cli/render.py go2_china_office_indoor.mcap --seconds 100 --out test.rrdin render.py - world lidar to odom alignment is very bad (single function though) builds full odom list and TS aligns lidar to it - will be changed after mem2 stream alignment is merged #2306