Skip to content

reloc: fiducial relocalization as a sibling of lidar - #3900

Closed
AaryanAgrawal wants to merge 5 commits into
dimensionalOS:feat/ivan/relocalize2from
AaryanAgrawal:feat/fiducial-relocalization-v2
Closed

AaryanAgrawal wants to merge 5 commits into
dimensionalOS:feat/ivan/relocalize2from
AaryanAgrawal:feat/fiducial-relocalization-v2

Conversation

@AaryanAgrawal

@AaryanAgrawal AaryanAgrawal commented Sep 2, 2026 •

Copy link
Copy Markdown

Contribution path

Stacked on #3890 (feat/ivan/relocalize2); base is that branch, not main.

Problem

Lidar relocalization needs distinctive geometry, so a featureless corridor defeats it. A surveyed AprilTag in view gives a fix from one sighting with no geometry at all. The earlier fiducial work (#3162) predates the per-source relocalizer contract in #3890 and fed a shared judge that contract removes.

Solution

FiducialRelocalization is a sibling of LidarRelocalization: it subclasses the same base, declares its own input, owns its own accept decision, and calls submit().

RayTracingVoxelMap          accumulate ─► global_map            ─► LidarRelocalization     align
MarkerDetectionStreamModule accumulate ─► aggregated_detections ─► FiducialRelocalization  compose + gate
                                                                          │
                                                                    base: tf world → map

Three decisions worth a look:

  • Aggregation stays in the detector. The base's split is accumulate-then-align. Fusing many sightings of one tag into one scored pose is accumulating, the mapper's half, so MarkerDetectionStreamModule gains an aggregated_detections output the way RayTracingVoxelMap publishes global_map. The relocalizer only composes world_T_marker @ inv(map_T_marker) and gates on the score.
  • Aggregation is one class, TagAggregator, in one file. It is the detector's tap: gate each glimpse, keep a per-tag window, fuse with Huber IRLS (weighted-mean translation, Markley quaternion mean), publish once per visit. Same logic as feat(relocalization): fiducial relocalization prior + pre-map marker aggregation #3162, rewritten rather than ported. Every knob in AggregationConfig carries its unit and the reason for its value on the same line; the gate values are jnav's (gsc_pgo: online and offline PGO  #2587) and are not re-tuned here.
  • No pointcloud premap. The tag's surveyed pose is the map, so map_file is optional and only marker_map_file is needed. The loader lives in the relocalizer, the only thing that reads it. min_score defaults to 0 because the bar is unmeasured; the per-glimpse gates still apply upstream.

Left out on purpose, each a separate change: the ambiguity-ratio gate, the marker-map size override, and the survey CLI. A marker map is a small JSON, so a hand-written one runs this today.

How to Test

dimos run unitree-go2-fiducial-relocalization -o fiducialrelocalization.marker_map_file=<survey>

Unit: pytest dimos/mapping/relocalization/fiducial dimos/perception/fiducial.

AI assistance

Claude Code with Fable 5.1 wrote the modules and tests from a design the operator set; the operator reviewed the diff and directed every design decision above.

Checklist

  • I have read and approved the CLA.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DgAoKBZuBdaxcXFmU4jiQi

leshy and others added 2 commits September 2, 2026 23:26
PGOConfig's odometry priors are tuned for a Go2-class ground robot and trust
relative z 100x tighter than xy - the comment in the config says so. The map
CLI constructed PGO() with no arguments, so there was no way past that short
of editing source, and on a stairs walk it is backwards: z is the axis a
lidar-inertial estimate drifts on worst, so loop closures that need to
correct height were being resisted.

--non-planar trusts z like xy. --pgo-set name=value overrides any PGOConfig
field for everything else (loop_search_radius is the next suspect on
switchback stairs), parsed against the field's own type so a typo is a CLI
error rather than a config that quietly means something else.

Measured on athens: the two maps differ by 4.3 cm median, 17 cm p90, 2.5 m
max, and the climb loses ~1 m of spurious height at the top. Ivan confirmed
the loosened one is the better map by eye.

data/mid360_athens_stairs.pc2.lcm is that map:

    dimos map global mid360_athens_stairs --lidar pointlio_lidar \
      --voxel 0.005 --export --denoise --non-planar
@github-actions github-actions Bot added the first-time-contributor PR opened by an author who had not previously committed to this repository label Sep 2, 2026
@codecov

codecov Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.76855% with 21 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/mapping/relocalization/fiducial/module.py 81.94% 10 Missing and 3 partials ⚠️
dimos/perception/fiducial/marker_aggregation.py 95.27% 3 Missing and 4 partials ⚠️
...ception/fiducial/marker_detection_stream_module.py 83.33% 1 Missing ⚠️
@@                    Coverage Diff                    @@
##           feat/ivan/relocalize2    #3900      +/-   ##
=========================================================
+ Coverage                  77.90%   77.95%   +0.04%     
=========================================================
  Files                       1335     1339       +4     
  Lines                     127203   127538     +335     
  Branches                   11170    11194      +24     
=========================================================
+ Hits                       99100    99418     +318     
- Misses                     25013    25020       +7     
- Partials                    3090     3100      +10     
Flag Coverage Δ
OS-ubuntu-24.04-arm 74.62% <93.76%> (+0.05%) ⬆️
OS-ubuntu-latest 75.15% <93.76%> (+0.05%) ⬆️
Py-3.10 75.15% <93.76%> (+0.05%) ⬆️
Py-3.11 75.15% <93.76%> (+0.05%) ⬆️
Py-3.12 75.14% <93.76%> (+0.05%) ⬆️
Py-3.13 75.14% <93.76%> (+0.05%) ⬆️
Py-3.14 75.15% <93.76%> (+0.05%) ⬆️
Py-3.14t 75.15% <93.76%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...mos/mapping/relocalization/fiducial/test_module.py 100.00% <100.00%> (ø)
...mos/perception/fiducial/test_marker_aggregation.py 100.00% <100.00%> (ø)
...on/fiducial/test_marker_detection_stream_module.py 100.00% <100.00%> (ø)
dimos/robot/all_blueprints.py 100.00% <ø> (ø)
.../robot/unitree/go2/blueprints/smart/unitree_go2.py 93.18% <100.00%> (+0.32%) ⬆️
...ception/fiducial/marker_detection_stream_module.py 81.60% <83.33%> (+3.56%) ⬆️
dimos/perception/fiducial/marker_aggregation.py 95.27% <95.27%> (ø)
dimos/mapping/relocalization/fiducial/module.py 81.94% <81.94%> (ø)

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@leshy
leshy force-pushed the feat/ivan/relocalize2 branch from 1d1cd54 to d552f67 Compare September 3, 2026 07:34
@leshy
leshy deleted the branch dimensionalOS:feat/ivan/relocalize2 September 4, 2026 08:39
@leshy leshy closed this Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

first-time-contributor PR opened by an author who had not previously committed to this repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants