Skip to content

Add offline object inventory and localize perception pipeline DIM1343 - #3422

Open
bogwi wants to merge 2 commits into
mainfrom
danvi/dim1343/object-registration-pr1
Open

Add offline object inventory and localize perception pipeline DIM1343#3422
bogwi wants to merge 2 commits into
mainfrom
danvi/dim1343/object-registration-pr1

Conversation

@bogwi

@bogwi bogwi commented Aug 10, 2026

Copy link
Copy Markdown
Member

What features we implement

From the issue feature list, this branch covers A, D, E, and F.

  • (A) Look at an image and identify all objects: inventory() discovers every in-scope support on the table without a text prompt, then names each instance.
  • (D) Text prompt to 2D masks: localize() runs SigLIP retrieval, OWLv2 boxes, and EdgeTAM segment() so a query string becomes masks on ranked frames.
  • (E) Prompt to point cloud / latest 3D pose: localize() lifts those masks through depth, clusters them, verifies across views, and returns the newest verified cloud and position. Offline over a recording, no live robot.
  • (F) Object deduplication: inventory() merges same-frame fragments and associates tracks with geometry only (Hungarian assignment plus fragment merge). Labels are applied after identity is fixed.

Not in this branch: (B) exact dimensions, (C) 360-degree clouds, (G) re-ID.

What is the proposed stack

Both paths open a memory2 SqliteStore, read StreamTF from it, and call into dimos.perception.memory.gates for poses and stillness. Both fit a support plane with fit_support_plane and lift masks with ImageDetections3DPC.from_depth.

Localize

(tool_localize -> localize()): SigLIP embeds and ranks frames. Retrieval keeps only gates.camera_still frames. Then fit_support_plane. Pass 2 loads Owlv2Detector and EdgeTAMImageSegmenter together.

_DetectionCache.detect runs OWLv2 boxes then EdgeTAM segment() on the same frame. _lift builds world clouds. Clusters form, sees() finds other viewpoints, those frames re-enter the same detect/lift path. The winner is the verified cluster with the newest observation timestamp.

flowchart TB
  L0["tool_localize: SqliteStore"]
  L1["localize: StreamTF + time window"]
  L2["SigLIPModel: embed + text retrieve"]
  L3["gates.camera_still on ranked frames"]
  L4["fit_support_plane"]
  L5["Owlv2Detector.query_detections"]
  L6["EdgeTAMImageSegmenter.segment"]
  L7["ImageDetections3DPC.from_depth + filters"]
  L8["cluster + project.sees cross-view"]
  L9["Localization: latest verified"]
  L0 --> L1 --> L2 --> L3 --> L4 --> L5 --> L6 --> L7 --> L8 --> L9
Loading

Inventory

(tool_inventory -> inventory()): gates.still_intervals then gates.keyframes (camera-still and scene-still). Then fit_support_plane.

EdgeTAM propose_all discovers masks with no text query. _lift_frame, plane _in_scope, and _merge_same_frame run per keyframe. _associate then _merge_tracks build identity. _name_and_suppress loads OWLv2 only after association. No SigLIP.

flowchart TB
  I0["tool_inventory: SqliteStore"]
  I1["inventory: StreamTF + time window"]
  I2["gates.keyframes: camera-still + scene-still"]
  I3["fit_support_plane"]
  I4["EdgeTAMImageSegmenter.propose_all"]
  I5["_lift_frame + _in_scope + _merge_same_frame"]
  I6["_associate + _merge_tracks"]
  I7["_name_and_suppress: Owlv2Detector"]
  I8["list of Instance"]
  I0 --> I1 --> I2 --> I3 --> I4 --> I5 --> I6 --> I7 --> I8
Loading

Models in the code: SigLIPModel (google/siglip-base-patch16-224) in localize only, Owlv2Detector (google/owlv2-base-patch16-ensemble) for localize boxes and inventory naming, EdgeTAMImageSegmenter (edgetam.pt) for localize segment() and inventory propose_all(). Inventory never constructs SigLIP. Localize never calls propose_all().

Prerequisites

recording

Both CLIs default to the stationery calibrated memory2 db via get_data(...):

xarm6_worldbelief_realsense_d435i_stationery_calibrated/xarm6_worldbelief_20260729_203624_161992.db

get_data resolves under the dimos data/ tree. If that path is already present, it returns it. If not, it pulls data/.lfs/xarm6_worldbelief_realsense_d435i_stationery_calibrated.tar.gz with git-lfs and decompresses it. Override with --dataset for another sqlite recording. The recording must have a tf stream, or localize / inventory raise.

models

Three weight sets, two loading paths.

  1. EdgeTAM checkpoint. EdgeTAMImageSegmenter loads get_data("models_edgetam") / "edgetam.pt". Same LFS rule as the recording: first miss pulls data/.lfs/models_edgetam.tar.gz, later runs reuse the decompressed tree. Needs CUDA or MPS. CPU raises.
  2. SigLIP and OWLv2 (Hugging Face transformers). SigLIPModel loads google/siglip-base-patch16-224. Owlv2Detector loads google/owlv2-base-patch16-ensemble. Both call from_pretrained(...) without local_files_only. If the Hugging Face hub cache already has the weights, they load from cache. If not, the first run downloads them into that cache and later runs reuse it.
  3. The transformers Python package itself is a dependency (dimos[perception] / sentencepiece for the SigLIP tokenizer). That is the library install through uv, not the model weights.

How to test

Localize

Group letters A-F below are command groups in this document. They are not the issue's feature letters A-G.

Template:

uv run python -m dimos.perception.memory.tool_localize "QUERY" --from FROM --duration DUR && uv run dimos-viewer localize.rrd

Omit --from / --duration for full recording. On expected None, drop the && viewer part (there should be no successful hit).

A. S1 positives (all baseline objects present)

# Query Command CLI expect Rerun expect (required)
A1 red marker ... "red marker" --from 53 --duration 76 exit 0; prints position box + blue on vertical red marker
A2 black pen ... "black pen" --from 53 --duration 76 exit 0 box + blue on horizontal pen
A3 book ... "book" --from 53 --duration 76 exit 0 box + blue on book
A4 post-it ... "yellow sticky-note" --from 53 --duration 76 exit 0 box + blue on small sticky pad (try "post-it pad" if this misses)

Commands

uv run python -m dimos.perception.memory.tool_localize "red marker" ORA1_red_marker.rrd --from 53 --duration 76 && uv run dimos-viewer ORA1_red_marker.rrd

uv run python -m dimos.perception.memory.tool_localize "black pen" ORA2_black_pen.rrd --from 53 --duration 76 && uv run dimos-viewer ORA2_black_pen.rrd

uv run python -m dimos.perception.memory.tool_localize "book" ORA3_book.rrd --from 53 --duration 76 && uv run dimos-viewer ORA3_book.rrd

uv run python -m dimos.perception.memory.tool_localize "yellow sticky-note" ORA4_post_it.rrd --from 53 --duration 76 && uv run dimos-viewer ORA4_post_it.rrd

B. Absences (CLI enough; rerun only if it wrongly succeeds)

# Query Command CLI expect Fail look
B1 coke can (never in inventory) ... "coke can" --from 53 --duration 76 no verified detection / exit 1 Any green/red/blue on table objects
B2 banana (never in inventory) ... "yellow banana" --from 53 --duration 76 exit 1 Same
B3 black tape before S5 ... "roll of black tape" --from 0 --duration 417 exit 1 Clouds on pen/marker/etc. labeled tape
B4 red marker during S2 (removed) ... "red marker" --from 135 --duration 75 exit 1 Clouds where marker no longer is

Commands

uv run python -m dimos.perception.memory.tool_localize "coke can" ORB1_coke_can.rrd --from 53 --duration 76

uv run python -m dimos.perception.memory.tool_localize "yellow banana" ORB2_banana.rrd --from 53 --duration 76

uv run python -m dimos.perception.memory.tool_localize "roll of black tape" ORB3_black_tape.rrd --from 10 --duration 417

uv run python -m dimos.perception.memory.tool_localize "red marker" ORB4_red_marker.rrd --from 135 --duration 75

C. Tape after it appears

# Query Command CLI expect Rerun expect
C1 roll of black tape ... "roll of black tape" --from 427 --duration 76 exit 0; ts in S5 box + blue on tape roll

Commands

uv run python -m dimos.perception.memory.tool_localize "roll of black tape" ORC1_black_tape.rrd --from 427 --duration 76 && uv run dimos-viewer ORC1_black_tape.rrd

D. Latest position (marker moved S1 -> S3)

Run both; keep the printed position lines (and/or both .rrd files under different names).

# Query Command What to look for
D1 red marker @ S1 ... "red marker" --from 53 --duration 76 Note CLI position; blue cloud at first marker spot
D2 red marker @ S3 ... "red marker" --from 218 --duration 76 Note CLI position; blue at different spot
D3 red marker full ... "red marker" (no from/duration) CLI position near D2, not D1; blue at post-move spot

Pass: D1 and D2 positions clearly differ (order of centimeters+); D3 matches D2's place.
Fail: D3 still on D1's place (stale).

Commands

uv run python -m dimos.perception.memory.tool_localize "red marker" ORD1_red_marker.rrd --from 53 --duration 76 && uv run dimos-viewer ORD1_red_marker.rrd

uv run python -m dimos.perception.memory.tool_localize "red marker" ORD2_red_marker.rrd --from 218 --duration 76 && uv run dimos-viewer ORD2_red_marker.rrd

uv run python -m dimos.perception.memory.tool_localize "red marker" ORD3_red_marker.rrd && uv run dimos-viewer ORD3_red_marker.rrd

E. Optional hard distractors

# Query Window Expect
E1 "black tape" (shorter phrasing) before S5 --from 0 --duration 417 exit 1 (same as B3)
E2 "red marker" in S1 --from 53 --duration 76 must not land on pen/book

Commands

uv run python -m dimos.perception.memory.tool_localize "black tape" ORE1_black_tape.rrd --from 0 --duration 417

uv run python -m dimos.perception.memory.tool_localize "red marker" ORE2_red_marker.rrd --from 53 --duration 76 && uv run dimos-viewer ORE2_red_marker.rrd

F. Twin ambiguity (S4, two identical post-its)

From inventory.yaml, duplicate_of: post_it adds a second identical post-it pad during the 417.9-427.2 gap. Both are on the table and visible in the same frames from S4 onward. No appearance model can tell them apart, so a confident single answer is wrong even when it happens to pick a real post-it. This check does not require tool_inventory or an instance_id field on localize.

# Query Command Pass Fail means
F1 post-it @ S4 ... "yellow sticky-note" --from 342 --duration 76 either a hit with ambiguity_margin below the refusal threshold, or None with an ambiguity reason a hit with a high identity score and no ambiguity flag: the system guessed and did not say so
F2 post-it @ S1 ... "yellow sticky-note" --from 53 --duration 76 a confident hit, exactly one post-it exists refusing here means the ambiguity gate is too tight

F1 and F2 are a pair. F1 alone can be passed by a system that refuses everything; F2 is what stops that.

Commands

uv run python -m dimos.perception.memory.tool_localize "yellow sticky-note" ORF1_post_it_S4.rrd --from 342 --duration 76
uv run python -m dimos.perception.memory.tool_localize "yellow sticky-note" ORF2_post_it_S1.rrd --from 53  --duration 76

Inventory

Headless-first. Counts, pairwise distances, and name similarity come from CLI / pytest stdout. Inventory.yaml pins which descs must be present (and, for I2, that each pinned desc stays at exactly one instance when the window grows). It does not pin the total instances: N against table clutter that sits in the same support-plane band.

Windows (same as the localize half; from events.jsonl relative to color-image start lo). Verified on the db: color lo = 1785382584.90; S1 53.3-128.8, S4 341.7-417.9, S5 427.2-502.7. --duration 76 covers S1 and S5 fully; --from 342 clips the first 0.3 s of S4 (scan starts at 341.7), kept anyway for consistency with test_localize_replay.py. PR1-inventory.md's dedup row uses the same 76 s span.

State --from --duration Physical fact (inventory.yaml)
S1_baseline 53 76 4 items: red marker, post_it, black pen, book
S1_short (dedup stress) 53 30 same 4; first 30s of S1 (fewer frames than full S1)
S4_identical 342 76 5 items: baseline 4 + 2nd identical post_it (duplicate_of)
S5_new_item 427 76 6 items: S4's 5 + roll of black tape (new_class; notes pin 6)

Ground-truth names for matching use inventory desc text (not name keys like red_marker):

  • red marker
  • yellow sticky-note / post-it pad (match either phrasing / soft embedding)
  • black pen
  • book
  • roll of black tape (S5+)

I1. S1 baseline count (A)

uv run python -m dimos.perception.memory.tool_inventory --from 53 --duration 76

I2. Dedup invariant (F) - short S1 vs full S1

uv run python -m dimos.perception.memory.tool_inventory --from 53 --duration 30
uv run python -m dimos.perception.memory.tool_inventory --from 53 --duration 76

Requiring equal total N across 30 s and 76 s conflates F fragmentation with newly visible in-scope clutter (laptop, foam blocks, mouse, phone, unknown-*).

Example

13:22:56.754[inf][perception/memory/inventory.py] inventory window: 53.0s to 83.0s (30.0s)
13:22:57.262[inf][perception/memory/inventory.py] gates: 12 keyframes pass camera-still + scene-still
13:22:59.432[inf][perception/memory/inventory.py] support plane: 18721 inliers
13:23:18.869[inf][perception/memory/inventory.py] discovery: 55 in-scope supports across 12 keyframes
13:23:18.884[inf][perception/memory/inventory.py] association: 17 grounded instances
13:23:18.885[inf][perception/memory/inventory.py] naming: OWLv2 over 10 keyframes, 4 prompts of <= 10 classes
instances: 10
0  id=obj-00  name=book  xyz=(0.533,-0.145,0.122)  ts_offset=80.9  members=8
1  id=obj-01  name=laptop  xyz=(0.892,0.337,0.213)  ts_offset=70.9  members=6
2  id=obj-02  name=sticky notes  xyz=(0.820,0.074,0.103)  ts_offset=64.5  members=5
3  id=obj-03  name=marker  xyz=(0.742,0.227,0.182)  ts_offset=70.9  members=6
4  id=obj-04  name=pen  xyz=(0.616,0.047,0.106)  ts_offset=70.9  members=8
5  id=obj-05  name=computer keyboard  xyz=(1.080,0.146,0.116)  ts_offset=69.5  members=5
6  id=obj-06  name=laptop  xyz=(0.667,-0.682,0.165)  ts_offset=60.9  members=2
7  id=obj-07  name=roll of tape  xyz=(0.458,-0.389,0.130)  ts_offset=60.9  members=2
8  id=obj-08  name=toy block  xyz=(0.516,-0.451,0.121)  ts_offset=60.9  members=2
9  id=obj-09  name=unknown-0  xyz=(0.575,0.225,0.111)  ts_offset=80.9  members=2

members is not object count. Each keyframe that proposes a mask, lifts it to a world cloud, and associates that cloud to the same track appends one SupportObservation. Across the window the book was accepted on 8 keyframes, so members=8. The print still shows one xyz because it only reports the latest member’s centroid (scene state at the newest sighting), not 8 positions.

I3. S4 identical post-its (F hard case)

uv run python -m dimos.perception.memory.tool_inventory --from 342 --duration 76

Expect exactly 2 instances name=sticky notes.

…43 PR1).

Text prompt to 2D masks and memory search to 3D point clouds, with OWLv2, SigLIP, MPS support, LocalizePolicy, and related dependency pins.
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
dimos/models/segmentation/edge_tam.py 22.22% 28 Missing ⚠️
dimos/models/base.py 80.00% 1 Missing and 1 partial ⚠️
@@            Coverage Diff             @@
##             main    #3422      +/-   ##
==========================================
- Coverage   76.09%   76.07%   -0.02%     
==========================================
  Files        1190     1190              
  Lines      115295   115338      +43     
  Branches    10367    10372       +5     
==========================================
+ Hits        87729    87746      +17     
- Misses      24554    24580      +26     
  Partials     3012     3012              
Flag Coverage Δ
OS-ubuntu-24.04-arm 70.14% <46.55%> (-0.02%) ⬇️
OS-ubuntu-latest 72.14% <46.55%> (-0.02%) ⬇️
Py-3.10 72.13% <46.55%> (-0.02%) ⬇️
Py-3.11 72.12% <46.55%> (-0.03%) ⬇️
Py-3.12 72.13% <46.55%> (-0.01%) ⬇️
Py-3.13 72.13% <46.55%> (-0.02%) ⬇️
Py-3.14 72.14% <46.55%> (-0.02%) ⬇️
Py-3.14t 72.13% <46.55%> (-0.02%) ⬇️
SelfHosted-Large 29.65% <27.58%> (-0.01%) ⬇️
SelfHosted-Linux 35.83% <29.31%> (-0.02%) ⬇️

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

Files with missing lines Coverage Δ
dimos/models/test_base.py 100.00% <100.00%> (ø)
dimos/perception/detection/detectors/yoloe.py 54.08% <100.00%> (ø)
dimos/models/base.py 74.07% <80.00%> (+0.10%) ⬆️
dimos/models/segmentation/edge_tam.py 26.20% <22.22%> (-0.55%) ⬇️

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

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