Skip to main content

perception-localize

Locate objects with a running PerceptionLocalize stack over MCP (`state`, `localize`) - probe the machine, build the time window, watch labels on a schedule, narrow an object's position, change params per call, and tell where a failure comes from. Use whenever a blueprint with PerceptionLocalize and McpServer runs, such as go2-localize or xarm-localize-live.

Source facts

Repository
dimensionalOS/dimos
Last source activity
October 21, 2026 at 18:27
Detected SKILL.md language
English
Stars
4,697
Forks
834

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
14 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
perception-localize
description
Locate objects with a running PerceptionLocalize stack over MCP (`state`, `localize`) - probe the machine, build the time window, watch labels on a schedule, narrow an object's position, change params per call, and tell where a failure comes from. Use whenever a blueprint with PerceptionLocalize and McpServer runs, such as go2-localize or xarm-localize-live.
# Localize over MCP `localize` answers from memory: the colour frames the module has embedded, lifted to 3D through depth or lidar. It does not look at the current frame. Every answer is a verified 3D object with a score and a view count, and an empty answer is an answer. ## Before the first call nvidia-smi --query-gpu=name,memory.used,memory.total --format=csv,noheader uv run dimos mcp call state - Probe the GPU first. How many labels a call can carry depends on it, see "Labels and GPU". - `state()` returns `ready: N frames embedded, localize will answer` or `not ready: <stage>`. Until it reads ready, `localize` returns its stage instead of an answer. - `Error: no running MCP server (is DimOS running?)` means the stack is down. Check `uv run dimos status` and report it. Do not stop or restart the stack. ## Calling localize uv run dimos mcp call localize -j '{"objects": "chair,door", "start": -30, "duration": 30}' --timeout 300 - `localize(objects, start=-10, duration=10, params="")` returns once the answer is built. Send the next call after it returns. - `objects` is one label, or a few separated by commas. The labels of one call share one detector pass per frame. - The client waits 30 s. A window with many unread frames takes longer, so pass `--timeout 300` or set `MCP_TIMEOUT=300`. ## Building the window `start` and `duration` are seconds over the embedded frames the module holds. | arguments | window | |---|---| | `start=-10, duration=10` (default) | the last 10 s | | `start=-60, duration=60` | the last minute | | `start=-60, duration=20` | from 60 s ago to 40 s ago | | `start=30, duration=20` | 20 s, from 30 s after the oldest frame held | - A negative `start` counts back from the newest embedded frame, a positive one forward from the oldest frame still held. The module holds `horizon_s` of frames, 600 s by default, so once memory is full the oldest frame moves. - Frames are embedded at 1 per second, 3 on a walking robot (`mobile=True`), each the sharpest of its slot. - The first answer line is the window as resolved: `window A s to B s of C s of feed, N frames`. With `N = 0` nothing was read and the answer is memory only. - A frame is read once per label and params, `accept_score` and `min_views` aside. A window of frames never read for a label costs detector passes; asking the same window again is nearly free. ## Reading the answer window 0.0s to 30.0s of 28.3s of feed, 76 frames 'chair' at (-0.97, 7.31, 0.40) in world score=0.49 views=6 id=ed6e5cde seen 24s ago no verified detection of 'bag' - One line per verified object, newest sighting first. The position is the centroid of the object's newest sighting, in the frame the line names. - `id` names the same object for as long as the module runs. - `score` is the detector's best score for the object. Compare scores within one label only; the scale changes with the wording. - `views` counts the distinct camera positions, 1 cm apart, that saw the object. An object needs 2, and a camera that does not move adds none. A camera that never moves, such as a fixed top view, stays silent until `min_views` is 1: pass `'{"min_views": 1}'`, which reads no frame again. - Objects found by earlier calls are answered again, also outside this window. `seen Ns ago` is the age of the newest sighting. - `no verified detection` means nothing passed the gates at these params. It is not proof that the object is absent. - Labels are not compared: one object can be answered under two labels. ## Watching labels on a schedule Four labels on a rolling 5 s window, one call after another: uv run dimos mcp call localize -j '{"objects": "chair,door,bag,plant", "start": -5, "duration": 5}' --timeout 60 - A laptop RTX 5070 with 8 GB sustained this, calling continuously. - A call reads only the frames not yet read for its labels, so each call costs the new seconds, not its whole window. - The window must span the time between the starts of two calls: the call's own run time plus any wait. If calls take longer than the window, widen it. The overlap is free, because read frames are skipped; a gap loses frames. - Keep the label strings and `params` the same on every call. Memory is per exact label string, and frames read under other params are read again, unless only `accept_score` or `min_views` changed. - An object that left view stays in the answer at its last position, with a growing `seen Ns ago`. An object that moved farther than `cluster_radius_m` gets a new `id` at its new position, and the old `id` stays at the old one. ## Narrowing a position The position is the newest sighting's, so a closer sighting refines it. 1. Bring the camera close to the object and move it a little: every new camera position adds a view. 2. Ask a short recent window, `{"objects": "red mug", "start": -5, "duration": 5}`. A sighting within `cluster_radius_m` of the object's centre joins it under the same `id`, and the position moves to the new sighting. 3. Check that `views` grew and `seen` is near 0 s. At a table, a walking robot's room-scale params are coarse for small objects: | field | room (`go2`) | desk (`xarm`) | desk (`dual_yam`) | |---|---|---|---| | `cluster_radius_m`: sightings this close are one object | 0.30 | 0.08 | 0.08 | | `fuse_voxel_m`: object cloud resolution | 0.03 | 0.01 | 0.01 | | `min_camera_range_m`: closer sightings are dropped | 0.5 | 0.28 | 0.07 | | `surface_patch_max_rise_m`: a sighting this flat on the support is surface | 0.08 | 0.003 | 0.003 | Pass desk-scale field overrides on every call for that label while working at the table. Objects the label already has from room-scale calls keep how they were built; a label first asked at the table is built at desk scale. ## Changing params per call Each blueprint sets its rig's `LocalizeParams` (`types.py`) at construction (desk-scale defaults in `LocalizeParams()`, room-scale `GO2_PARAMS` in `go2_localize.py`). `params` is a JSON object of `LocalizeParams` field overrides for that call only. The next call without it is back on the module's params, the ones its blueprint set. | `params` value | meaning | |---|---| | `""` | the module's params | | `'{"accept_score": 0.45}'` | these fields on the module's params | uv run dimos mcp call localize -j '{"objects": "apple", "params": "{\"accept_score\": 0.45}"}' --timeout 300 uv run dimos mcp call localize -j '{"objects": "apple", "params": "{\"min_views\": 3}"}' --timeout 300 | field | acts on | what it gates | |---|---|---| | `accept_score` | every object of the label, at once | best score an object needs to be answered | | `min_views` | every object of the label, at once | camera positions an object needs | | `candidate_floor` | frames read by this call | detector score that forms a box; lower means more boxes and more GPU memory | | `cluster_radius_m` | frames read by this call | sightings this close are one object | | `max_object_extent_m`, `min_points`, `min_camera_range_m` | frames read by this call | size, points and camera distance a sighting needs | | `fuse_voxel_m` | objects a sighting of this call joins | resolution of the object's cloud | | `verify_radius_m`, `peak_prominence` | frames read by this call | how many frames retrieval hands to the detector | - `accept_score` and `min_views` act only when the answer is built: changing them reads no frame again and applies at once to everything remembered for the label. Try them first. - The other fields shape only what happens from now on. Objects already built keep how they were built. - A misspelled field fails the call with `unexpected keyword argument`. - Every field and its desk-scale default is defined on `LocalizeParams` in `types.py`. ## Labels and GPU - Do not pass many labels at once. Every detector box is segmented on the GPU, so each label adds memory and time. - Reference: a laptop RTX 5070 with 8 GB sustained 4 labels per call on a rolling 5 s window, calling continuously. With less free GPU memory, or with Rerun or other models on the same GPU, start with fewer. - Use concrete nouns with one attribute that sets the object apart: `red mug`, `wooden crate`. Broad or overlapping labels make many boxes. - On `Remote torch.OutOfMemoryError`, halve the labels per call. ## Where a failure comes from Embedding, detection and segmentation work from the image and find almost any object. Lifting a detection to 3D needs depth at the object, and that depends on the rig. Most failures to get a position come from the lift or from the rig, not from the image models. ### Room: walking robots - **No depth at the object.** The stack lifts through the rig's depth camera or lidar, so a detected object gets no 3D answer until depth reaches it. Go2's L1 lidar is sparse and covers a small radius: when the robot starts moving, localize can lift only what is close. Bring the robot nearer and ask a recent window again. - **Odometry drift.** A static object seen at two times with drifted poses lands in two places, as two `id`s. The one seen most recently matches the current odometry. ### Desk: arms - **Depth** is rarely the problem. - **Look-alike objects.** A group of objects of the same colour and shape, with few unique features, can be missed by the detector from some angles. ### Any rig - **Calibration.** A poorly calibrated rig shows every object twice, the copies shifted by the same offset for every object. That points at the rig, not at the params. - **Light and environment.** Every rig depends heavily on them. Tune params on the fly with the call's `params`, the score fields `candidate_floor` and `accept_score` first. - **Clouds for grasping.** `fuse_voxel_m` is 0.01 in `LocalizeParams()` and 0.03 in `go2`. For GraspGenX, go as low as 0.003, because it can reject clouds fused at larger voxels. The voxel applies every time a sighting joins an object, so pass it on every call for that label, for example `'{"fuse_voxel_m": 0.003}'`. A cloud already fused coarser does not regain detail. - **Simulation.** Sim images and MuJoCo workflows need their own params; the presets were fit on images from real rigs. Tuning params offline on a recording, before deploying them, is in `README.md`.
View on GitHub