The rails go — and the trees beside them do not move GT class 66 −75.7 %
085 painted GT, guardrail (66): 25 772 → 6 276 · hard FP 46 519 → 26 965 · tp 156 209 → 156 209. Unpaint bands: 0.3–1 m 23 052, 1–2 m 2, > 2 m 0.
ground.cell_m = 1.5) removes 3.0 k hard FP for 4.4 k GT-tree points (ratio 0.68), a noise-trim drops 135 k of 156 k GT-tree, p20/p35/p50 are worse still. Lowering the hard-cell point floor 300 → 100 captures 2.6 k (5.8 %) because the residual is an mrf failure, not an n failure (nlow p50 = 206, mrf p50 = 0.73); an elongation gate captures 10 points. The one move with a ratio above 1 was unpainting tree points covered by the seg3d guardrail mask: 42.8 % of the residual, zero GT-tree.lost_tree_bands {} empty. Everything unpainted sits at 0.3–1 m (23 052 pts) plus 2 points in 1–2 m; above 2 m: 0. Both truth crowns and both shrub banks visually identical.n_points and position is bit-identical to r8. The hook edits paint only, never cluster membership.guardrail_mask_unpaint_classes = ["w_beam"] plus mask erosion, not turning the knob off.w_beam + guardrail_support, no guardrail_top_rail); the road is not in the mask at all. Both sit inside the same two mega-instances that hold every true positive — that is instance splitting, which is seg3d’s domain.masks.py, so road and vehicle rows we paint tree still leak through the fuse. Adding tree to PAINT_FIRST_CLASSES is a one-line change that would catch the remaining road class.15fd05a), and the run now warns when the mask’s record names match none of the segment’s run3 records instead of silently no-op’ing (ab85cfc).Each comparison render is one spot in the raw no-scan-angle-filter cloud, left to right: true RGB | number_of_returns (grey = 1, orange = 2, red = ≥ 3) | r8 (the BEFORE reference) | r9. The BEFORE column always comes first; top row oblique, bottom row the same points top-down; each column is labelled in the image itself. The last two figures are pass-8 diagnosis panels reproduced for context, captioned individually.
unmaskedtreemedium_vegetationsign-ish (sign / pole / delineator)guardrailother
| feature | knob / commit | verdict | reason (one line) |
|---|---|---|---|
| guardrail-mask unpaint | tree_detection.guardrail_mask_unpaint_enabled false → true (3e0d04f) | keep — ship ON with masks | 085 GT class 66 −19 496 (−75.7 %), hard FP −19 554 (−42 %), 0 GT-tree lost, 0 detections changed. Defaults OFF, so runs without a seg3d guardrail model are unaffected. |
| which mask classes to unpaint | guardrail_mask_unpaint_classes = [] (= all instance_type) | keep as-is | 085 vocabulary is w_beam 1 496 703 + guardrail_support 64 681 pts. Restricting to w_beam is the escape hatch if the hedge cost ever bites. |
| mask source | rail_halfpost.models_dir (already pointed at guardrails_A1B0) | reuse, no new config | mask_file_missing = 0 on all four segments; with no masks the hook is a no-op. |
| sidecar parsing | 15fd05a | review fix | Parse the guardrail sidecar with the shared reader rather than a private path, and guard against desynced canopy extras. |
| silent-mismatch failure mode | ab85cfc | review fix | If the mask’s record_names match none of the segment’s run3 records the run now warns instead of quietly unpainting nothing. |
ground.percentile / ground.cell_m / noise trim | — (diagnosed, rejected) | do not touch | No alternative DEM reaches hard-FP-removed / GT-tree-lost ≥ 1. Least-bad p8 / 1.5 m = 0.68 and barely touches rails; trim loses 135 k of 156 k GT-tree. |
hard_blob_min_points_per_cell 300 → 100 | — (diagnosed, rejected) | do not lower | Residual rails live in dense mixed cells (nlow p50 206, mrf p50 0.73) — they fail mrf, not n. Would capture 2 630 pts = 5.8 %. |
| elongated-join gate (L ≥ 5 m & W ≤ 2 m) | — (diagnosed, rejected) | do not add | New cells 8-join onto the wide mega-instance blobs, so the joined component is never elongated: 10 GT points captured on 085, 0 on 066. |
| recommended new-data config | 2a06eb1 | written | Low band 0.3 m ON, mrf hysteresis 0.0, canopy-column guard as shipped, tcs_ground.per_segment = false, guardrail-mask unpaint ON. |
| mega-instance splitting | — (open, seg3d) | still open | The residual 19 892 road + 6 276 rail sits in the two instances that hold every TP. No paint rule reaches it. |
085 painted GT, guardrail (66): 25 772 → 6 276 · hard FP 46 519 → 26 965 · tp 156 209 → 156 209. Unpaint bands: 0.3–1 m 23 052, 1–2 m 2, > 2 m 0.
085 GT low_veg −2 530 + medium_veg −970 = 3 500 points, 0.6 % of the segment’s veg paint. Also 55 road + 3 c70 points, and 44 points in the 066 1–2 m band (top of a w-beam post cluster, not crown).
lowrej_018_c0_s0, canopy top 13.2 m · r8 and r9 are indistinguishable — full crown, full understorey; only the guardrail line at the bottom edge is lightly thinned. This is the whole GT-tree-loss story: 0 points.M1 hi 0.83264 · M1b mid 0.898 · M2 foliage 0.90608 · M3 9 cells / 28 556 pts (top-cell list cell-for-cell identical) · M4 6 signs / 56 delineators / 93 trees / 0 GHOST · M6 truth spots 12/12, recall 1.000, 0 FP — every one identical to r8.
Residual after r9 on 085: 19 892 road (71) + 6 276 guardrail (66). The road is not in the mask; the rail residual is the part the seg3d guardrail model misses (no guardrail_top_rail class on 085).
| metric | r6pr (pass 7) | r8 (pass 8) | r9 | r9 − r8 |
|---|---|---|---|---|
| tp (GT tree, class 5) | 156 209 | 156 209 | 156 209 | 0 |
| fn | 50 760 | 50 760 | 50 760 | 0 |
| fp (all non-5) | 160 809 | 154 467 | 131 413 | −23 054 |
| definition FP (74 low_veg + 75 medium_veg) | 108 498 | 107 948 | 104 448 | −3 500 |
| hard FP (66 + 71 + 76 + 70) | 52 311 | 46 519 | 26 965 | −19 554 (−42 %) |
| — guardrail (66) | 26 922 | 25 772 | 6 276 | −19 496 (−75.7 %) |
| — road (71) | 20 013 | 19 947 | 19 892 | −55 |
| — c76 barrier | 5 147 | 571 | 571 | 0 |
| — c70 | 229 | 229 | 226 | −3 |
| precision | 0.4927 | 0.5028 | 0.5431 | +0.0403 |
| veg-tolerant precision | 0.7491 | 0.7705 | 0.8528 | +0.0823 |
| recall | 0.7547 | 0.7547 | 0.7547 | 0 |
guardrail mask unpaint block, detections and runtime| segment | instances touched | points unpainted | biggest instance | mask file missing | detections r8 / r9 | det_diff | runtime r8 → r9 |
|---|---|---|---|---|---|---|---|
| 018 | 2 | 11 701 | tree vegetation_rf, 11 387 of 525 437 | 0 | 75 / 75 | 0 rem / 0 add | 255.66 → 242.44 s |
| 060 | 8 | 83 975 | tree vegetation_rf, 20 620 of 252 233 | 0 | 80 / 80 | 0 / 0 | 293.53 → 307.13 s |
| 066 | 8 | 97 797 | tree vegetation_rf, 75 977 of 3 522 476 | 0 | 71 / 71 | 0 / 0 | 388.33 → 414.44 s |
| 085 | 2 | 399 092 | tree vegetation_rf, 220 598 of 6 935 432 | 0 | 9 / 9 | 0 / 0 | 376.49 → 369.72 s |
| DEM height band | r8 | r9 | delta |
|---|---|---|---|
| 0.3–0.5 m | 10 468 | 8 962 | −1 506 |
| 0.5–1 m | 17 417 | 8 296 | −9 121 |
| 1–2 m | 3 801 | 3 757 | −44 |
| 2–4 m | 12 798 | 12 798 | 0 |
| 4–10 m | 31 180 | 31 180 | 0 |
| > 10 m | 64 533 | 64 533 | 0 |
| h < 1 m (the vehicle/marking layer) | 27 885 | 17 258 | −38 %, and −88.1 % vs the r6pr 145 164 baseline |
| metric | base | pass 2–3 | passes 4–7 (r6pr) | pass 8 (r8) | pass 9 (r9) |
|---|---|---|---|---|---|
| precision | 0.4893 | 0.4924 | 0.4927 | 0.5028 | 0.5431 |
| veg-tolerant precision | 0.7413 | 0.7483 | 0.7491 | 0.7705 | 0.8528 |
| hard FP (66+71+76+70) | 54 520 | 52 538 | 52 311 | 46 519 | 26 965 |
| recall | 0.7547 | 0.7547 | 0.7547 | 0.7547 | 0.7547 |
| metric | base | now (r9) | delta |
|---|---|---|---|
| M1 canopy recall > 10 m | 0.0092 | 0.8326 | +0.823 |
| M1b canopy recall 4–10 m | 0.7803 | 0.8980 | +0.118 |
| M2 foliage-cell recall (> 4 m) | 0.6299 | 0.9061 | +0.276 |
| M3 hard contamination (points) | 195 676 | 28 556 | −85 % |
| M6 truth spot recall / FP | 1.000 / 0 | 1.000 / 0 | 0 |
2a06eb1); what remains is the write-up and the PR.return_number export — not needed for now; number_of_returns alone carries the whole multi-return signal this work used.