-
Notifications
You must be signed in to change notification settings - Fork 611
feat: livox replay #2089
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: livox replay #2089
Changes from all commits
215f337
8d8b028
36ebc5f
bb3427f
bec4833
54dc893
2669672
3b3c670
68cce39
6e62fd4
b385513
961c9e4
6d60368
12110e2
0574778
c118caf
fd7311e
9e02acd
0cd82a7
2d492d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,20 +12,108 @@ | |
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from pathlib import Path | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from reactivex.disposable import Disposable | ||
|
|
||
| from dimos.core.coordination.blueprints import autoconnect | ||
| from dimos.core.core import rpc | ||
| from dimos.core.stream import In, Out | ||
| from dimos.hardware.sensors.lidar.fastlio2.module import FastLio2 | ||
| from dimos.mapping.voxels import VoxelGridMapper | ||
| from dimos.memory2.module import MemoryModule, MemoryModuleConfig, Recorder, RecorderConfig | ||
| from dimos.msgs.geometry_msgs.Transform import Transform | ||
| from dimos.msgs.nav_msgs.Odometry import Odometry | ||
| from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 | ||
| from dimos.utils.testing.replay import timed_playback | ||
| from dimos.visualization.vis_module import vis_module | ||
|
|
||
| if TYPE_CHECKING: | ||
| from rerun._baseclasses import Archetype | ||
|
|
||
|
|
||
| class FastlioMemoryConfig(RecorderConfig): | ||
| db_path: str | Path = "recording_fastlio.db" | ||
| default_frame_id: str = "base_link" | ||
|
|
||
|
|
||
| voxel_size = 0.05 | ||
|
|
||
|
|
||
| class FastlioMemory(Recorder): | ||
| config: FastlioMemoryConfig | ||
| lidar: In[PointCloud2] | ||
| odometry: In[Odometry] | ||
|
|
||
| @rpc | ||
| def start(self) -> None: | ||
| super().start() | ||
|
|
||
| def _on_odom(msg: Odometry) -> None: | ||
| self.tf.publish(Transform.from_odometry(msg)) | ||
|
|
||
| self.register_disposable(Disposable(self.odometry.subscribe(_on_odom))) | ||
|
|
||
|
|
||
| class FastlioReplayConfig(MemoryModuleConfig): | ||
| db_path: str | Path = "recording_fastlio.db" | ||
| speed: float = 1.0 | ||
|
|
||
|
|
||
| class FastlioReplay(MemoryModule): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file, fastlio_blueprints, implies it's just for blueprints. Move these new modules to |
||
| """Replays a FastLIO2 recording (lidar + odometry) at real-time speed. | ||
|
|
||
| Drop-in replacement for ``FastLio2`` when feeding rerun off a recorded session. | ||
| Publishes odometry to tf so downstream visualizers see robot pose. | ||
| """ | ||
|
|
||
| config: FastlioReplayConfig | ||
| lidar: Out[PointCloud2] | ||
| odometry: Out[Odometry] | ||
|
|
||
| @rpc | ||
| def start(self) -> None: | ||
| super().start() | ||
|
|
||
| lidar_stream = self.store.stream("lidar", PointCloud2) | ||
| odom_stream = self.store.stream("odometry", Odometry) | ||
|
|
||
| def _publish_odom(msg: Odometry) -> None: | ||
| self.tf.publish(Transform.from_odometry(msg)) | ||
| self.odometry.publish(msg) | ||
|
|
||
| speed = self.config.speed | ||
|
|
||
| self.register_disposable( | ||
| timed_playback( | ||
| lambda: ((obs.ts, obs.data) for obs in lidar_stream), | ||
| speed=speed, | ||
| ).subscribe(self.lidar.publish) | ||
| ) | ||
| self.register_disposable( | ||
| timed_playback( | ||
| lambda: ((obs.ts, obs.data) for obs in odom_stream), | ||
| speed=speed, | ||
| ).subscribe(_publish_odom) | ||
| ) | ||
|
|
||
|
|
||
| def _convert_global_map(msg: PointCloud2) -> "Archetype": | ||
| return msg.to_rerun(voxel_size=voxel_size) | ||
|
|
||
|
|
||
| mid360_fastlio = autoconnect( | ||
| FastLio2.blueprint(voxel_size=voxel_size, map_voxel_size=voxel_size, map_freq=-1), | ||
| vis_module("rerun"), | ||
| ).global_config(n_workers=2, robot_model="mid360_fastlio2") | ||
|
|
||
| mid360_fastlio_memory = autoconnect( | ||
| FastLio2.blueprint(voxel_size=voxel_size, map_voxel_size=voxel_size, map_freq=-1), | ||
| vis_module("rerun"), | ||
| FastlioMemory.blueprint(), | ||
| ).global_config(n_workers=3, robot_model="mid360_fastlio2_memory") | ||
|
|
||
| mid360_fastlio_voxels = autoconnect( | ||
| FastLio2.blueprint(), | ||
| VoxelGridMapper.blueprint(voxel_size=voxel_size, carve_columns=False), | ||
|
|
@@ -39,6 +127,31 @@ | |
| ), | ||
| ).global_config(n_workers=3, robot_model="mid360_fastlio2_voxels") | ||
|
|
||
| mid360_fastlio_replay = autoconnect( | ||
| FastlioReplay.blueprint(), | ||
| vis_module( | ||
| "rerun", | ||
| rerun_config={ | ||
| "visual_override": { | ||
| "world/global_map": _convert_global_map, | ||
| }, | ||
| }, | ||
| ), | ||
| ).global_config(n_workers=2, robot_model="mid360_fastlio2_replay") | ||
|
|
||
| mid360_fastlio_replay_voxels = autoconnect( | ||
| FastlioReplay.blueprint(), | ||
| VoxelGridMapper.blueprint(voxel_size=voxel_size, carve_columns=True), | ||
| vis_module( | ||
| "rerun", | ||
| rerun_config={ | ||
| "visual_override": { | ||
| "world/global_map": _convert_global_map, | ||
| }, | ||
| }, | ||
| ), | ||
| ).global_config(n_workers=2, robot_model="mid360_fastlio2_replay") | ||
|
aclauer marked this conversation as resolved.
|
||
|
|
||
| mid360_fastlio_voxels_native = autoconnect( | ||
| FastLio2.blueprint(voxel_size=voxel_size, map_voxel_size=voxel_size, map_freq=3.0), | ||
| vis_module( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| import rerun as rr | ||
|
|
||
| from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped | ||
| from dimos.msgs.nav_msgs.Odometry import Odometry | ||
|
|
||
| from dimos_lcm.geometry_msgs import ( | ||
| Transform as LCMTransform, | ||
|
|
@@ -161,6 +162,17 @@ def __neg__(self) -> Transform: | |
| """Unary minus operator returns the inverse transform.""" | ||
| return self.inverse() | ||
|
|
||
| @classmethod | ||
| def from_odometry(cls, odom: Odometry) -> Transform: # type: ignore[name-defined] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the |
||
| """Create a Transform from an Odometry message using its own frame names.""" | ||
| return cls( | ||
| translation=odom.pose.position, | ||
| rotation=odom.pose.orientation, | ||
| frame_id=odom.frame_id, | ||
| child_frame_id=odom.child_frame_id, | ||
| ts=odom.ts, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def from_pose(cls, frame_id: str, pose: Pose | PoseStamped) -> Transform: # type: ignore[name-defined] | ||
| """Create a Transform from a Pose or PoseStamped. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,6 +155,14 @@ def get_transform( | |
| return None | ||
|
|
||
| def get(self, *args, **kwargs) -> Transform | None: # type: ignore[no-untyped-def] | ||
| parent_frame = args[0] if args else kwargs.get("parent_frame") | ||
| child_frame = args[1] if len(args) > 1 else kwargs.get("child_frame") | ||
|
Comment on lines
+158
to
+159
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an odd way of parsing arguments. Just replace |
||
| if parent_frame is not None and parent_frame == child_frame: | ||
| raise ValueError( | ||
| f"tf.get() called with same parent and child frame {parent_frame!r}; " | ||
| "this is almost always a caller bug — the data is already in that frame" | ||
| ) | ||
|
aclauer marked this conversation as resolved.
|
||
|
|
||
| simple = self.get_transform(*args, **kwargs) | ||
| if simple is not None: | ||
| return simple | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_native_ignore_fieldsexposes infrastructure fields as binary CLI argsFastLio2Configre-declarescwd,executable,build_command, andcli_excludein its class body (to set different defaults). Because those names appear in bothNativeModuleConfig.model_fieldsandFastLio2Config.__annotations__, the new logic removes them fromignore_fields.to_cli_args()then appends--executable result/bin/fastlio2_native --cwd cpp --build_command "nix build .#fastlio2_native" --cli_exclude "frozenset({'config', 'mount'})"to the subprocess command, which the binary will likely reject as unrecognised arguments. The old code was safe becauseignore_fieldsretained everyNativeModuleConfigfield (with only an explicitframe_idcarve-out). A targeted fix would add"cwd","executable","build_command","cli_exclude", and"cli_name_override"toFastLio2Config.cli_exclude, or restrict_native_ignore_fieldsto only strip fields that are not control/infrastructure fields of the base config.