Skip to content

dim_slam: one crate, cuVSLAM feeding fusion in-process - #2

Draft
jeff-hykin wants to merge 14 commits into
mainfrom
jeff/feat/dim_slam
Draft

dim_slam: one crate, cuVSLAM feeding fusion in-process#2
jeff-hykin wants to merge 14 commits into
mainfrom
jeff/feat/dim_slam

Conversation

@jeff-hykin

Copy link
Copy Markdown
Member
  • rust/ is now a single dim_slam crate with one binary
  • cuvslam_odometry (VoCore) and odometry_fusion (FusionCore) become transport-free libraries; main.rs wires the tracker's pose straight into the ESKF as a drifting source
  • internal odometry hop no longer crosses the wire; external sources still arrive on sources, split by header.frame_id
  • no-IMU blend in eskf.rs: filter seeds level off the first source and coasts between measurements
  • tf-output config removed from the vo side (map_frame, publish_map_to_odom, publish_tf) — fusion owns tf
  • standalone module binaries removed; older dimos branches pin older revs and keep building
  • 31/31 tests, clippy clean, nix build .#metal / .#orin produce result/bin/dim_slam

cuVSLAM reports covariance_xyz_rpy on the rig frame, but publish() moved
only the pose onto base_frame; with a nonzero base_from_rig the 6x6 was
in the wrong frame for any downstream consumer weighting by it. Move it
through the SE(3) adjoint of base_from_rig, fixed at tracker creation.
Loop closure and relocalization move downstream to the fusion filter;
the module is now pure visual odometry. Drops the corrected_odometry
output, the map->odom correction, and the slam_* config; map->odom
identity stays behind publish_map_to_odom.
15-state error-state Kalman filter (position, velocity, orientation, gyro and
accel bias) propagated by the IMU and corrected by nav_msgs/Odometry sources
told apart by header.frame_id: the filter's own odom_frame fuses absolutely,
any other frame fuses consecutive-pose deltas anchored to the filter. Per
source, per dimension variance policy (negative reads the message covariance,
zero drops the dimension, positive is fixed), body-frame zero-twist constraint
rows for non-holonomic bases, a Mahalanobis gate, and a rollback-replay buffer
for late measurements. Filter core is header-only with a doctest suite.
Same design as the C++ module: 15-state ESKF, sources told apart by
header.frame_id, per-dimension variance policy, zero-twist constraints,
Mahalanobis gate, rollback-replay buffer. The tf output goes through the SDK's
Tf client instead of a hand-rolled one, messages come from the generated
lcm-msgs crate, and the filter math sits on nalgebra with the test suite
ported alongside. Port names shift slightly because the SDK derives them from
field names: the multiplexed odometry input is "sources", the output stays
"odometry". Builds with cargo against dimos and dimos-lcm git dependencies.
Everything except libcuvslam itself is Rust on the dimos module SDK:
tf rig discovery, depth reprojection, IMU buffering, covariance/speed
rebase gates. shim/cuvslam_shim.cpp wraps the three C++ calls used
(construct, Track, RegisterImuMeasurement) and catches exceptions into
error strings, so a Track failure skips the frame instead of aborting.
Crate, binary, CMake target, C++ source and type names all follow.
The flake now builds rust/cuvslam_odometry and rust/odometry_fusion
(cargo, vendored git deps) instead of the cmake tree, so src/,
CMakeLists.txt, and the dimos-lcm/pfr/dimos-src/lcm-extended inputs go
away. Each variant package carries both binaries via symlinkJoin.
Also untracks the accidentally committed nix result symlink.
Port of 53cc07d (jeff/feat/imu_info): the five imu_* config scalars are
replaced by a sensor_msgs.ImuInfo input, published by the driver the way
camera_info is. The IMU frame comes from the message header instead of
the first Imu sample. ImuInfo is vendored until dimos-lcm ships it, with
a test pinning the packed fingerprint to the lcm hash chain.
The RealSense driver's own cloud carries every far, noisy depth pixel, and
depth error grows as range squared, so every consumer ends up rebuilding the
same gate. The tracker already holds the depth intrinsics and the depth scale,
so it can emit the clean sensor-frame cloud once.
Downstream of the odometry_fusion filter both modules publish
odom_frame->base_frame, so they race on the same tf edge; giving cuVSLAM its
own root instead leaves base_link with two parents, which is a malformed tree
rather than a redundant one. Off, this publishes odometry only and the filter
owns the edge alone.
…nd IMU frame

A drifting source's anchor was re-set to the corrected filter pose after every
accepted update, so the fraction of each delta the gain did not apply was folded
into the next anchor and lost. Advancing the anchor along the source's own chain
takes cuVSLAM through the filter from 15.99 m to 3.05 m ATE on a 150 s Alfred
slice at the module defaults.

The IMU was consumed in whatever frame it arrived in. On Alfred that is
camera_accel_optical_frame on a rotated mount, so yaw landed on the wrong axis
and every IMU combination scored worse than its IMU-free counterpart. It is now
rotated into base_frame through the tf tree, falling back with a warning when no
transform is available.

use_imu makes the IMU optional: with it off the filter seeds level from the first
source message and coasts at constant world velocity between measurements,
turning at the last reported angular rate. Wheel odometry through the filter with
no IMU scores 10.86 m against the raw stream's 10.84 m.
…rustworthy

Rotating the IMU into base_frame through the tf tree is the right idea but it made
Alfred's wheel+IMU run far worse, 18.61 m to 57988 m ATE, so it cannot stay in.

The recording's chain gives base_link -> camera_accel_optical_frame a roll of
-66.97 deg, and after applying it the gyro correlates 0.84 with the point-lio yaw
rate on x and 0.85 on z, so yaw is split across two axes instead of landing on
one. Solving for the yaw axis directly puts it 54.8 deg away from the gravity
direction the stationary accelerometer reports, which cannot be true of a rigidly
mounted IMU. The mount published in the tf tree and the mount the IMU samples
describe disagree, and until that is settled the raw path is the honest default.
… in-process

One binary, one Module. VoCore (cuvslam_odometry) and FusionCore
(odometry_fusion) become transport-free libraries; the DimSlam module in
main.rs wires the tracker's pose stream straight into the filter as a
drifting source, so the internal odometry hop never touches the wire.
Standalone module binaries are gone; older dimos branches pin older revs
of this repo and keep building.

Also folds in the no-IMU blend work in eskf.rs and drops the tf-output
config (map_frame, publish_map_to_odom, publish_tf) from the vo side --
fusion owns all tf.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant