Guardrails × number_of_returns — pass 1
returns_filter_* drops mask rows whose multi-return
neighbourhood rate is low, targeting phantom w_beam clumps in dense vegetation. On val segment 085 vs GT-66:
P 0.733→0.753, F1 0.707→0.716, R roughly flat (0.682→0.682), all handoff acceptance gates pass,
zero guardrail_support rows dropped anywhere. 10/10 vision-judge renders correct, no rail steel or posts removed.
Known miss: short dense grass is single-return, so it survives (expected, addressed by pass 2's greenery-rate rule).
1. What changed
Default-OFF knob family:
| Knob | Value |
|---|---|
returns_filter_enabled | false (default) |
returns_filter_radius_m | 0.5 |
returns_filter_tau | 0.20 |
returns_filter_min_neighbors | 5 |
returns_filter_instance_types | ["w_beam"] |
Per mask row: share of mask points within a 0.5 m ball in (x, y, height-above-ground) whose pulse gave
2+ returns; drop w_beam rows with rate > 0.20; fewer than 5 neighbours → keep. Runs after
relabel_support_points in both detect.py branches. number_of_returns
read once per record; column absent → whole-segment no-op with a warning. Stats block
"returns_filter" appended to run_summary.json.
Reviewed adversarially by Opus + Codex: 6 findings, 4 fixed, 2 documented. 427 unit tests pass.
2. Proof of no-op
Knob OFF on segments 085 and 060 versus the pre-change baseline at HEAD 41bd9eb:
| Artifact | Result |
|---|---|
point_masks.npz | identical |
guardrails.json | identical |
| XML | differs only in <CreationDate> |
3. Data / split
A1NR (no-scan-angle-filter export with number_of_returns), lane XML run7.
Train segments run: 018, 060, 066. 085 (val, the only human GT) used as the point-level yardstick
per the handoff rule. Test split untouched.
4. Results on 085
GT-centric vs GT class 66, 5 cm join, fresh HEAD baseline:
| baseline | pass 1 | |
|---|---|---|
| P | 0.7334 | 0.7530 |
| R | 0.6824 | 0.6819 |
| F1 | 0.7070 | 0.7157 |
| TP / FP / FN | 78,044 / 28,368 / 36,321 | 77,981 / 25,583 / 36,384 |
Rows dropped: 16,203 (all w_beam; guardrail_support 0). By GT class: medium vegetation (bushes) 10,247, low vegetation 3,374, unmatched 1,894, guardrail 459, terrain 229.
| Acceptance gate (handoff §6) | Threshold | Actual | |
|---|---|---|---|
| GT-66 lost | ≤ 1,000 | 459 | pass |
| Bushes removed | ≥ 4,500 | 10,247 | pass |
| Precision | ≥ 0.740 | 0.753 | pass |
| F1 | ≥ 0.685 | 0.716 | pass |
| Recall | ≥ 0.637 | 0.682 | pass |
| Support rows dropped | 0 | 0 | pass |
| Runtime | ≈ baseline | 1216 s vs 1242 s | pass |
| Peak RSS | ≈ baseline | 4.29 vs 4.28 GB | pass |
Note: the fresh HEAD baseline is not the A1B0 mask the handoff measured (that mask has 1,561,384 rows, R 0.638; ours 1,567,405 rows, R 0.682 — 085 was not in the A1B0 driver's segment list, provenance unknown). All deltas above are against the HEAD baseline.
5. Train segments (no GT)
| Segment | Rows dropped | Mask rows | Supports dropped | Runtime (pass1 vs baseline) |
|---|---|---|---|---|
| 018 | 7,856 | 776,827 | 0 | 1333 s vs 1363 s |
| 060 | 5,611 | 1,682,718 | 0 | 841 s vs 815 s |
| 066 | 9,021 | 1,989,204 | 0 | 1051 s vs 1072 s |
Runtimes unchanged within noise everywhere.
6. Visual check
Independent Opus vision judge, 10 renders (085 ×4 incl. 2 random, 018/060/066 ×2 each): 10/10 correct, zero cases of rail steel or posts removed, no holes in any rail.
Removed = ragged clumpy off-axis blobs on the multi-return band (verge grass, scrub, hedge fringe); 085 spot_02 wiped a 13,869-row pure-vegetation false w_beam block to zero, correctly.
Missed (as predicted): short dense grass is single-return, so the tall grass swallowing the median rail in 060 spot_01 survives (2.4k of 205k removed); low grass strips along median rails in 060/066; guardrail_support blobs 3–6 m inside tree bands in 018 (trunks, single-return).
Render note: oblique panels cramped when tall trees stretch the extent; AFTER legend overlaps the panel corner.
Segment 018 (train) — guardrail_support blobs inside tree bands (single-return trunks) missed
Segment 060 (train) — spot_01 shows the predicted miss: tall grass swallowing the median rail survives
Segment 066 (train)
7. Verdict
KEEP — default stays OFF until the val gate.
8. Offline sweep that decides pass 2
dev/analysis/returns_085/sweep_085.md, on the A1B0 mask:
- A per-point colour gate is catastrophic:
exg > 0.06kills 152,101 rail points. - A neighbourhood green rate is safe.
- The w_beam mask below 0.15 m height-above-ground is 65% terrain + 31% low vegetation + 0.4% rail.
Rule R1 = returns rule OR (hag < 0.15 m AND green rate(exg > 0.03) > 0.30): 34,957 rows removed for 360 rail lost (returns alone: 8,734 for 358); P 0.729→0.769, recall unchanged.
Next: pass 2 implements R1 as greenery_floor_* knobs (in review now), with the floor
threshold re-swept against the detector's own ground model.