Skip to content

map_cleanup: phantom orientations — picker scores height, not prominence - #97

Merged
MJohnson459 merged 4 commits into
mainfrom
map-cleanup-peak-prominence
Aug 12, 2026
Merged

map_cleanup: phantom orientations — picker scores height, not prominence#97
MJohnson459 merged 4 commits into
mainfrom
map-cleanup-peak-prominence

Conversation

@MJohnson459

@MJohnson459 MJohnson459 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

map_cleanup: pick wall directions above the broadband floor, not above zero.

Branch map-cleanup-peak-prominence, 3 commits on main: the cherry-picked diagnostics label fix from map-cleanup-diag-labels, the picker change, and a docs-only commit recording how the picker relates to ROSE.

What was wrong

_pick_directions kept any local maximum of the angular energy reaching 0.45 of the global maximum. Clutter puts energy at every orientation, so a real map's curve is a few wall peaks riding on a pedestal measuring ~0.49 of that maximum -- more than half the threshold's range sits below the pedestal, everything clears the gate, and what survives is whatever the 12 deg suppression radius leaves. On the 2026-08-02 flat map that was the 73.8 deg phantom: the shoulder of the real 86.8 deg family, 13.0 deg away, at 0.50 of the max against the weakest real family's 0.64 (1.3x apart). Six of the thirteen real occupancy maps on the box carried such a shoulder, every one 12.0-13.5 deg from a stronger peak.

Energy panel before and after

The flat map's angular energy. Top: the shipped rule, with 74 deg picked off the flank of the real 87 deg family. Bottom: the same curve (dim) with the floor-subtracted residual over it and the threshold across — where the phantom is a flat nothing. Full panel and data.

The change

Threshold the floor-subtracted residual -- the same 45 deg floor angular_stats already subtracted, at the same 0.15. Those two peaks then read 0.012 and 0.296, 24x apart.

  • _pick_peaks is the NMS loop; _pick_directions = floor-subtract + _pick_peaks; _floor_subtract is shared with _residual_spectrum.
  • angular_stats calls _pick_peaks on its already-residual curve, so its behaviour is bit-identical.
  • Params/SpectrumParams.peak_rel_threshold 0.45 -> 0.15. Both halves must travel together, and a test pins that.
  • room_segmentation.dominant_rotation_deg now ranks on the same residual it picked from.
  • The diagnostics energy panel plots both curves with the threshold across them, since one showing only the raw energy cannot explain a rejection.

Relation to ROSE (the third commit)

The module cites ROSE, so it should say where it departs. ROSE selects directions by topographic prominence at 50% of the curve's peak-to-trough range (arXiv:2004.08794 3.1; literally find_peaks(pol_h, prominence=(max-min)*0.5) in aislabunimi/ROSE2). The shipped relative-height rule was never that, and neither is this one -- so the phantom came from a pre-existing deviation from the paper, not from the paper.

Run over the same 13 maps, ROSE's criterion is more conservative than either: exactly the two strongest near-orthogonal directions everywhere, never a phantom -- and never the real off-axis families this pass has to keep (11.8 / 110.2 deg on the flat), which is the two-direction result rejected at --peak-rel 0.65 for visibly eroding walls. That is a difference in the maps rather than a mistake in the paper: ROSE scores large rectilinear floor plans and abstracts one, where this gates observed pixels to real walls on a small flat that has genuine off-axis families.

So prominence is not simply worse -- it has no setting that expresses what is wanted here. The paper's threshold gives two directions; any threshold loose enough to admit the third and fourth admits the shoulder with them, the real off-axis family scoring 0.033 of the maximum against the phantom's 0.028. Looser still and it starts promoting lone bumps in the noise floor with no structural energy at all (47.2 deg on the tuning map, 43.2 / 137.2 deg on the replay maps). A wider suppression radius cannot separate them either: 13.0 deg to the phantom's parent against the real family's 14.5 deg.

Verification

  • Corpus of all 13 genuine ROS occupancy PNGs on the box (tuning input + warehouse, 3 sim world maps, 8 bag-replay map_raw_notraj.png; bag_replay_results/*/*/map.png is a rendered figure, not a map, and was excluded): 7 unchanged -- including the whole tuning set and all three sim worlds -- 6 lost exactly one shoulder each (12.0-13.0 deg from a stronger peak, residual 0.012-0.105 against a weakest-kept 0.204-0.285), none gained a direction.
  • The flat map cleaned with the new defaults is byte-identical to the --peak-rel 0.55 output threaded by hand on 2026-08-02 (0 differing cells of 43 259); the old default differed by 664.
  • Threshold window measured on both sides: 0.10 admits a phantom on sim/mote_world (residual 0.109), 0.20 drops a real family on the tuning map (0.189).
  • segment-eval unchanged at 0 / +17 / -31 deg (30/33 hospital, 10/10 office, 1/1 mote, zero merges) -- identical to a baseline run of unpatched main.
  • 118 tests pass (test_angular_stats, test_room_segmentation, test_sites, test_bundle), including 3 new picker tests; pre-commit clean across the tree.

Measurements and data in docs/tuning/2026-08-11-orientation-picking.md (+ dir: corpus, threshold sweep, ROSE-rule comparison, before/after figure), registered in the mkdocs nav.

Follow-up filed as task 380: the CLI, the diagnostics labels and every saved revision's meta.yaml call these "orientations" when they are wall normals, 90 deg from what angular_stats reports for the same map.

MJohnson459 and others added 4 commits August 11, 2026 21:08
The angle labels were drawn with their baseline at y=22 under the 18 px
title bar that _label() paints over the finished panel, hiding their top
halves; the right-edge reserve was also too narrow for three digits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013o39JoxarofwaPi6kGYxXY
…e zero

The declutter pass kept any local maximum of the angular energy reaching
0.45 of the global maximum. Clutter puts energy at every orientation, so a
real map's curve is a few wall peaks riding on a pedestal measuring ~0.49
of that maximum: more than half the threshold's range sits below the
pedestal, where everything clears the gate and what survives is whatever
the 12 deg suppression radius leaves. On the 2026-08-02 flat map that was
a phantom at 73.8 deg -- the shoulder of the real 86.8 deg family, 13.0 deg
away -- reaching 0.50 against the weakest real family's 0.64. Six of the
thirteen real occupancy maps on the box carried such a shoulder, every one
12.0-13.5 deg from a stronger peak.

Threshold the floor-subtracted residual instead (the same curve and the
same 45 deg floor angular_stats already used, at the same 0.15): those two
peaks read 0.012 and 0.296, 24x apart rather than 1.3x. Both halves have to
move together, and a test pins that -- 0.45 on the residual drops real
families, 0.15 on the raw curve returns four directions on a clean
rectilinear map, two of them flank samples.

Literal topographic prominence was measured first and is worse: the real
off-axis family scores 0.033 against the phantom's 0.028, since it sits on
the dominant family's tail and is a shoulder in the same sense; and
thresholding prominence promotes isolated bumps in the noise floor that
carry no structural energy at all. A wider suppression radius cannot
separate them either (13.0 deg to its parent against the real family's
14.5 deg).

Over every genuine occupancy map on the box: 7 unchanged including the
whole tuning set and all three sim worlds, 6 lost exactly one shoulder
each, none gained a direction. The flat map now cleans byte-identically to
the --peak-rel 0.55 output the operator threaded by hand. Room
segmentation, which picks through the same function, is unchanged on the
sim ladder at 0, +17 and -31 deg.

The diagnostics panel now plots both curves with the threshold across
them, because one showing only the raw energy cannot explain a rejection.

Measurements in docs/tuning/2026-08-11-orientation-picking.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5jicDV5ybEtZfvtwrqWNu
The module cites ROSE, so it should say where it departs from it. ROSE
selects directions by topographic prominence at 50% of the angular curve's
peak-to-trough range (arXiv:2004.08794 3.1; literally
find_peaks(pol_h, prominence=(max-min)*0.5) in aislabunimi/ROSE2). The
shipped relative-height rule was never that, and neither is the
floor-subtracted one replacing it.

Run over the same 13 maps, ROSE's criterion is more conservative than
either: exactly the two strongest near-orthogonal directions everywhere,
never a phantom -- and never the real off-axis families this pass has to
keep, which is the two-direction result rejected at --peak-rel 0.65 for
visibly eroding walls. That is a difference in the maps, not a mistake in
the paper: ROSE scores large rectilinear floor plans and abstracts one,
where this gates observed pixels to real walls on a small flat that has
genuine off-axis families.

Corrects the earlier claim that prominence is simply worse. It is not
worse at the paper's threshold -- it just has no setting that keeps the
third and fourth families while excluding a shoulder, because the real
off-axis family's prominence (0.033 of the maximum) sits below the
phantom's neighbours and beside the phantom's own 0.028.

No behaviour change: docstrings, README, tuning note and the rose-rule.txt
measurement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5jicDV5ybEtZfvtwrqWNu
Both branches added a 2026-08-11 tuning note to the mkdocs nav; keep both,
in filename order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5jicDV5ybEtZfvtwrqWNu
@MJohnson459
MJohnson459 merged commit 5d3805b into main Aug 12, 2026
5 checks passed
@MJohnson459
MJohnson459 deleted the map-cleanup-peak-prominence branch August 12, 2026 11:13
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