Guardrails greenery filters — pass 2 (green-voted height floor) and final val gate
--set returns_filter_enabled=true --set greenery_floor_enabled=true.
Pass 2 adds a green-voted height floor for w_beam mask rows near the ground. On val segment 085 vs GT-66
(both filters on): P 0.7334→0.7937, R roughly flat (0.6824→0.6818), F1 0.7070→0.7335.
The final gate on the never-tuned val split (003, 022, 023, 087, 093, 094) passed vision judge 12/12, with
caveats: treat 0.15 m as a ceiling on the floor, 022's overgrown median hedge is only partially helped, and
image checks can't rule out sub-pixel bottom-flange loss.
1. What changed (pass 2)
Second default-OFF knob family:
| Knob | Value |
|---|---|
greenery_floor_enabled | false (default) |
greenery_floor_hag_m | 0.15 |
greenery_floor_green_vote | true |
greenery_floor_exg_min | 0.06 |
greenery_floor_green_rate_tau | 0.30 |
greenery_floor_radius_m | 0.5 |
greenery_floor_min_neighbors | 5 |
greenery_floor_instance_types | ["w_beam"] |
Rule: drop a w_beam row when height-above-ground < 0.15 m AND the share of mask neighbours (0.5 m ball
in x, y, hag) with excess-green index exg = (2G−R−B)/(R+G+B) > 0.06 exceeds 0.30.
RGB read once per record; both filters share one KD-tree; sequential stats ("greenery_floor"
block after "returns_filter" in run_summary.json).
No-op guarantees: knob off → byte-identical; RGB absent with green vote on → zero drops plus a warning; a per-segment warning fires when the floor sits at or below the mask replay's own height floor (0.20 m on the non-component path, 0.10 m with component masks).
Code renamed: returns_filter.py → mask_filters.py,
ReturnsRows → MaskRowAttributes.
Adversarial review Opus + Codex: 10 findings, all fixed (default exg 0.03→0.06, lazy tree on no-op paths, chunk/decimation alignment test, in-place exg reduction 32→14 B/pt peak, boundary-strictness tests). 473 unit tests pass.
2. Why exg 0.06 and not the sweep's 0.03
dev/analysis/returns_085/resweep_detector_hag.md: the detector's own ground model is the 8th
percentile of all points per 0.75 m cell, so where no ground return exists (under the beam, grass banks) it
sits above the true surface; mask-row hag is biased low (median −0.010 m, RMSE 0.049 m; w_beam
p1 −0.14 m). The DEM-tuned rule (hag<0.15 & green(exg>0.03)>0.30) costs 2,479 GT-66 rows
on detector hag instead of 360; tightening the vote to exg>0.06 costs 447 (P 0.782, F1 0.702 on the A1B0
mask). An ungated 0.15 m floor costs 3,744 — the colour vote is load-bearing.
| Rule | Dropped | GT-66 lost | GT-74 | GT-71 | P | F1 |
|---|---|---|---|---|---|---|
| returns only | 8,740 | 365 | 2,281 | 180 | 0.7394 | 0.6845 |
| + floor 0.15, exg>0.03 vote | 52,335 | 2,479 | 17,006 | 25,199 | 0.7911 | 0.7052 |
| + floor 0.15, exg>0.06 vote (shipped) | 42,734 | 447 | 15,239 | 19,684 | 0.7817 | 0.7020 |
| + floor 0.15 ungated | 54,249 | 3,744 | 17,304 | 25,498 | 0.7921 | 0.7050 |
| + floor 0.20, exg>0.06 vote | 65,307 | 1,751 | 27,579 | 27,233 | 0.8100 | 0.7130 |
Table basis: detector hag, A1B0 mask, w_beam-scoped, returns rule included in every row.
3. Proof of no-op
On segment 085 versus the pre-change HEAD baseline (41bd9eb):
| Configuration | Result |
|---|---|
| All knobs off | point_masks.npz and guardrails.json identical to baseline |
| Returns filter only | identical to the pass-1 output |
4. Results on 085 (both filters on)
GT-centric vs GT-66, 5 cm join, fresh HEAD baseline:
| baseline | pass 1 | pass 2 | |
|---|---|---|---|
| P | 0.7334 | 0.7530 | 0.7937 |
| R | 0.6824 | 0.6819 | 0.6818 |
| F1 | 0.7070 | 0.7157 | 0.7335 |
| Rows dropped | – | 16,203 | 49,935 |
| GT-66 lost | – | 459 | 518 |
| Terrain / low-veg / bush removed | – | 229 / 3,374 / 10,247 | 20,227 / 15,661 / 10,394 |
Floor increment alone (pass 2 vs pass 1): 33,732 rows dropped for 59 additional GT-66; 19,998 terrain,
12,287 low veg. guardrail_support drops 0. Runtime and peak RSS unchanged (4.28 GB).
5. Train segments
Rows dropped (returns / floor) of total mask rows; guardrail_support drops 0 everywhere.
| Segment | Mask rows | Returns dropped | Floor dropped |
|---|---|---|---|
| 018 | 776,827 | 7,856 | 26,413 |
| 060 | 1,682,718 | 5,611 | 48,652 |
| 066 | 1,989,204 | 9,021 | 54,463 |
Vision judge (Opus, 10 renders of the floor increment, 085×4 + 018/060/066×2): 10/10 correct. Removals form a band on the ground side of the beam, no holes, rail-core pixel counts within ±2% (usually up, because overlapping grass no longer hides the core), post bases intact.
Caveat: 066 spot_01 — a rail end terminal ramping toward the ground loses a few pixels at its foot; worth one targeted check of terminals.
Remaining: above-floor tufts/ferns along the rails; the grass bank in 060 spot_01 that pass 1 mislabelled is only trimmed.
6. Final gate on the val split
003, 022, 023, 087, 093, 094 — never tuned on. Base config vs recommended = both knobs on. No GT available for this split; vision judge only. Instance counts unchanged, runtime/RSS unchanged.
| Segment | Mask rows | Returns dropped | Floor dropped | Total % |
|---|---|---|---|---|
| 003 | 1,054,091 | 8,616 | 2,636 | 1.1% |
| 022 | 1,263,854 | 8,418 | 44,968 | 4.2% |
| 023 | 1,366,816 | 4,418 | 49,377 | 3.9% |
| 087 | 2,335,432 | 1,164 | 68,401 | 3.0% |
| 093 | 1,390,515 | 1,343 | 77,188 | 5.6% |
| 094 | 1,357,474 | 1,771 | 57,091 | 4.3% |
Vision judge (Opus, 12 renders, 2 per segment: largest removal cell + random): 12/12 correct.
PASS WITH CAVEATS
- On 087/093/094 the removed set hugs the beam's lower/back edge as a near-continuous line at the rail toe — correct at 0.15 m, but treat 0.15 m as a ceiling; do not raise the floor.
- 022 under-removes: large bush/hedge masses on an overgrown median rail stay labelled w_beam (the filter helps, does not solve it).
- Image-based; sub-pixel loss of individual bottom-flange points cannot be excluded from renders.
Segment 003 (val)
Segment 022 (val)
Segment 023 (val)
Segment 087 (val)
Segment 093 (val)
Segment 094 (val)
7. Verdict
KEEP both filters. Defaults remain OFF in the shipped config; recommended
production config = --set returns_filter_enabled=true --set greenery_floor_enabled=true.
Flipping the defaults ON is Miro's call (release decision).
8. Not improved / next
- Recall — 36,391 GT-66 points on 085 still unclaimed (the handoff's §5c investigation, untouched here).
- Above-floor greenery attached to the rail (tufts, ferns, the 022 hedge) — single-return and above 0.15 m, neither cue reaches it; a neighbourhood green rate without the height floor was measured at 138 GT-66 lost for 6,784 veg on the DEM sweep and is the next candidate, but must be re-swept on detector hag first.
- guardrail_support purity (terrain/low-veg 53% of that class) is out of scope here.
- The detector ground model's high bias where no ground return exists is a root cause worth its own ticket (SMRF-style DEM would make the floor both cheaper and safer).
- One targeted check of ramped end terminals against the floor.