feat(relocalization): fiducial prior + per-prior config for Relocaliz… - #3160
Closed
AaryanAgrawal wants to merge 1 commit into
Closed
feat(relocalization): fiducial prior + per-prior config for Relocaliz…#3160AaryanAgrawal wants to merge 1 commit into
AaryanAgrawal wants to merge 1 commit into
Conversation
…ationModule A surveyed AprilTag becomes an absolute relocalization fix that competes in the same wall-fitness judge as RANSAC. Adds the prior pool + per-prior triggers, the Huber/Markley tag aggregation (live + --markers survey), and --eval per-source logging. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(relocalization): fiducial prior + per-prior config for RelocalizationModule
Contribution path
Problem
A robot waking up in a prior map where lidar geometry is ambiguous — a corridor, a bare room, a symmetric hall — relocalizes into the wrong place on wall fitness alone, and every nav goal after it inherits the error. RANSAC is the only source, it cannot answer until the live map is dense enough to search, and an AprilTag surveyed into that map has no way in.
What this adds
Three things, and nothing else is load-bearing:
--evallogs a per-source table so you can see which prior won, live or under replay.1 · The fiducial relocalization prior
RelocalizationModuleused to have one hardcoded source — RANSAC. This generalizes "a relocalization source" into a prior: a thing that proposes candidate poses on its own trigger. Every prior's candidates go through the one shared judge; the winner is published with itssource.The design is a Protocol + a discriminated union — the Strategy pattern, made declarative. A prior is a candidate source (structural interface); a blueprint declares which priors run.
The two triggers look like what they are — RANSAC is polled on each cloud with a time+geometry gate; the fiducial is edge-triggered by a completed tag burst:
The fiducial fix is one frame composition — a surveyed tag pose against a live-detected one, judged like any other candidate:
Per prior, its own trigger and accept bar:
fitness_thresholdmin_local_points=50000enabledtoggles a prior in or out; a fiducial-only or lidar-only blueprint runs the same path.2 · Markers
Detect tags, gate each glimpse, and fuse a marker's sightings into one robust pose via
robust_cluster_pose(Huber-IRLS translation + Markley quaternion mean). One fusion, two windows — because time means different things offline and online:The offline survey is a single pass: PGO-corrected
world_T_tagper sighting, grouped by id, fused, and written alongside the premap as themarker_map.jsonthe fiducial prior loads. The live path streamsaggregated_detections— one aggregated pose per completed burst — additively beside the detector's existing per-framedetections.3 · Eval logging
RelocEval(--eval) listens on the real streams — no ground truth, live and under--replayalike — and logs a per-source table so you can see which prior is winning:The run log is the only place each accept's winning
source+ fitness live (the/tfcarries neither), so--evalturns on the module's verbose trace and joins accepts to sources by translation.Breaking Changes
fitness_thresholdandmin_local_pointsmoved onto the prior entries. The old module-level keys raise, naming the new home.priorsis required onRelocalizationModule.Config.unitree-go2-relocalizationis replaced by three blueprints named by their priors:-lidar,-lidar-fiducial,-fiducial.Core changes — why
perception/fiducial/apriltag_aggregation.py(new) — per-tag robust fusion: Huber-IRLS translation + Markley quaternion mean, each cited inline; a streaming aggregator for the live path.perception/fiducial/{marker_transformer,marker_detection_stream_module,marker_pose}.py— additive: a newaggregated_detectionsstream + burst aggregator beside the existing detector. Upstreamdetectionsuntouched.mapping/relocalization/{module,priors,relocalize}.py— the prior pool, per-prior triggers/thresholds, jump guard, and the shared judge (refine_candidatesunchanged from arkluc's go2 relocalization #2160).mapping/utils/cli/map.py—--markersalso fuses the sightings and writes<premap>.marker_map.jsonalongside the export.robot/cli/dimos.py—-otolerates pydanticmissing, so a partial overlay may omit a required field likerelocalizationmodule.priors. Unknown keys and type errors still raise.blueprints/smart/unitree_go2.py+ regeneratedall_blueprints.py— the three presets. Nocore/ortransport/change.How to Test
Hardware. Run on a Go2 against an sf office premap with surveyed tags, started cold in the mapped room:
Watch
relocalize acceptedfor the winningsource=; Ctrl+C logs the per-source table.Replay, both priors, no robot:
Test to read:
test_relocalize.py::test_fiducial_composes_map_T_world_then_consumes_it_once. Existing suites unchanged.Follow-up
To investigate before this is trusted beyond flat, single-floor sites:
gravity_tilt_max_deg(10°) of upright and scores wall-only fitness, soworld_T_mapis a gravity-aligned planar correction. How it composes with a 3D nav stack — ramps, stairs, multi-floor — is untested.global_map. A decoded tag is an absolute fix on its own; scoring it against the stored premap submap near the tag instead would drop the lidar dependency at acquisition.AI assistance
Claude Code (Opus 4.8) — design, implementation, tests; all changes reviewed.
Checklist
uv run pytest, pre-commit) for the files I changed.Announcement (Discord / PR comment)
Relocalization takes priors now, and one of them is an AprilTag.
A robot waking in a prior map waits on RANSAC, which cannot answer until the live map is dense enough to search, and in a corridor or a bare room it can answer confidently and wrongly. A surveyed tag is an absolute fix. Both propose into the same judge, which refines against the premap walls and publishes the winner with its source. Triggers are per prior: the tag fires on a sighting burst, RANSAC on its 2 s timer. Both clear the same 0.60 wall-fitness bar.
--evallogs the per-source table.