From 43796085d7a773ae7a4fccb33fc3a0b335bea60d Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Sun, 2 Aug 2026 17:18:30 +0100 Subject: [PATCH 1/3] map_cleanup: keep the energy panel's peak labels clear of the title bar 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 Claude-Session: https://claude.ai/code/session_013o39JoxarofwaPi6kGYxXY --- mote_bringup/mote_bringup/map_cleanup/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mote_bringup/mote_bringup/map_cleanup/cli.py b/mote_bringup/mote_bringup/map_cleanup/cli.py index 56a2906..9d47953 100644 --- a/mote_bringup/mote_bringup/map_cleanup/cli.py +++ b/mote_bringup/mote_bringup/map_cleanup/cli.py @@ -74,11 +74,11 @@ def _energy_panel(res: StructureResult, size: tuple[int, int]) -> np.ndarray: cv2.line(img, (x0, y0), (x1, y1), (200, 200, 60), 1, cv2.LINE_AA) for d in res.directions_deg: x = int(d / 180.0 * (w - 1)) - cv2.line(img, (x, 10), (x, h - 20), (80, 255, 80), 1, cv2.LINE_AA) + cv2.line(img, (x, 20), (x, h - 20), (80, 255, 80), 1, cv2.LINE_AA) cv2.putText( img, f"{d:.0f}", - (min(x + 2, w - 22), 22), + (min(x + 2, w - 28), 32), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (80, 255, 80), From 175d70749aeabdc1e6c53e484ab7d44b564cc2d0 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Tue, 11 Aug 2026 21:23:06 +0100 Subject: [PATCH 2/3] map_cleanup: pick wall directions above the broadband floor, not above 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 Claude-Session: https://claude.ai/code/session_01U5jicDV5ybEtZfvtwrqWNu --- docs/tuning/2026-08-11-orientation-picking.md | 106 +++++++++++++ .../2026-08-11-orientation-picking/corpus.txt | 33 +++++ .../energy-panel-before-after.png | Bin 0 -> 120700 bytes .../flat-map-output.txt | 10 ++ .../threshold-sweep.txt | 139 ++++++++++++++++++ mkdocs.yml | 1 + .../mote_bringup/map_cleanup/README.md | 17 ++- .../mote_bringup/map_cleanup/angular_stats.py | 93 ++++++++++-- mote_bringup/mote_bringup/map_cleanup/cli.py | 37 +++-- .../map_cleanup/room_segmentation.py | 14 +- .../map_cleanup/structure_extraction.py | 17 ++- mote_bringup/test/test_angular_stats.py | 103 +++++++++++++ 12 files changed, 533 insertions(+), 37 deletions(-) create mode 100644 docs/tuning/2026-08-11-orientation-picking.md create mode 100644 docs/tuning/2026-08-11-orientation-picking/corpus.txt create mode 100644 docs/tuning/2026-08-11-orientation-picking/energy-panel-before-after.png create mode 100644 docs/tuning/2026-08-11-orientation-picking/flat-map-output.txt create mode 100644 docs/tuning/2026-08-11-orientation-picking/threshold-sweep.txt diff --git a/docs/tuning/2026-08-11-orientation-picking.md b/docs/tuning/2026-08-11-orientation-picking.md new file mode 100644 index 0000000..d68e4f1 --- /dev/null +++ b/docs/tuning/2026-08-11-orientation-picking.md @@ -0,0 +1,106 @@ +# Orientation picking — the phantom wall direction, 2026-08-11 + +**Verdict: the declutter pass picks wall orientations by their height above the +angular energy's broadband floor (`peak_rel_threshold` 0.15 of the residual +maximum), not by their height above zero (0.45 of the raw maximum).** The +phantom direction that forced a hand-tuned threshold on the 2026-08-02 flat map +is gone, and the shipped defaults now reproduce that hand-tuned output exactly. +This doc is the measurement behind the change. Data: +`2026-08-11-orientation-picking/`. + +## The defect + +`_pick_directions` kept any local maximum of the angular energy reaching +`peak_rel_threshold` (0.45) of the global maximum, after suppressing peaks +within 12° of a stronger one. + +Clutter, speckle and ragged wall edges put energy at *every* orientation, so a +real map's angular energy is a modest set of wall peaks riding on a broad +pedestal — **measured at 0.49 of the global maximum** on the 2026-08-02 flat +map. A threshold expressed as a fraction of the raw maximum therefore spends +more than half its range below the pedestal, where nothing is a direction and +everything clears the gate. + +What survives that gate is whatever the 12° suppression radius happens to +leave: on that map, a direction at **73.8°**, which is the shoulder of the real +86.8° wall family 13.0° away — just outside the radius. It reaches 0.50 of the +raw maximum against the weakest real family's 0.64, a ratio of 1.3, and no +threshold placed between two such numbers is a measurement. The operator's +workaround was `--peak-rel 0.55`, threaded between them by hand. + +It is not one map's curiosity. Of the **13 genuine occupancy maps** on this +box, **6 carried such a shoulder**, every one of them 12.0–13.5° from a +stronger peak. + +![before and after](2026-08-11-orientation-picking/energy-panel-before-after.png) + +## Two fixes that do not work + +**Literal topographic prominence** (a peak's height above the higher of its two +flanking minima) does not separate the phantom from the real families. On the +flat map the phantom's prominence is 0.028 of the maximum and the *real* +off-axis family at 11.8° has 0.033 — because that family sits on the tail of +the dominant one and is a shoulder too, in exactly the same sense. Thresholding +prominence is worse than a wash: it rewards isolation, so it promotes lone +bumps in the noise floor that carry no structural energy at all (47.2° on the +tuning map; 43.2° and 137.2° on the replay maps) while dropping real families. + +**A wider suppression radius** cannot separate them either. The phantom is +13.0° from its parent; the real off-axis family is 14.5° from its own. Any +radius that suppresses the one suppresses the other. + +## The fix + +Subtract the broadband floor before thresholding — the curve smoothed over +`FLOOR_HALFWIDTH_DEG` (45°), i.e. what is left once anything with a wall's +angular sharpness is averaged away. This module already computed that residual +for `angular_stats`; the declutter picker was simply not using it. + +On the same two peaks, the residual reads **0.012 for the phantom and 0.296 for +the weakest real family** — 1.3× apart before, 24× apart after. + +The threshold moves with the curve: 0.15 of the residual maximum, the same +number `angular_stats` already used on the same kind of curve. **Both halves +have to travel together.** 0.45 on the residual would drop real families; 0.15 +on the raw curve returns four directions on a clean synthetic rectilinear map, +two of them flank samples. A test pins that combination. + +The threshold's window is measured, and is not wide: + +| bound | map | direction | residual | +| --- | --- | --- | --- | +| from below | `sim/mote_world` | 137.2° phantom, admitted at 0.10 | 0.109 | +| from above | `tuning/input_map` | 16.2°, a real family the shipped rule kept | 0.189 | + +## What changed, over every map on the box + +`corpus.txt` — 13 genuine ROS occupancy PNGs (the tuning input, the three sim +world maps, and eight `map_raw_notraj.png` outputs of the bag-replay harness; +`bag_replay_results/*/*/map.png` is a rendered *figure*, not a map, and is not +in the corpus). + +* **7 maps unchanged**, including the whole tuning set and all three sim worlds. +* **6 maps lost exactly one direction each**, and every one of them was a + shoulder 12.0–13.0° from a stronger peak, with residual 0.012–0.105 against a + weakest-kept of 0.204–0.285. +* **No map gained a direction.** + +## Does it reproduce the hand-tuned map? + +Yes, byte for byte. The 2026-08-02 flat map cleaned with the new defaults is +**identical** to the `--peak-rel 0.55` output the operator hand-threaded and +shipped (0 differing cells of 43 259), and differs from the old default's output +by 664 cells. `flat-map-output.txt`. + +Room segmentation, which picks directions through the same function to find the +map's dominant rotation, is unchanged on the sim ladder — 30/33 hospital, 10/10 +office, 1/1 mote, zero merges, and the same again with the map turned +17° and +−31°. + +## What this does not fix + +The picker still returns *directions*, not families, and `max_directions` (5) is +still a cap rather than a decision: a building genuinely using six wall +directions loses one, silently. Nothing here estimates how many directions a map +*should* have — `angular_stats`'s frame table is the diagnostic that reads the +answer back, and it is a diagnostic, not a gate. diff --git a/docs/tuning/2026-08-11-orientation-picking/corpus.txt b/docs/tuning/2026-08-11-orientation-picking/corpus.txt new file mode 100644 index 0000000..815fd11 --- /dev/null +++ b/docs/tuning/2026-08-11-orientation-picking/corpus.txt @@ -0,0 +1,33 @@ +# Direction sets over every genuine ROS occupancy PNG on the dev box, 2026-08-11. +# "before" reproduces the shipped rule (relative height on the raw angular +# energy, 0.45); "after" is the patched pipeline (relative height on the +# floor-subtracted residual, 0.15). Angles are spectral (wall normals), as the +# declutter CLI prints them. +# +# Corpus: scratchpad_results/map_cleanup/{input_map,warehouse/cleaned}.png, +# mote_simulation/sim_home/sites/*/floors/ground/maps/*/map.png, and +# bag_replay_results/*/*/map_raw_notraj.png. + +map before after directions +-------------------------------------------------------------------------------------------------------------- +tuning/input_map 4 4 UNCHANGED +tuning/warehouse 1 1 UNCHANGED +sim/hospital_world 2 2 UNCHANGED +sim/mote_world 2 2 UNCHANGED +sim/office_world 2 2 UNCHANGED +replay/20260802T142558/r2-chain10 5 4 dropped 73.75 + dropped 73.75: raw h/max 0.501 residual 0.012 raw prominence/max 0.028 gap to stronger peak 13.0 deg (weakest kept residual 0.285) +replay/20260802T162552/r2-chain10 5 5 UNCHANGED +replay/20260802T162659/r2-chain10 5 4 dropped 170.25 + dropped 170.25: raw h/max 0.472 residual 0.070 raw prominence/max 0.014 gap to stronger peak 12.0 deg (weakest kept residual 0.204) +replay/20260802T162758/r2-chain10 5 4 dropped 96.75 + dropped 96.75: raw h/max 0.538 residual 0.105 raw prominence/max 0.000 gap to stronger peak 12.0 deg (weakest kept residual 0.235) +replay/20260802T163028/r2-chain10 5 4 dropped 100.75 + dropped 100.75: raw h/max 0.526 residual 0.028 raw prominence/max 0.000 gap to stronger peak 12.0 deg (weakest kept residual 0.270) +replay/20260802T163104/r2-chain10 5 4 dropped 98.25 + dropped 98.25: raw h/max 0.525 residual 0.104 raw prominence/max 0.000 gap to stronger peak 12.0 deg (weakest kept residual 0.259) +replay/20260802T163141/r2-chain10 4 4 UNCHANGED +replay/20260802T163259/r2-chain10-finang 5 4 dropped 80.25 + dropped 80.25: raw h/max 0.533 residual 0.065 raw prominence/max 0.000 gap to stronger peak 12.0 deg (weakest kept residual 0.279) +-------------------------------------------------------------------------------------------------------------- +7 maps unchanged, 6 changed diff --git a/docs/tuning/2026-08-11-orientation-picking/energy-panel-before-after.png b/docs/tuning/2026-08-11-orientation-picking/energy-panel-before-after.png new file mode 100644 index 0000000000000000000000000000000000000000..7f2394ac9940e3c3bcf77195b7024426ca46f9e6 GIT binary patch literal 120700 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

=G?_~)LHlRm#` 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 literal 0 HcmV?d00001 diff --git a/docs/tuning/2026-08-11-orientation-picking/flat-map-output.txt b/docs/tuning/2026-08-11-orientation-picking/flat-map-output.txt new file mode 100644 index 0000000..2e84f7a --- /dev/null +++ b/docs/tuning/2026-08-11-orientation-picking/flat-map-output.txt @@ -0,0 +1,10 @@ +# The 2026-08-02 flat map through the new defaults, against the outputs cleaned +# on 2026-08-02. `cleaned` is the old default (--peak-rel 0.45); `cleaned-rel*` +# are the hand-threaded runs. 0.55 is the one the operator shipped. + +new default directions: [11.75, 86.75, 110.25, 177.25] +occupied: 4474 -> 3245 + vs cleaned differing cells: 664 (1.535% of the grid) + vs cleaned-rel0.5 differing cells: 664 (1.535% of the grid) + vs cleaned-rel0.55 differing cells: 0 (0.000% of the grid) IDENTICAL + vs cleaned-rel0.65 differing cells: 1321 (3.054% of the grid) diff --git a/docs/tuning/2026-08-11-orientation-picking/threshold-sweep.txt b/docs/tuning/2026-08-11-orientation-picking/threshold-sweep.txt new file mode 100644 index 0000000..c2b1e27 --- /dev/null +++ b/docs/tuning/2026-08-11-orientation-picking/threshold-sweep.txt @@ -0,0 +1,139 @@ +# Threshold sweep behind peak_rel_threshold = 0.15, 2026-08-11. Same corpus as +# corpus.txt. Schemes, all with the shipped 12 deg suppression radius and a cap +# of 5 directions: +# raw @t : threshold t * max on the raw angular energy (what shipped) +# res @t : threshold t * max on the floor-subtracted residual (what ships now) +# prom@t : threshold t * max on topographic prominence of the raw curve +# prom@ is the rejected candidate: note the directions it invents (47.2 on the +# tuning map, 43.2 / 137.2 on the replay maps) and the real families it drops. + + +== tuning/input_map + raw @0.45 (ships): 16.25 88.25 114.75 179.75 + res @0.10 : 16.25 88.25 114.75 179.75 + res @0.15 : 16.25 88.25 114.75 179.75 + res @0.20 : 88.25 114.75 179.75 + res @0.25 : 88.25 114.75 179.75 + prom@0.03 : 16.25 47.25 88.25 114.75 179.75 + prom@0.05 : 16.25 47.25 88.25 114.75 179.75 + prom@0.08 : 16.25 47.25 88.25 114.75 179.75 + +== tuning/warehouse + raw @0.45 (ships): 179.75 + res @0.10 : 179.75 + res @0.15 : 179.75 + res @0.20 : 179.75 + res @0.25 : 179.75 + prom@0.03 : 179.75 + prom@0.05 : 179.75 + prom@0.08 : 179.75 + +== sim/hospital_world + raw @0.45 (ships): 0.25 90.25 + res @0.10 : 0.25 90.25 + res @0.15 : 0.25 90.25 + res @0.20 : 0.25 90.25 + res @0.25 : 0.25 90.25 + prom@0.03 : 0.25 90.25 + prom@0.05 : 0.25 90.25 + prom@0.08 : 0.25 90.25 + +== sim/mote_world + raw @0.45 (ships): 0.25 90.25 + res @0.10 : 0.25 90.25 137.25 + res @0.15 : 0.25 90.25 + res @0.20 : 0.25 90.25 + res @0.25 : 0.25 90.25 + prom@0.03 : 0.25 47.25 69.75 90.25 137.25 + prom@0.05 : 0.25 47.25 69.75 90.25 137.25 + prom@0.08 : 0.25 47.25 90.25 137.25 + +== sim/office_world + raw @0.45 (ships): 90.25 179.25 + res @0.10 : 90.25 179.25 + res @0.15 : 90.25 179.25 + res @0.20 : 90.25 179.25 + res @0.25 : 90.25 179.25 + prom@0.03 : 90.25 179.25 + prom@0.05 : 90.25 179.25 + prom@0.08 : 90.25 179.25 + +== replay/20260802T142558/r2-chain10 + raw @0.45 (ships): 11.75 73.75 86.75 110.25 177.25 + res @0.10 : 11.75 86.75 110.25 177.25 + res @0.15 : 11.75 86.75 110.25 177.25 + res @0.20 : 11.75 86.75 110.25 177.25 + res @0.25 : 11.75 86.75 110.25 177.25 + prom@0.03 : 47.25 86.75 110.25 137.25 177.25 + prom@0.05 : 47.25 86.75 110.25 137.25 177.25 + prom@0.08 : 47.25 86.75 110.25 137.25 177.25 + +== replay/20260802T162552/r2-chain10 + raw @0.45 (ships): 7.25 80.25 92.25 104.75 174.75 + res @0.10 : 7.25 80.25 92.25 104.75 174.75 + res @0.15 : 7.25 80.25 92.25 104.75 174.75 + res @0.20 : 7.25 80.25 92.25 104.75 174.75 + res @0.25 : 7.25 80.25 92.25 104.75 174.75 + prom@0.03 : 7.25 80.25 92.25 104.75 174.75 + prom@0.05 : 7.25 80.25 92.25 104.75 174.75 + prom@0.08 : 80.25 92.25 104.75 174.75 + +== replay/20260802T162659/r2-chain10 + raw @0.45 (ships): 2.25 14.75 90.75 114.75 170.25 + res @0.10 : 2.25 14.75 90.75 114.75 + res @0.15 : 2.25 14.75 90.75 114.75 + res @0.20 : 2.25 14.75 90.75 114.75 + res @0.25 : 2.25 90.75 114.75 + prom@0.03 : 2.25 14.75 73.75 90.75 114.75 + prom@0.05 : 2.25 14.75 73.75 90.75 114.75 + prom@0.08 : 2.25 14.75 90.75 114.75 + +== replay/20260802T162758/r2-chain10 + raw @0.45 (ships): 10.75 84.75 96.75 109.25 178.25 + res @0.10 : 10.75 84.75 96.75 109.25 178.25 + res @0.15 : 10.75 84.75 109.25 178.25 + res @0.20 : 10.75 84.75 109.25 178.25 + res @0.25 : 84.75 109.25 178.25 + prom@0.03 : 40.75 65.25 84.75 109.25 178.25 + prom@0.05 : 40.75 65.25 84.75 109.25 178.25 + prom@0.08 : 84.75 109.25 178.25 + +== replay/20260802T163028/r2-chain10 + raw @0.45 (ships): 13.25 88.25 100.75 112.75 179.75 + res @0.10 : 13.25 88.25 112.75 179.75 + res @0.15 : 13.25 88.25 112.75 179.75 + res @0.20 : 13.25 88.25 112.75 179.75 + res @0.25 : 13.25 88.25 112.75 179.75 + prom@0.03 : 13.25 88.25 112.75 151.25 179.75 + prom@0.05 : 13.25 88.25 112.75 151.25 179.75 + prom@0.08 : 13.25 88.25 112.75 179.75 + +== replay/20260802T163104/r2-chain10 + raw @0.45 (ships): 11.25 86.25 98.25 110.75 178.25 + res @0.10 : 11.25 86.25 98.25 110.75 178.25 + res @0.15 : 11.25 86.25 110.75 178.25 + res @0.20 : 11.25 86.25 110.75 178.25 + res @0.25 : 11.25 86.25 110.75 178.25 + prom@0.03 : 48.75 86.25 110.75 139.25 178.25 + prom@0.05 : 48.75 86.25 110.75 139.25 178.25 + prom@0.08 : 86.25 110.75 178.25 + +== replay/20260802T163141/r2-chain10 + raw @0.45 (ships): 2.25 14.75 92.75 114.75 + res @0.10 : 2.25 14.75 92.75 114.75 + res @0.15 : 2.25 14.75 92.75 114.75 + res @0.20 : 2.25 92.75 114.75 + res @0.25 : 2.25 92.75 + prom@0.03 : 2.25 14.75 73.75 92.75 114.75 + prom@0.05 : 2.25 14.75 73.75 92.75 114.75 + prom@0.08 : 2.25 92.75 114.75 + +== replay/20260802T163259/r2-chain10-finang + raw @0.45 (ships): 0.25 14.25 80.25 92.25 113.75 + res @0.10 : 0.25 14.25 92.25 113.75 + res @0.15 : 0.25 14.25 92.25 113.75 + res @0.20 : 0.25 14.25 92.25 113.75 + res @0.25 : 0.25 14.25 92.25 113.75 + prom@0.03 : 0.25 14.25 92.25 113.75 143.25 + prom@0.05 : 0.25 14.25 92.25 113.75 143.25 + prom@0.08 : 0.25 14.25 92.25 113.75 diff --git a/mkdocs.yml b/mkdocs.yml index 3333925..3689c65 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -155,3 +155,4 @@ nav: - " ↳ gated benchmark": tuning/2026-07-28-icp-velocity-gate/bench-gated.md - Slip detection: tuning/2026-07-28-slip-detection.md - Camera layer decay: tuning/2026-07-29-camera-layer-decay.md + - Orientation picking: tuning/2026-08-11-orientation-picking.md diff --git a/mote_bringup/mote_bringup/map_cleanup/README.md b/mote_bringup/mote_bringup/map_cleanup/README.md index 11bfb6c..89ab8af 100644 --- a/mote_bringup/mote_bringup/map_cleanup/README.md +++ b/mote_bringup/mote_bringup/map_cleanup/README.md @@ -18,7 +18,9 @@ orientations; clutter and speckle smear energy across *all* orientations. So: 1. binarise the map into a wall image, 2. take its 2D FFT and measure spectral energy as a function of angle, -3. pick the dominant orientations (peaks of that angular energy), +3. pick the dominant orientations — peaks of that angular energy measured + *above its broadband floor*, since the same clutter that smears energy + everywhere lifts every orientation at once, 4. keep only the frequency wedges aligned with those orientations — a directional band-pass — and invert the FFT to get a continuous structure score, @@ -33,7 +35,7 @@ corridors. ## Usage ```bash -pixi run clean-map path/to/map.png [--out DIR] [--wedge 5] [--peak-rel 0.45] [--gate 2] +pixi run clean-map path/to/map.png [--out DIR] [--wedge 5] [--peak-rel 0.15] [--gate 2] ``` Writes `_cleaned.png` (a ROS occupancy PNG) and `_diagnostics.png` @@ -126,6 +128,17 @@ off-axis walls) and `peak_rel_threshold` (higher = fewer orientations kept). Direction detection sharpens considerably on full-resolution maps — the current validation input is a low-res screenshot, which inflates spectral side-lobes. +`peak_rel_threshold` (0.15) is a fraction of the strongest peak **measured above +the angular energy's broadband floor**, not of the raw curve's maximum, and the +two are not interchangeable: on a real map the floor is around half the maximum, +so a fraction of the raw height spends most of its range on clutter and admits +the *shoulder* of a real wall family as a direction of its own. Both halves of +that change have to travel together — the old 0.45 on the residual would drop +real families, and 0.15 on the raw curve accepts nearly anything. Why literal +topographic prominence is not the answer either is in +`angular_stats._pick_directions`; the measurements are in +`docs/tuning/2026-08-11-orientation-picking.md`. + `RoomParams` in `room_segmentation.py` governs the segmentation. `door_max_m` (1.4) is the width that still counts as a doorway rather than an opening, and `min_wall_run_m` (1.5) the shortest unbroken wall allowed to define a room — diff --git a/mote_bringup/mote_bringup/map_cleanup/angular_stats.py b/mote_bringup/mote_bringup/map_cleanup/angular_stats.py index 3b67795..3146ebb 100644 --- a/mote_bringup/mote_bringup/map_cleanup/angular_stats.py +++ b/mote_bringup/mote_bringup/map_cleanup/angular_stats.py @@ -77,6 +77,8 @@ "refine_peak", "_angular_energy", "_smooth_circular", + "_floor_subtract", + "_pick_peaks", "_pick_directions", "_angdist", ] @@ -96,7 +98,7 @@ class SpectrumParams: angle_step_deg: float = 0.5 lowcut_frac: float = 0.02 - peak_rel_threshold: float = 0.45 + peak_rel_threshold: float = 0.15 peak_nms_deg: float = 12.0 max_directions: int = 5 wedge_halfwidth_deg: float = 5.0 @@ -109,12 +111,14 @@ class SpectrumParams: FLOOR_HALFWIDTH_DEG = 45.0 # Relative peak threshold used when picking the direction families the stats are -# reported against. Deliberately *not* ``Params.peak_rel_threshold`` (0.45), -# which selects wedges to keep in the declutter reconstruction — a different job -# where being conservative is right. At 0.45, picking on the floor-subtracted -# residual drops a whole family on some rotations of an unchanged map and its -# energy lands in ``unassigned_energy_frac`` (measured 0.099 -> 0.181 -> 0.163 -# across 0/+17/-31 deg). At 0.15 the same map gives 0.099 / 0.071 / 0.084. +# reported against. It is the same 0.15 as ``Params.peak_rel_threshold`` because +# both now threshold the same *kind* of curve — a floor-subtracted residual, in +# which a family's height is its structural energy rather than its structural +# energy plus a broadband pedestal (see :func:`_pick_directions`). Measured here +# first: at 0.45 picking on the residual drops a whole family on some rotations +# of an unchanged map and its energy lands in ``unassigned_energy_frac`` +# (0.099 -> 0.181 -> 0.163 across 0/+17/-31 deg); at 0.15 the same map gives +# 0.099 / 0.071 / 0.084. STATS_PEAK_REL_THRESHOLD = 0.15 # At most this many direction families. ``Params.max_directions`` is 5, which @@ -206,15 +210,32 @@ def _smooth_circular(x: np.ndarray, k: int) -> np.ndarray: return np.convolve(padded, kernel, mode="valid") -def _pick_directions( - angles: np.ndarray, energy: np.ndarray, params: SpectrumParams +def _floor_subtract(energy: np.ndarray, params: SpectrumParams) -> np.ndarray: + """Angular energy with its broadband floor removed. + + The floor is the curve smoothed over :data:`FLOOR_HALFWIDTH_DEG`, i.e. what + is left of the energy once anything with a wall's angular sharpness is + averaged away. Subtracting it turns "how tall is this peak" into "how much + energy does this peak carry *above the background*", which is the quantity + every consumer here actually means. + """ + k = max(1, int(round(FLOOR_HALFWIDTH_DEG / params.angle_step_deg))) + return np.clip(energy - _smooth_circular(energy, k), 0.0, None) + + +def _pick_peaks( + angles: np.ndarray, curve: np.ndarray, params: SpectrumParams ) -> list[float]: - """Non-max-suppress the angular energy into a small set of orientations.""" - thresh = params.peak_rel_threshold * float(energy.max()) - order = np.argsort(-energy) + """Non-max-suppress a curve into a small set of orientations. + + ``curve`` must already be floor-subtracted — see :func:`_pick_directions`, + which is the entry point for a caller holding raw angular energy. + """ + thresh = params.peak_rel_threshold * float(curve.max()) + order = np.argsort(-curve) chosen: list[float] = [] for idx in order: - if energy[idx] < thresh: + if curve[idx] < thresh: break a = float(angles[idx]) if all(_angdist(a, c) >= params.peak_nms_deg for c in chosen): @@ -224,6 +245,45 @@ def _pick_directions( return sorted(chosen) +def _pick_directions( + angles: np.ndarray, energy: np.ndarray, params: SpectrumParams +) -> list[float]: + """Dominant orientations of a raw angular-energy curve. + + **The threshold is on the floor-subtracted residual, not on the raw + height**, and that is the whole content of this function. Clutter, speckle + and ragged edges put energy at *every* orientation, so a real map's angular + energy is a modest set of wall peaks riding on a broad pedestal — measured + at ~0.49 of the global maximum on the 2026-08-02 flat map. A relative-height + test therefore spends half its range on the pedestal: the weakest real + family there stands at 0.64 of the maximum and a phantom at 0.50, a ratio of + 1.3, and no threshold placed between two such numbers is a measurement. + Subtract the floor and the same two are 0.296 and 0.012 — a ratio of 24. + + The phantom is worth describing because it is the common case rather than a + curiosity: it is the *shoulder* of a real family, one NMS radius away + (13.0 deg from its parent at a 12 deg radius), so it survives suppression + and inherits most of its parent's flank height. Six of the thirteen real + occupancy maps on hand carried one, every one of them at 12.0-13.5 deg from + a stronger peak and with essentially zero prominence. + + Two things were tried first and are worse, both measured on that corpus: + + * **Literal topographic prominence** (height above the higher flanking + minimum) does not separate them. On the flat map the phantom's is 0.028 of + the maximum and the *real* off-axis family's is 0.033 — because that + family sits on the tail of the dominant one and is a shoulder too, in + exactly the same sense. Worse, prominence rewards isolation, so + thresholding it promotes lone bumps in the noise floor that carry no + structural energy at all (47.2 deg on the tuning map, 43.2 and 137.2 deg + on the replay maps) while dropping real families. + * **A wider NMS radius** cannot separate them either: the phantom is 13.0 + deg from its parent and the real off-axis family is 14.5 deg from its own, + so any radius that suppresses the one suppresses the other. + """ + return _pick_peaks(angles, _floor_subtract(energy, params), params) + + def _angdist(a: float, b: float) -> float: """Smallest distance between two orientations on the 180-deg circle.""" d = abs(a - b) % 180.0 @@ -308,8 +368,7 @@ def _residual_spectrum( signal = signal * np.hanning(h)[:, None] * np.hanning(w)[None, :] mag = np.abs(np.fft.fftshift(np.fft.fft2(signal))) angles, energy = _angular_energy(mag, params) - k = max(1, int(round(FLOOR_HALFWIDTH_DEG / params.angle_step_deg))) - residual = np.clip(energy - _smooth_circular(energy, k), 0.0, None) + residual = _floor_subtract(energy, params) total = float(residual.sum()) q = residual / total if total > 0 else residual return angles, energy, q @@ -640,7 +699,9 @@ def angular_stats( max_directions=max_directions, wedge_halfwidth_deg=params.wedge_halfwidth_deg, ) - dirs = _pick_directions(angles, q, pick) + # ``q`` is already the floor-subtracted residual, so this is the low-level + # picker rather than :func:`_pick_directions`, which would subtract twice. + dirs = _pick_peaks(angles, q, pick) directions = _directions_table(angles, q, params, dirs) frames, dominant_share = _frames_table(directions, frame_merge_deg) diff --git a/mote_bringup/mote_bringup/map_cleanup/cli.py b/mote_bringup/mote_bringup/map_cleanup/cli.py index 9d47953..b3c7d77 100644 --- a/mote_bringup/mote_bringup/map_cleanup/cli.py +++ b/mote_bringup/mote_bringup/map_cleanup/cli.py @@ -61,17 +61,34 @@ def _spectrum_panel(res: StructureResult, size: tuple[int, int]) -> np.ndarray: def _energy_panel(res: StructureResult, size: tuple[int, int]) -> np.ndarray: - """Angular-energy curve g(theta) with detected peaks marked.""" + """Angular-energy curve g(theta) with detected peaks marked. + + Both curves are drawn: the raw energy dim, and the floor-subtracted + residual — the one the picker thresholds — bright, with the threshold + across it. A panel showing only the raw curve cannot explain a rejection, + since a phantom direction is a visible bump there and a flat nothing here. + """ h, w = size img = np.full((h, w, 3), 30, np.uint8) - e = res.energy / (res.energy.max() + 1e-9) - n = len(e) - for i in range(n - 1): - x0 = int(i / n * (w - 1)) - x1 = int((i + 1) / n * (w - 1)) - y0 = int(h - 20 - e[i] * (h - 30)) - y1 = int(h - 20 - e[i + 1] * (h - 30)) - cv2.line(img, (x0, y0), (x1, y1), (200, 200, 60), 1, cv2.LINE_AA) + + def plot(curve, colour): + y = curve / (curve.max() + 1e-9) + n = len(y) + for i in range(n - 1): + cv2.line( + img, + (int(i / n * (w - 1)), int(h - 20 - y[i] * (h - 50))), + (int((i + 1) / n * (w - 1)), int(h - 20 - y[i + 1] * (h - 50))), + colour, + 1, + cv2.LINE_AA, + ) + + plot(res.energy, (90, 90, 40)) + if res.residual is not None and res.params is not None: + plot(res.residual, (200, 200, 60)) + y = int(h - 20 - res.params.peak_rel_threshold * (h - 50)) + cv2.line(img, (0, y), (w - 1, y), (90, 90, 220), 1) for d in res.directions_deg: x = int(d / 180.0 * (w - 1)) cv2.line(img, (x, 20), (x, h - 20), (80, 255, 80), 1, cv2.LINE_AA) @@ -87,7 +104,7 @@ def _energy_panel(res: StructureResult, size: tuple[int, int]) -> np.ndarray: ) cv2.putText( img, - "angular energy vs orientation (deg)", + "orientation (deg): raw energy (dim), above-floor residual + threshold", (4, h - 6), cv2.FONT_HERSHEY_SIMPLEX, 0.35, diff --git a/mote_bringup/mote_bringup/map_cleanup/room_segmentation.py b/mote_bringup/mote_bringup/map_cleanup/room_segmentation.py index a2a8888..463b446 100644 --- a/mote_bringup/mote_bringup/map_cleanup/room_segmentation.py +++ b/mote_bringup/mote_bringup/map_cleanup/room_segmentation.py @@ -48,7 +48,8 @@ UNKNOWN, Params, _angular_energy, - _pick_directions, + _floor_subtract, + _pick_peaks, ) @@ -140,13 +141,16 @@ def dominant_rotation_deg(occ: np.ndarray, params: Params | None = None) -> floa square[top : top + wall.shape[0], left : left + wall.shape[1]] = wall mag = np.abs(np.fft.fftshift(np.fft.fft2(square))) angles, energy = _angular_energy(mag, params) - directions = _pick_directions(angles, energy, params) + residual = _floor_subtract(energy, params) + directions = _pick_peaks(angles, residual, params) if not directions: return 0.0 - # Strongest direction first -- _pick_directions returns them sorted by - # angle, so re-rank by the energy at each. + # Strongest direction first -- _pick_peaks returns them sorted by angle, so + # re-rank by the energy at each. Ranking on the same floor-subtracted curve + # the peaks came from: on the raw one the tallest peak is whichever family + # sits highest on the pedestal, which is not the same question. idx = [int(np.argmin(np.abs(angles - d))) for d in directions] - best = directions[int(np.argmax([energy[i] for i in idx]))] + best = directions[int(np.argmax([residual[i] for i in idx]))] rot = best % 90.0 return rot - 90.0 if rot > 45.0 else rot diff --git a/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py b/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py index 9da45a5..a0515ef 100644 --- a/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py +++ b/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py @@ -13,7 +13,8 @@ 1. binarise the occupancy grid into a wall image, 2. take its 2D FFT and measure spectral energy as a function of angle, - 3. pick the dominant orientations (local maxima of that angular energy), + 3. pick the dominant orientations (peaks of that angular energy, measured + above its broadband floor rather than above zero), 4. keep only the frequency wedges around those orientations (a directional band-pass) and invert the FFT to get a continuous "structure score", 5. threshold that score back into a decluttered occupancy grid. @@ -33,7 +34,7 @@ import cv2 import numpy as np -from .angular_stats import _angular_energy, _pick_directions +from .angular_stats import _angular_energy, _floor_subtract, _pick_peaks # ROS map_server occupancy-PNG conventions. FREE = 254 @@ -50,18 +51,23 @@ class StructureResult: spectrum: np.ndarray # float, log magnitude spectrum (fftshifted) angles_deg: np.ndarray # angular-energy sample angles, [0, 180) energy: np.ndarray # angular energy per sample angle + residual: np.ndarray = None # energy with its broadband floor removed directions_deg: list[float] = field(default_factory=list) # detected peaks mask: np.ndarray = None # float, directional band-pass mask (fftshifted) score: np.ndarray = None # float in [0, 1], reconstructed structure score clean_wall: np.ndarray = None # bool, decluttered walls cleaned_map: np.ndarray = None # uint8, ROS occupancy PNG + params: "Params" = None # the settings that produced all of the above @dataclass class Params: angle_step_deg: float = 0.5 # angular resolution of the energy scan lowcut_frac: float = 0.02 # ignore this central disc of the spectrum (DC) - peak_rel_threshold: float = 0.45 # peak must reach this frac of the max + # Peak must reach this frac of the max *of the floor-subtracted residual*, + # which is a different and much wider scale than the raw energy this was + # once compared against — see angular_stats._pick_directions. + peak_rel_threshold: float = 0.15 peak_nms_deg: float = 12.0 # suppress peaks closer than this to a stronger one max_directions: int = 5 # keep at most this many orientations wedge_halfwidth_deg: float = 5.0 # angular half-width kept around each peak @@ -113,7 +119,8 @@ def extract_structure(occ: np.ndarray, params: Params | None = None) -> Structur spectrum_log = np.log1p(mag) angles, energy = _angular_energy(mag, params) - directions = _pick_directions(angles, energy, params) + residual = _floor_subtract(energy, params) + directions = _pick_peaks(angles, residual, params) mask = _directional_mask(signal.shape, directions, params) filtered = fft * mask @@ -162,11 +169,13 @@ def extract_structure(occ: np.ndarray, params: Params | None = None) -> Structur spectrum=spectrum_log, angles_deg=angles, energy=energy, + residual=residual, directions_deg=directions, mask=mask, score=score, clean_wall=clean, cleaned_map=cleaned_map, + params=params, ) diff --git a/mote_bringup/test/test_angular_stats.py b/mote_bringup/test/test_angular_stats.py index 27605bd..2768d3d 100755 --- a/mote_bringup/test/test_angular_stats.py +++ b/mote_bringup/test/test_angular_stats.py @@ -23,6 +23,11 @@ from mote_bringup.map_cleanup.angular_stats import ( # noqa: E402 SpectrumParams, + _angdist, + _angular_energy, + _floor_subtract, + _pick_directions, + _pick_peaks, angular_stats, fold_90, refine_peak, @@ -545,6 +550,104 @@ def test_score_is_invariant_to_incidental_map_extent(): assert abs(a["unassigned_energy_frac"] - b["unassigned_energy_frac"]) < 1e-6 +# -------------------------------------------------------------------------- +# Peak picking: what counts as a wall direction + + +OLD_RULE = SpectrumParams(peak_rel_threshold=0.45) # relative height, raw curve + + +def _declutter_curve(mask): + """The angular energy the declutter pass sees: no crop, no taper.""" + mag = np.abs(np.fft.fftshift(np.fft.fft2(mask.astype(np.float32)))) + return _angular_energy(mag, SpectrumParams()) + + +def _shoulder_curve(): + """A dominant family, a weaker real one, and a bump on the dominant's flank. + + Shaped after the 2026-08-02 flat map, where the phantom sat 13 deg from its + parent — just outside the 12 deg suppression radius — on a broadband + pedestal measuring ~0.49 of the global maximum. + """ + step = SpectrumParams().angle_step_deg + a = np.arange(0, 180, step) + step / 2 + + def gauss(centre, height, sigma): + d = np.abs(a - centre) % 180.0 + return height * np.exp(-0.5 * (np.minimum(d, 180.0 - d) / sigma) ** 2) + + curve = ( + 0.49 + + gauss(90.0, 0.51, 6.0) # dominant wall family + + gauss(20.0, 0.15, 6.0) # real, weaker, off-axis family + + gauss(103.0, 0.06, 2.5) # a bump on the dominant family's flank + ) + return a, curve + + +def test_a_shoulder_of_a_real_family_is_not_a_wall_direction(): + """The defect: a phantom direction 13 deg from its parent, over the gate. + + It is not that the shoulder is tall — it is that on a curve riding a + pedestal, *everything* is tall. Here it reaches 0.60 of the maximum against + the weakest real family's 0.64, so no relative-height threshold separates + them. Above the floor the same two are 0.06 and 0.30. + """ + angles, curve = _shoulder_curve() + + resid = _floor_subtract(curve, SpectrumParams()) + at = lambda c: int(np.argmin(np.abs(angles - c))) # noqa: E731 + assert curve[at(103.0)] / curve.max() > OLD_RULE.peak_rel_threshold + assert resid[at(103.0)] < 0.25 * resid[at(20.0)] + + picked = _pick_directions(angles, curve, SpectrumParams()) + assert [round(d) for d in picked] == [20, 90], picked + + # And what the shipped rule did with it, so the fixture is known to bite. + old = _pick_peaks(angles, curve, OLD_RULE) + assert any(_angdist(d, 103.0) <= 3.0 for d in old), old + + +def test_a_broadband_pedestal_does_not_change_the_picked_directions(): + """Clutter raises every orientation at once; that must not pick directions. + + A map's angular energy is wall peaks riding on whatever speckle, ragged + edges and furniture contribute at *all* orientations. Since the pedestal + carries no orientation, adding one must leave the answer alone — and under + a relative-height rule it does not: the threshold ends up below the + pedestal, every bin clears it, and the picker returns the five + highest bins the suppression radius allows rather than five wall families. + """ + angles, energy = _declutter_curve(pure(11.0)) + walls = _pick_directions(angles, energy, SpectrumParams()) + assert len(walls) == 2, walls + + cluttered = energy + 3.0 * energy.max() + assert _pick_directions(angles, cluttered, SpectrumParams()) == walls + assert len(_pick_peaks(angles, cluttered, OLD_RULE)) == 5 + + +def test_the_declutter_pass_picks_wall_families_not_flanks(): + """End to end through the shipped defaults, on a rectilinear map. + + This is what catches the dangerous half of the change being made alone: + dropping the threshold to 0.15 while still measuring the raw curve returns + four directions on this map, of which two are flank samples. + """ + from mote_bringup.map_cleanup.structure_extraction import ( + FREE, + OCCUPIED, + Params, + extract_structure, + ) + + occ = np.where(pure(11.0), OCCUPIED, FREE).astype(np.uint8) + res = extract_structure(occ, Params()) + assert len(res.directions_deg) == 2, res.directions_deg + assert abs(_angdist(*res.directions_deg) - 90.0) < 2.0, res.directions_deg + + def test_declutter_params_duck_type_in(): """The declutter pass passes its own Params straight through.""" from mote_bringup.map_cleanup.structure_extraction import Params From 487c066e3e1859093ffdb5f314d0feed16810b1f Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Wed, 12 Aug 2026 11:35:39 +0100 Subject: [PATCH 3/3] map_cleanup: record how the direction picker relates to ROSE 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 Claude-Session: https://claude.ai/code/session_01U5jicDV5ybEtZfvtwrqWNu --- docs/tuning/2026-08-11-orientation-picking.md | 46 +++++++++++++++---- .../rose-rule.txt | 28 +++++++++++ .../mote_bringup/map_cleanup/README.md | 9 ++++ .../mote_bringup/map_cleanup/angular_stats.py | 28 ++++++++--- .../map_cleanup/structure_extraction.py | 6 +++ 5 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 docs/tuning/2026-08-11-orientation-picking/rose-rule.txt diff --git a/docs/tuning/2026-08-11-orientation-picking.md b/docs/tuning/2026-08-11-orientation-picking.md index d68e4f1..e2b25ad 100644 --- a/docs/tuning/2026-08-11-orientation-picking.md +++ b/docs/tuning/2026-08-11-orientation-picking.md @@ -34,16 +34,46 @@ stronger peak. ![before and after](2026-08-11-orientation-picking/energy-panel-before-after.png) +## What ROSE itself does, and why this departs from it + +Worth stating plainly, because this module cites ROSE: **the shipped +relative-height rule was never ROSE's rule**, and neither is the one replacing +it. ROSE (Luperto et al., [arXiv:2004.08794](https://arxiv.org/abs/2004.08794), +§3.1) selects directions by **topographic prominence**, thresholded at 50% of +the curve's peak-to-trough range — literally, in the reference implementation +([aislabunimi/ROSE2](https://github.com/aislabunimi/ROSE2), +`src/rose_v1_repo/fft_structure_extraction.py`): + +```python +find_peaks(pol_h, prominence=(np.max(pol_h) - np.min(pol_h)) * 0.5) +``` + +Run over our corpus (`rose-rule.txt`), that criterion is *more* conservative +than either of ours: on all 13 maps it returns exactly the two strongest, +near-orthogonal directions, and never a phantom. It also drops the real +off-axis families along with them — 11.8° and 110.2° on the flat map — which is +the two-direction result the operator 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, +overwhelmingly rectilinear building floor plans, where two directions is the +right answer and a third is usually clutter; and its output is an abstracted +floor plan. A small flat mapped by a 2-D lidar has genuine off-axis wall +families, and this pass keeps *observed pixels* gated to real walls, so a +dropped family costs real wall rather than detail. + ## Two fixes that do not work -**Literal topographic prominence** (a peak's height above the higher of its two -flanking minima) does not separate the phantom from the real families. On the -flat map the phantom's prominence is 0.028 of the maximum and the *real* -off-axis family at 11.8° has 0.033 — because that family sits on the tail of -the dominant one and is a shoulder too, in exactly the same sense. Thresholding -prominence is worse than a wash: it rewards isolation, so it promotes lone -bumps in the noise floor that carry no structural energy at all (47.2° on the -tuning map; 43.2° and 137.2° on the replay maps) while dropping real families. +**Literal topographic prominence**, at a threshold loose enough to keep the four +families, cannot exclude the phantom. The real off-axis family at 11.8° scores +0.033 of the maximum against the phantom's 0.028 — that family sits on the tail +of the dominant one and is a shoulder too, in exactly the same sense. So +prominence has no setting that expresses what is wanted here: the paper's +threshold gives two directions, and any threshold that admits the third and +fourth admits the phantom with them. Thresholding it that low is worse still, +because prominence rewards isolation and starts promoting lone bumps in the +noise floor that carry no structural energy at all (47.2° on the tuning map; +43.2° and 137.2° on the replay maps). **A wider suppression radius** cannot separate them either. The phantom is 13.0° from its parent; the real off-axis family is 14.5° from its own. Any diff --git a/docs/tuning/2026-08-11-orientation-picking/rose-rule.txt b/docs/tuning/2026-08-11-orientation-picking/rose-rule.txt new file mode 100644 index 0000000..3f59ace --- /dev/null +++ b/docs/tuning/2026-08-11-orientation-picking/rose-rule.txt @@ -0,0 +1,28 @@ +# ROSE's own peak criterion over our corpus, 2026-08-11, against this PR's. +# +# ROSE (arXiv:2004.08794 §3.1) selects dominant directions by topographic +# prominence at 50% of the curve's peak-to-trough range; the reference +# implementation (aislabunimi/ROSE2, src/rose_v1_repo/fft_structure_extraction.py) +# is find_peaks(pol_h, prominence=(max(pol_h) - min(pol_h)) * 0.5). Applied here +# to the same angular energy curve our declutter pass measures, with scipy's +# prominence definition transcribed (no scipy in the robot env). +# +# It never admits a phantom, and it never returns more than the two strongest +# near-orthogonal directions -- including on the maps whose real off-axis +# families this pass has to keep. Angles are spectral (wall normals). + +map ROSE (prom >= 0.5*range) this PR +---------------------------------------------------------------------------------------------------- +tuning/input_map 88.2 179.8 16.2 88.2 114.8 179.8 +tuning/warehouse 179.8 179.8 +sim/hospital_world 0.2 90.2 0.2 90.2 +sim/mote_world 0.2 90.2 0.2 90.2 +sim/office_world 90.2 179.2 90.2 179.2 +replay/20260802T142558/r2-chain10 86.8 177.2 11.8 86.8 110.2 177.2 +replay/20260802T162552/r2-chain10 80.2 174.8 7.2 80.2 92.2 104.8 174.8 +replay/20260802T162659/r2-chain10 2.2 90.8 2.2 14.8 90.8 114.8 +replay/20260802T162758/r2-chain10 84.8 178.2 10.8 84.8 109.2 178.2 +replay/20260802T163028/r2-chain10 88.2 179.8 13.2 88.2 112.8 179.8 +replay/20260802T163104/r2-chain10 86.2 178.2 11.2 86.2 110.8 178.2 +replay/20260802T163141/r2-chain10 2.2 92.8 2.2 14.8 92.8 114.8 +replay/20260802T163259/r2-chain10-finang 0.2 92.2 0.2 14.2 92.2 113.8 diff --git a/mote_bringup/mote_bringup/map_cleanup/README.md b/mote_bringup/mote_bringup/map_cleanup/README.md index 89ab8af..0d65dfe 100644 --- a/mote_bringup/mote_bringup/map_cleanup/README.md +++ b/mote_bringup/mote_bringup/map_cleanup/README.md @@ -32,6 +32,15 @@ The building does **not** have to be Manhattan (axis-aligned): the dominant orientations are whatever the map actually contains, including diagonal corridors. +Step 3 is where this **departs from ROSE deliberately**. ROSE picks directions +by topographic prominence at 50% of the angular curve's peak-to-trough range, +which on every map measured here returns exactly the two strongest, +near-orthogonal directions. That is right for the large, overwhelmingly +rectilinear floor plans ROSE scores, and wrong for a small flat mapped by a 2-D +lidar, which has genuine off-axis wall families that a two-direction filter +erodes. See `angular_stats._pick_directions` and +`docs/tuning/2026-08-11-orientation-picking.md`. + ## Usage ```bash diff --git a/mote_bringup/mote_bringup/map_cleanup/angular_stats.py b/mote_bringup/mote_bringup/map_cleanup/angular_stats.py index 3146ebb..2869c5d 100644 --- a/mote_bringup/mote_bringup/map_cleanup/angular_stats.py +++ b/mote_bringup/mote_bringup/map_cleanup/angular_stats.py @@ -270,16 +270,30 @@ def _pick_directions( Two things were tried first and are worse, both measured on that corpus: * **Literal topographic prominence** (height above the higher flanking - minimum) does not separate them. On the flat map the phantom's is 0.028 of - the maximum and the *real* off-axis family's is 0.033 — because that - family sits on the tail of the dominant one and is a shoulder too, in - exactly the same sense. Worse, prominence rewards isolation, so - thresholding it promotes lone bumps in the noise floor that carry no - structural energy at all (47.2 deg on the tuning map, 43.2 and 137.2 deg - on the replay maps) while dropping real families. + minimum) cannot express what is wanted here, at any threshold. It is what + ROSE itself uses — ``find_peaks(prominence=(max - min) * 0.5)`` in the + reference implementation — and at that threshold it is *more* conservative + than this: on all 13 maps it returns exactly the two strongest, + near-orthogonal directions and never a phantom. It also drops the real + off-axis families with them (11.8 and 110.2 deg on the flat map), which is + the two-direction result an operator rejected for visibly eroding walls. + Lower the threshold to keep those and the shoulder comes back with them: + the real off-axis family's prominence is 0.033 of the maximum against the + phantom's 0.028, since it sits on the dominant family's tail and is a + shoulder in exactly the same sense. Prominence also rewards isolation, so + a threshold low enough to admit them promotes lone bumps in the noise + floor carrying no structural energy at all (47.2 deg on the tuning map, + 43.2 and 137.2 deg on the replay maps). * **A wider NMS radius** cannot separate them either: the phantom is 13.0 deg from its parent and the real off-axis family is 14.5 deg from its own, so any radius that suppresses the one suppresses the other. + + So this is a deliberate departure from ROSE, and the reason is the map: ROSE + scores large, overwhelmingly rectilinear building floor plans, where two + directions is the right answer and a third is usually clutter. A small flat + mapped by a 2-D lidar has genuine off-axis wall families, and the declutter + pass keeps *observed pixels* gated to real walls rather than abstracting a + floor plan, so a dropped family costs real wall rather than detail. """ return _pick_peaks(angles, _floor_subtract(energy, params), params) diff --git a/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py b/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py index a0515ef..0f34ad8 100644 --- a/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py +++ b/mote_bringup/mote_bringup/map_cleanup/structure_extraction.py @@ -19,6 +19,12 @@ band-pass) and invert the FFT to get a continuous "structure score", 5. threshold that score back into a decluttered occupancy grid. +Step 3 departs from ROSE on purpose: it picks peaks above the angular energy's +broadband floor, where ROSE thresholds their topographic prominence at half the +curve's range — which returns two directions on every map measured here, and +erodes the off-axis walls a small flat actually has. +:func:`angular_stats._pick_directions` carries the measurements. + Room segmentation (the ROSE2 layer on top of this) is intentionally out of scope for this module.