Skip to content

feat(evals): pointcloud VQA suite + PointCloud2.agent_encode (evo autoresearch, 0.136 -> 0.96) - #3415

Draft
spomichter wants to merge 10 commits into
feat/evals-frameworkfrom
feat/pointcloud-agent-encode
Draft

feat(evals): pointcloud VQA suite + PointCloud2.agent_encode (evo autoresearch, 0.136 -> 0.96)#3415
spomichter wants to merge 10 commits into
feat/evals-frameworkfrom
feat/pointcloud-agent-encode

Conversation

@spomichter

Copy link
Copy Markdown
Contributor

Problem

we had no way to measure whether an agent can actually read pointcloud geometry — and it turns out it couldn't: str(PointCloud2) hands the model num_points and nothing else. baseline on the new pointcloud VQA evals: 0.136, identical to the blind-guessing control (0.138). the encoding carried literally zero information.

Solution

two things, stacked on #3411 (uses its evals framework as the harness):

1. pointcloud VQA benchmark (evals_bench/): 40 generated cases over go2_short + go2_bigoffice (the long replay). ground truth computed analytically from full-res clouds + odom (privileged modality). families: extent / zspan / area (world geometry), nearest-obstacle clearance from the robot's own pose (metric distance), shift / compass / areatrend over cloud sequences (spatiotemporal). anti-gaming: blind-ablation gate (context withheld must stay ≤0.35), answer-leak grep, target-only diff gate, PointCloud2 unit tests.

2. PointCloud2.agent_encode() — found by evo autoresearch overnight (14 experiments, every commit on this branch is one experiment with its hypothesis + measured effect in the message). final: 0.962 (sighted) vs 0.15 blind. 2.5-D design: exact full-cloud scalars (extent/z-span/footprint) + per-frame world centroid + body-height slab as per-row occupied x-intervals in exact world meters with y-extents, plus closed-form reasoning recipes (clearance dx = max(0, min-qx, qx-max), trig-free 8-way compass).

per-family: extent/zspan/area/shift/direction/areatrend ~1.0, nearest 0.81→0.85 (band-limited, sampling-noisy).

things we learned that generalize (kept in the commit messages): models short-circuit branchy recipes but can't short-circuit closed-form formulas; give decision procedures not trig; every added signal needs a routing note or it becomes a distractor for other question types; exact coordinates beat quantized grids for LLM metric reasoning.

report w/ rendered figures: https://dimos-pc-encoding-report.vercel.app

Breaking Changes

None (new method + new benchmark dir only; diff outside those is empty by gate).

How to Test

uv run pytest dimos/msgs/sensor_msgs/test_PointCloud2.py -q
# full eval run (needs OPENAI_API_KEY + lfs data):
env PYTHONPATH=. uv run python evals_bench/benchmark.py
# guessing ablation:
env PYTHONPATH=. uv run python evals_bench/benchmark.py --blind --max-mean 0.35

Contributor License Agreement

  • I have read and approved the CLA

…ht (z 0.15-1.0) ASCII occupancy grid with labeled world coords (cell auto 0.25-3.2m, <=28 cells/dim). Centroid deltas across frames make direction/shift computable; grid + odom pose makes nearest clearance readable; occupied-cell count grounds area.
… legend key (bearing_deg=atan2(dy,dx); round to NEAREST 45deg: 0=E,45=NE,90=N,135=NW,180=W,-135=SW,-90=S,-45=SE) to counter off-by-one sector errors, and 'xy_footprint' {cell_m, occupied_cells} over ALL points (floor included) so area=cells*cell^2 is readable. Grid + centroid unchanged.
…nt: replace atan2 compass legend with exact ratio rule (|dx|>2.41|dy| -> pure E/W; |dy|>2.41|dx| -> pure N/S; else diagonal by signs) so 8-way sector selection needs only compare/multiply; soften 'half a cell nearer' nearest hint to neutral 'point may lie anywhere within the cell' to remove systematic underestimation seen in exp_0003 (5 of 9 nearest outputs low).
…coding: add exact_stats (x/y/z ranges, horizontal_extent_m, vertical_span_m, per-frame occupied_floor_footprint_m2 at 0.2m cells) with note to use scalars not grid for numeric answers; footprint framed as this-frame-only so areatrend compares per-frame areas not cumulative coverage. Grid, compass ratio rule, nearest phrasing unchanged.
… compass note with one sentence telling the reader to judge motion/coverage-gain direction from the centroid_xy_m delta (last minus first), not from bbox range-edge changes. Targets pc_direction_30_59 regression (south vs southwest) introduced when per-frame x/y ranges appeared in exact_stats; grid, nearest phrasing, exact_stats values unchanged.
…lock + routing + centroid-delta compass verbatim, replace boolean body_height_occupancy grid with exp_0008's per-row exact x-interval representation (selective @y, neutral clearance recipe); drop exp_0008's duplicate xy_footprint AABB (exact_stats x/y_range is canonical, routed). Predicted: nearest family rises from ~0.669 toward exp_0008's 0.847 while extent/zspan/area/direction stay at exp_0007 levels.
…one 'dx = 0 if min<=qx<=max else distance to nearer endpoint' clause with the closed-form interval distance 'dx = max(0, min-qx, qx-max) (zero only when qx lies inside the interval)'. Diagnosed on pc_nearest_t40: model answered exactly dy=0.061 for interval 2.67:3.17@7.68 with qx 0.54 m outside it -- it skipped the containment branch. A single formula cannot be short-circuited. Predicted: pc_nearest_t40 recovers (0 -> >0.7), nearest family >0.83, other families unchanged; encoding bytes unchanged.
…ffix with an always-emitted exact y-extent @ymin:ymax per x-interval, and extend the closed-form clearance to both axes (dy = max(0, ymin-qy, qy-ymax)), making clearance an exact point-to-box computation instead of y-band quantization. Predicted: raises nearest family (0.812) by removing the row-band dy error that drives the mild underestimates (bo_nearest_t10/t220, pc_nearest_t40) without exp_0009's mean-collapse regression.
@spomichter

Copy link
Copy Markdown
Contributor Author

evo:ship mergeability report (advisory)

Technique. Adds one method pair to PointCloud2 (agent_encode() + _body_height_occupancy()): exact full-cloud scalars (bbox ranges, extent, z-span, 0.2 m-cell floor footprint), per-frame world centroid, and the body-height slab (z 0.15–1.0 m) as per-row occupied x-intervals in exact world meters with per-interval y-extents — plus closed-form reasoning recipes (clearance dx = max(0, min−qx, qx−max) per axis; trig-free 8-way compass rule). ~2.7 kB per observation.

Behavioural correctness. Pointcloud VQA evals: 0.136 baseline → 0.962 shipped (blind control 0.15, gate ≤0.35). Distilled file is byte-identical to winner exp_0013's, so the measured score carries exactly.

Regression safety. test_PointCloud2.py: 3 passed, 1 deselected (pre-existing marker). No other files touched.

Scope. One file, +93 lines, purely additive (new methods; no existing code modified). A git gate enforced target-file-only diffs throughout the search.

Test correctness. No test modified, weakened, or removed at any point (gate-enforced). New behaviour is covered by the eval suite in this PR's evals_bench/ rather than unit tests — worth a unit test for agent_encode() output shape before merge if reviewers want offline coverage.

Mechanical cleanliness. ruff check + format clean; full pre-commit suite passed.

Codebase adherence. Follows the existing points_f32()/bounding_box_dimensions() method style on the class; numpy-only, no new deps.

Branches. This PR carries the full 8-experiment research history. A distilled single-commit equivalent (identical final file, no evals_bench/) is at feat/pointcloud-agent-encode-min (40f22ba7e) if a squashed landing is preferred.

Caveat: exp_0013 (0.962) vs exp_0012 (0.957) is within measured sampling noise on the nearest family — treat as tied; the tip includes both refinements.

@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
dimos/msgs/sensor_msgs/PointCloud2.py 85.10% 3 Missing and 4 partials ⚠️
@@                  Coverage Diff                  @@
##           feat/evals-framework    #3415   +/-   ##
=====================================================
  Coverage                 76.12%   76.13%           
=====================================================
  Files                      1201     1201           
  Lines                    115988   116035   +47     
  Branches                  10415    10421    +6     
=====================================================
+ Hits                      88294    88338   +44     
  Misses                    24671    24671           
- Partials                   3023     3026    +3     
Flag Coverage Δ
OS-ubuntu-24.04-arm 70.33% <6.38%> (-0.03%) ⬇️
OS-ubuntu-latest 72.17% <6.38%> (-0.03%) ⬇️
Py-3.10 72.17% <6.38%> (-0.03%) ⬇️
Py-3.11 72.17% <6.38%> (-0.02%) ⬇️
Py-3.12 72.16% <6.38%> (-0.03%) ⬇️
Py-3.13 72.17% <6.38%> (-0.02%) ⬇️
Py-3.14 72.17% <6.38%> (-0.03%) ⬇️
Py-3.14t 72.16% <6.38%> (-0.04%) ⬇️
SelfHosted-Large 29.68% <6.38%> (-0.01%) ⬇️
SelfHosted-Linux 35.94% <85.10%> (+0.01%) ⬆️

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

Files with missing lines Coverage Δ
dimos/msgs/sensor_msgs/PointCloud2.py 62.01% <85.10%> (+2.78%) ⬆️

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