Skip to content

feat(mapping): add a voxel clear mask so a sensor can erase its own ghost - #3712

Merged
mustafab0 merged 1 commit into
mainfrom
mustafa/feat/voxel-clear-mask
Aug 27, 2026
Merged

feat(mapping): add a voxel clear mask so a sensor can erase its own ghost#3712
mustafab0 merged 1 commit into
mainfrom
mustafa/feat/voxel-clear-mask

Conversation

@mustafab0

@mustafab0 mustafab0 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

The physics

Ray tracing clears a voxel by passing a ray through it to a farther return. That works when the sensor sees past the thing it is mapping.

A wrist camera does not. Its own arm sits between the lens and the space behind it, so no ray ever reaches through the arm and record_miss never fires in that volume. Meanwhile the arm's own returns land in the map as ordinary hits. The result: the arm deposits voxels of itself, and there is no mechanism that can ever erase them. Over a work session the robot slowly walls itself in with a ghost of where it has been, and a planner reading global_map refuses to go there.

Ray-trace clearing cannot fix this — the geometry forbids it. The information has to come from somewhere else: a publisher that knows, from URDF plus TF, that a given volume is the robot and therefore free.

What this adds

A voxel_clear_mask: Input<PointCloud2> port. The cloud is world-frame metric points meaning "these are definitely free"; the node deletes the voxels covering them.

  • Frame-gated against world_frame. The points are metric positions, so the mask must already be in the world frame

Tests

cargo test --workspace --all-features --locked: 60 passed. cargo fmt --check and cargo clippy -D warnings clean..

Files: 6.

@mustafab0
mustafab0 requested a review from leshy as a code owner August 27, 2026 01:00
@mustafab0
mustafab0 requested a review from aclauer August 27, 2026 01:00
@mustafab0
mustafab0 force-pushed the mustafa/feat/voxel-clear-mask branch from d713c5a to 3502047 Compare August 27, 2026 01:02
@greptile-apps

greptile-apps Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds a point-cloud clear mask for removing mapped voxels and updates the mapper’s persistent and live-overlay data. Rust tests reproduced that clearing a healthy voxel leaves it eligible for point emission and leaves neighboring support counts inflated, so the clear operation does not fully remove the voxel’s map presence.

Confidence Score: 4/5

Not safe to merge until voxel clearing synchronizes the healthy-chunk index and neighboring support counters.

Focused Rust execution exercised the healthy-voxel clearing path twice and consistently showed that a deleted voxel remains emitted and that a neighbor retains excess support.

Files Needing Attention: dimos/mapping/ray_tracing/rust/src/voxel_ray_tracer.rs

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced a proof for the posted P1 finding and linked it to the review comment.
  • T-Rex ran the focused Rust test harness to execute the clear_voxels tests and captured the initial and repeated outputs.
  • Contract validation showed that voxel_ray_tracer.rs, lines 324-332, only removes the entry from voxels; it does not update the health index or propagate a support decrement; and the two existing execution captures show failure of clear_voxels_removes_from_the_healthy_chunk_index and clear_voxels_decrements_neighbor_support.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 clear_voxels leaves stale healthy-index entries and inflated neighbor support

    • Bug
      • Clearing a healthy voxel leaves its key in the healthy-chunk index, so emit_points(..., support_min = 0, ...) can emit the deleted voxel, while neighboring voxels retain inflated support.
    • Cause
      • clear_voxels at dimos/mapping/ray_tracing/rust/src/voxel_ray_tracer.rs:324-332 directly removes from voxels without invoking the health-index synchronization at lines 202-216 or support propagation at lines 241-259.
    • Fix
      • When removing a healthy voxel, remove it from the healthy-chunk index and propagate -1 support to existing neighbors, preferably through a shared health-transition helper.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: d713c5a | Re-trigger Greptile

Comment thread dimos/mapping/ray_tracing/rust/src/voxel_ray_tracer.rs
@codecov

codecov Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

@@            Coverage Diff             @@
##             main    #3712      +/-   ##
==========================================
- Coverage   78.01%   78.00%   -0.01%     
==========================================
  Files        1301     1301              
  Lines      125328   125329       +1     
  Branches    10952    10952              
==========================================
- Hits        97769    97765       -4     
- Misses      24375    24381       +6     
+ Partials     3184     3183       -1     
Flag Coverage Δ
OS-ubuntu-24.04-arm 73.08% <100.00%> (-0.01%) ⬇️
OS-ubuntu-latest 74.89% <100.00%> (-0.01%) ⬇️
Py-3.10 74.88% <100.00%> (-0.01%) ⬇️
Py-3.11 74.88% <100.00%> (-0.01%) ⬇️
Py-3.12 74.89% <100.00%> (-0.01%) ⬇️
Py-3.13 74.89% <100.00%> (-0.01%) ⬇️
Py-3.14 74.89% <100.00%> (-0.01%) ⬇️
Py-3.14t 74.88% <100.00%> (-0.01%) ⬇️
SelfHosted-Large 29.90% <100.00%> (+<0.01%) ⬆️
SelfHosted-Linux 35.05% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
dimos/mapping/ray_tracing/module.py 100.00% <100.00%> (ø)

... and 3 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.

…host

Ray tracing only clears what a ray passes through. A sensor that occludes
itself - a wrist camera staring past its own arm - can never clear the volume
its arm hides: no ray reaches through the arm, so record_miss never fires
there. The arm's own returns land in the map and stay, and over a work session
the robot slowly walls itself in.

A publisher that knows those points are free now says so on a voxel_clear_mask
port. The mask is world-frame metric points, stamp-ordered and gated against
world_frame, quantized through the same path returns take.

VoxelMap::clear_voxels deletes by key, not with a bare remove: it also pulls
the key out of the healthy-chunk index emit_points scans and decrements every
existing 26-neighbour's support counter, exactly as record_miss does on its
removal path. Skipping either leaves an index pointing at a voxel that is gone
and support counts that never come back down. An unhealthy voxel was never in
its neighbours' counts, so removing it leaves them alone. The fine-cell bitmask
rides inside the removed voxel, so the fine layer needs no separate cleanup.
Mapper::clear_metric drops this frame's live hits alongside the voxels, so a
cleared voxel cannot come back out of the live overlay.

@aclauer aclauer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does setting the shadow_depth config not fix this? It defaults to 0.1

mustafab0 added a commit that referenced this pull request Aug 27, 2026
Manipulation becomes the first non-navigation consumer of the ray-tracing voxel
map. A wrist camera builds an occupancy map of the workspace, and the planner
treats those voxels as one octree obstacle.

Three producers, all model-driven so they work for any URDF:

- RobotTfPublisher resolves a complete TF tree from measured joint state. A
  manipulator's sensor pose comes from forward kinematics, not SLAM, and main
  had no generic equivalent - only a G1-specific publisher. An incomplete joint
  state publishes nothing rather than leaving links at a stale pose.

- PointCloudSelfFilter cuts the robot out of its own camera's cloud, and emits
  the map cells the robot occupies as a clear mask. It emits the previous
  frame's cells too: a link that moved leaves a ghost behind it that ray
  tracing can never clear, because the link occluded that volume while it was
  there. The mask floors world coordinates by voxel_size exactly as the mapper
  does, or it would name cells the map does not hold.

- GlobalMapObstacleBridge reconciles each complete map snapshot into the
  planning world under one stable obstacle ID, so a republished map replaces
  the obstacle instead of piling up new ones.

ObstacleType.OCTREE carries occupied cell centers plus a resolution into
roboplan's addOcTreeGeometry. Coal reads a cell as center, edge length, cost
and occupancy threshold; every cell here is occupied, so cost is 1.0 against
the 0.5 default. Verified against roboplan 0.5.1 and the pinned 0.6.1: adding
cells flips a collision-free configuration to colliding and blocks a path
through them, while a clear path stays clear.

Obstacle.points is a tuple of tuples, not an ndarray. Obstacles are deepcopied
into world snapshots, compared, and pickled across worker RPC, and an ndarray
field breaks all three.

The shape-string obstacle RPCs cannot carry a map, so ManipulationModule gains
set_voxel_obstacle alongside them. Additive: no existing caller changes. An
empty point list removes the obstacle, which is how a mapper says the space it
owns is clear. Validation caps an octree at 200k points, since every point
crosses worker RPC as a pickled tuple and an unbounded map would stall the pipe.

Depends on the voxel_clear_mask port (#3712) for the mask to land anywhere, and
on the TFLookup forward_tolerance keyword (#3711) for the self filter to wait
on a wrist transform still in flight.
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Aug 27, 2026
@mustafab0
mustafab0 added this pull request to the merge queue Aug 27, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 27, 2026
@mustafab0
mustafab0 added this pull request to the merge queue Aug 27, 2026
Merged via the queue into main with commit b4cd978 Aug 27, 2026
30 checks passed
@mustafab0
mustafab0 deleted the mustafa/feat/voxel-clear-mask branch August 27, 2026 03:09
mustafab0 added a commit that referenced this pull request Aug 27, 2026
Manipulation becomes the first non-navigation consumer of the ray-tracing voxel
map. A wrist camera builds an occupancy map of the workspace, and the planner
treats those voxels as one octree obstacle.

Three producers, all model-driven so they work for any URDF:

- RobotTfPublisher resolves a complete TF tree from measured joint state. A
  manipulator's sensor pose comes from forward kinematics, not SLAM, and main
  had no generic equivalent - only a G1-specific publisher. An incomplete joint
  state publishes nothing rather than leaving links at a stale pose.

- PointCloudSelfFilter cuts the robot out of its own camera's cloud, and emits
  the map cells the robot occupies as a clear mask. It emits the previous
  frame's cells too: a link that moved leaves a ghost behind it that ray
  tracing can never clear, because the link occluded that volume while it was
  there. The mask floors world coordinates by voxel_size exactly as the mapper
  does, or it would name cells the map does not hold.

- GlobalMapObstacleBridge reconciles each complete map snapshot into the
  planning world under one stable obstacle ID, so a republished map replaces
  the obstacle instead of piling up new ones.

ObstacleType.OCTREE carries occupied cell centers plus a resolution into
roboplan's addOcTreeGeometry. Coal reads a cell as center, edge length, cost
and occupancy threshold; every cell here is occupied, so cost is 1.0 against
the 0.5 default. Verified against roboplan 0.5.1 and the pinned 0.6.1: adding
cells flips a collision-free configuration to colliding and blocks a path
through them, while a clear path stays clear.

Obstacle.points is a tuple of tuples, not an ndarray. Obstacles are deepcopied
into world snapshots, compared, and pickled across worker RPC, and an ndarray
field breaks all three.

The shape-string obstacle RPCs cannot carry a map, so ManipulationModule gains
set_voxel_obstacle alongside them. Additive: no existing caller changes. An
empty point list removes the obstacle, which is how a mapper says the space it
owns is clear. Validation caps an octree at 200k points, since every point
crosses worker RPC as a pickled tuple and an unbounded map would stall the pipe.

Depends on the voxel_clear_mask port (#3712) for the mask to land anywhere, and
on the TFLookup forward_tolerance keyword (#3711) for the self filter to wait
on a wrist transform still in flight.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants