Handoff: AI3D-382 tree recall and number_of_returns foliage cue on the no-scan-angle-filter dataset
Summary
Continuation target. Rebuild tree recall and the foliage-vs-hard-object split of the verticalsigns pipeline on the new dataset /mnt/d/a123-data/Abschnitt_1_no_angle_filter_returns/ (battlebox), using the empirical facts below. Nothing in the research is implemented yet; an implementation agent is already working on this branch.
The one fact that reorders everything: the old NPZ filter abs(scan_angle) < 80 was a height ceiling, not a side filter. scan_angle is nadir-referenced (0 = down at the road, |80–90| ≈ horizontal, >90 = looking up). Trees in the old data were cut at 3–8 m and had 7–25× fewer points; the same truth spots read 18–26 m on the new data. Every height-based rule and the vegetation RF were tuned on decapitated crowns. Measure first, then retune; do not port thresholds.
Second fact: return_number is not lost. The source Riegl LAS (on this workstation, not battlebox) carries it and it is internally consistent; only the Step 3 NPZ export dropped it. Re-exporting it is the cheapest change with the largest analytical payoff.
Priority legend: P0 do first, everything else depends on it · P1 expected main wins · P2 second tier, cheap, may surprise · P3 only if time · impl code change · exp experiment / measurement.
Priorities (ordered)
P0 Foundations — do these before touching any threshold
| # | Item | Type | What / why |
|---|---|---|---|
| P0-1 | Export return_number in Step 3 | impl | Source LAS is LAS 1.2 / PDRF 3 (Riegl rdbconvert 1.11.1): return bits (0–2 of the flag byte) are valid, RN ≤ NoR on every point checked. Add a key next to NUMBER_OF_RETURNS_KEY in segmentationtrajectory/.../segment_mapper.py (_record_field_or_zeros). Unlocks pulse-level counting (RN == 1 = one pulse), Li 2018 R_pc, first-return ratio, Hess-style transmissivity; removes the ×1.78 pulse-weighting ambiguity of the NoR-only fraction. |
| P0-2 | Re-measure before re-tuning (report B0) | exp | Run v6 unchanged on the new data, rescore phase0/truth_v4.json by spot-hit, and dump per truth parent: h_max_robust, tile relief, upper_half_surplus, crown_isotropy, tile heights, point counts, NoR shares. Expect the hedge gate (h_top ≤ 3.3) to stop firing on most former "bands" (tops now 15–25 m) and the ~90-point trunks to have 5–20× the points. This dump is the baseline for every P1/P2 decision; without it, threshold changes are guesses. |
| P0-3 | Regenerate the feature corpus on unthinned cluster points | impl | The NoR fraction is pulse-weighted (each echo of an n-echo pulse carries n); whole-pulse filters keep the weighting, per-point thinning or voxel subsampling breaks it. Compute echo features on unthinned points, or on pulses via RN == 1 once P0-1 lands. Note the _row_to_vector KeyError trap on old CSVs — regenerate rather than patch. |
P1 Expected main wins
| # | Item | Type | What / why |
|---|---|---|---|
| P1-1 | Echo features into the vegetation RF (A1–A3) | impl | Add to TREE_FEATURE_NAMES (fractions only, keeps the scale-invariance contract): mr_fraction = share of cluster points with NoR ≥ 2 (≥ 0.3 m AGL); mr3_fraction = share with NoR ≥ 3; mean_nor; per-height-slab mrf for 0.3–1, 1–2, 2–4, 4–8, >8 m plus mr_profile_min/mr_profile_range. Measured on this scanner (6 m truth spots): trees median 0.43 (crowns ≈ 0.55), Miro's confirmed hedge walls 0.09–0.28, clean sign plates 0.11–0.22, delineators 0.15, guardrail half-posts 0.30 (rail-backed hedges inflate it). Foliage interiors are NoR 3–4, foliage edges and hard linear objects NoR 2 — keep NoR = 2 and NoR ≥ 3 shares separate. Never a hard gate: edges, mesh fences and wet surfaces multi-echo; echo-only classifiers confuse 27 % of urban furniture and 39 % of shrubs with trees (Hess 2026). |
| P1-2 | Retrain the vegetation RF, don't recalibrate (B1) | impl exp | Train on full-angle clusters with P1-1 features; segment-grouped CV; report strict and lenient (soft-label) metrics. Add sampling-geometry augmentation: random scan-line dropout and an angle-window crop that reproduces the old |angle| < 80 geometry (verified: scan-line dropout alone recovers +3–5 mIoU cross-sensor and +4 vegetation IoU; a plain 2× density change costs ~2 OA points with column neighbourhoods). Use fixed-radius spherical neighbourhoods for neighbourhood features and expose neighbour count as a reliability feature (B6). |
| P1-3 | Stem-seeded splitting of long DBSCAN chains (B2) | impl | Trunks now exist in the data. In the 1–4 m slice: two-pass DBSCAN + RANSAC circle/line → trunk candidates; trunk confidence from existing eigen features + diameter; assign crown points by min-cut / nearest compatible stem with an allometric crown-width cap (re-fit CD–H locally; Fan 2021's CD = 0.446·H^0.854 is Indo-Malayan). Re-run the RF per split tree so crown_isotropy is per crown, not per belt. Trunks are single-return: low mrf in the 0.3–2 m slab is the trunk cue, high mrf above it the crown cue. Literature expectation on a Riegl-class scanner with a full vertical window: ~100 % roadside stem recall (139/139, Hyyppä 2023); the 67–78 % figures come from a 2007 phase-based Faro at 15 Hz. |
| P1-4 | Hedge gate re-fit on complete canopies (B3) | impl exp | Keep the gate but re-fit every threshold on the P0-2 distributions and add two conjuncts: mr_fraction of the band (hedges ≈ 0.3 vs tree bands ≈ 0.55) and "no stem seed from P1-3". Count stems_per_10m from the detector, not from cluster shape. Bands with a stem seed or a tree-like mrf profile are never gated. Literature hedge/tree line is a top-height band on complete canopies: 1.3–3 m managed hedge / 3–15 m mature hedge or tree canopy (THaW), 6 m (England model) — our 3.3 m is the THaW band, but it was fitted on truncated crowns. |
P2 Second tier — cheap, and the kind of thing that surprises
| # | Item | Type | What / why |
|---|---|---|---|
| P2-1 | Per-point contamination stripper (A4/A5) | impl exp | Neighbourhood multi-return rate (r = 0.5–1 m) per point → cluster median / P10 / share > 0.3. Before sign/guardrail features, drop points with rate > τ (start τ = 0.4) from those candidates; before crown features, drop rate < 0.1 points above 2 m from vegetation candidates (keeps trunks). Fixes the "hedge behind rail / foliage over plate" contamination both ways. Half-post p90 mrf ≈ 0.8 at r = 1 m shows how fast a hard-object neighbourhood is polluted by verge vegetation — this may matter more for guardrail/sign precision than anything in P1. |
| P2-2 | Per-object NoR rates on cluster masks | exp | All spot-level numbers above are 6 m circles, not object masks (AUC tree vs hedge only 0.73 at spot level; on segments 085 + 060 the cue separated tree from hedge perfectly). Recompute mrf / mr3 / mean_nor on v6 cluster masks for: clean sign plates, W-beam rails, half-posts, mesh fences, noise walls, confirmed hedges, trees. This is the table the literature does not have for any scanner and it decides where the RF will actually cut. Script to extend: docs/research/deep-research-20260905-mr_stats.py (copy of battlebox:/home/ai/veg373_work/recall/ret_stats/mr_stats.py). |
| P2-3 | Thin trunks by linkage, not point mass (B4) | impl | Replace the point-mass floor with: keep a low-mass vertical core if a multi-return-rich crown (mrf ≥ 0.4, ≥ 2 m AGL) sits within 1.5 m above it (Matikainen 2019's rule needs no mass). Also try a vertical-continuity count (number of scan lines intersecting the core) — thin-target point mass is a profiles × points-per-profile property (Cahalane 2015), and the count is more stable than a mass floor. Probably moot after P0-2 shows the trunks are back; verify on the miss list. |
| P2-4 | Intensity features after the angle change | exp | Near-horizontal and upward looks are now present. Raw intensity is incidence-angle dependent above ~15°; an incidence correction moved an ALS land-cover RF from 44 → 74 % OA (amplitude). Re-check hi_intensity_fraction and sign retro-intensity gates on the new distributions; consider Riegl relative reflectance if the RDB export can provide it. Do not filter by angle. |
| P2-5 | Airborne noise at |angle| > 110 | exp | Look at the sky/dust population at high angles (isolated low-intensity points) and zbin_count_cv; if present, drop by isolation + intensity floor before clustering (Matikainen used an intensity floor). Never by angle. |
| P2-6 | Edge-direction histogram (A6) | impl | Histogram of local principal-direction angles (eigenvectors already computed) → shares of horizontal / vertical / oblique; signs, rails and fences peak at 0°/90°. Cheap; effectiveness unverified. |
| P2-7 | Re-ask the soft labels only after P0-2 | exp | 021 c0, 068 c1, 121 c5, 108 c34, 066 c8, 094 c1, 066 c15 were judged on decapitated crowns ("trunks are often missing"). They may become hard labels on complete canopies. Ask Miro once, with the new renders, after the v6 rerun. |
P3 Only if time
- Spacing prior for planted avenues: when a row is regular, tree count = row length ÷ spacing minus observed gaps (orchard literature); a DBH-free splitter for avenues where stems are occluded by a guardrail.
- Scanner model: the
rdbconvertheader does not name the head (VUX-1HA22 vs VMX-2HA). Ask the survey provider; it fixes the echo-separation figure (~0.5 m) and the max targets per pulse (15/15/9/7/5/4 at 300–1800 kHz). - Field QA:
lasreturn -check_return_numberingon one full LAS; confirm no synthetic-return flag (Global Encoding was 1 = GPS time type only).
Findings the priorities rest on
Empirical (this scanner, this dataset)
- Multi-return share by raw scan angle (20 segments, points ≥ 0.5 m AGL): 0.00 for |angle| < 50, 0.03–0.06 at 60–70, 0.25–0.39 at 70–90, 0.39–0.46 for |angle| ≥ 90. The old filter kept the single-return world.
- Segment 085: everything ≥ 2 m above ground is at |angle| ≥ 80; points 8–15 m up sit at |angle| ≈ 100–125.
- NoR mix of the handoff sample NPZ (vegetated lane segment): 73 / 24 / 2 / 0.3 %. First 2 M records of the raw LAS Record011 (road-dominated): 91.8 / 6.6 / 1.4 / 0.17 %. Per-segment NoR mix is itself a coarse vegetation indicator.
- Source LAS:
/mnt/data/dev/3dai.lanefinder/data/00_external/251017_Color_Abschnitt_1_long/25-9166A1Record011.las→ symlink to/mnt/d/dev/3dai.lanefinder/data/00_external/251017_Color_Abschnitt_1/(Record011…019). LAS 1.2, PDRF 3, 40-byte records, 49.4 M points,rdbconvert1.11.1-50+dev+d295d19. Scan Angle Rank int8 −130..+125 → Riegl scanner-frame angle written into the byte; the nadir reading is empirical, sign/offset unverified.
Verified literature (adversarial 3-vote panels; report §4 and §7)
- MLS thresholds for NoR-only features exist but are far below this scanner's foliage: Matikainen 2019 (Riegl VUX-1HA, leaf-off) tree if > 5 % multi-return points and top ≥ 2 m; Liu 2023 (Velodyne HDL-32E) neighbourhood rate > 0.3 in r = 1 m. Use the shape of the rules, not the numbers.
- Hard objects do multi-echo (Li 2018, 3-0: road-furniture edges; Ouster docs: chain-link fences). Riegl-class echo separation ≈ 0.5 m along the beam, so a plate/pole/rail splits only against a far background; foliage splits routinely and reaches NoR 3–4 because it is deep.
- Vertical FOV is a height ceiling (Hyyppä 2023, 2-1): −25..+15° scanner → usable density to ~5 m, stems to 3.3 m vs 6.5 m, crowns unmodellable. Same authors: VUX-1HA on a tilted 360° line found 139/139 roadside stems (3-0).
- Sampling-geometry shift is recovered by retraining with augmentation (Kim 2023 CVPR, 3-0), not recalibration; density halving costs ~2 OA points for an RF (Mohamed 2021, 3-0).
- Hedge vs tree row: national ALS workflows decide by top-height band and area only (THaW 3-0; England model 2-1), never by crown shape or stems. Where stems are invisible, the honest output is "band, ambiguous".
- Geometry alone on a Riegl VMX gets sign F1 0.90 / fence 0.86 / vegetation 0.97 with height histograms and OBB features (Voelsen 2021) — the baseline the echo features must beat.
Refuted — do not build on these
- "Solid surfaces give one return" as a law; any fixed literature threshold (0.05, 0.3, 0.078, 0.761, "10–50 % band") as a gate.
- "Scan angles > 90 imply LAS point format 6+" — our file is PDRF 3 with int8 values beyond ±90.
- "Density, not algorithm, explains stem misses" (Li 2018 raised the same site 69.7 → 80 % by algorithm alone).
- Small footprint ⇒ multi-returns must be edges; pulse width ⇒ echo splitting; fences/walls "multi-echo like vegetation" (all 0-3 as inferences; the quoted facts are verbatim).
Current state and key artifacts
- Research report (read §0, §2, §3, §5, §6, §7.6 first):
docs/research/deep-research-20260905-multireturn-tree-recall.md, commits1721162andd3f9a34ont3code/improve-tree-foliage-recall(worktree/home/miro/.t3/worktrees/3dai.iolabs.pointcloud.verticalsigns/t3code-25aa3ef7). - Per-claim verification audit:
docs/research/deep-research-20260905-run2-verification.json(95 claims: 56 confirmed, 38 refuted, 1 unverified;fact_verbatimmarks "fact stands, inference killed"). - Empirical script:
docs/research/deep-research-20260905-mr_stats.py; on battlebox run withuv run pythonfrom/home/ai/dev/wt-vs-recall($HOME/.local/binon PATH; system python3 has no numpy). Output/home/ai/veg373_work/recall/ret_stats/mr_stats.json. - Handoff that started this:
battlebox:/home/ai/veg373_work/recall/HANDOFF_no_angle_filter.md; v6 outputsphase3/out/v6_corpus/segment_NNN/{clusters.csv,tree_clusters.csv,verticalsigns.json,rail_halfposts.csv}; truthphase0/truth_v4.json(x_local/y_local/r_eval in NPZ frame). - Old filter location:
segmentationtrajectory/src/.../segment_mapper.py(np.abs(scan_angle.astype(np.int16)) < angle_limit,NUMBER_OF_RETURNS_KEY,_record_field_or_zeros,apply_angle_limit_rejection_colors). - Vault: project note
Project notes/Point cloud analysis on linear structures/2026-09-05 Multi-return foliage cue and tree recall on full-angle MLS data (AI3D-382).md, reading list2026-09-06 Articles that can help AI3D-382 - …, 25 new reference notes underReference notes/Academic/. - Battlebox access:
ssh battlebox "bash -s" <<'EOF' … EOF; copy files out withssh battlebox "bash -c 'cat …'" > local(scp lands on the Windows side). Hold the ssh session for long jobs.
Verification status of this handoff
- done NoR / angle statistics on 20 segments and 113 truth spots; source LAS header and 2 M-point return/angle check; three verification passes over the literature.
- not run No code in the pipeline has been changed; no v6 rerun on the new data; no per-object mask statistics; no RF retraining. Run-1 findings F1–F11 were not re-verified with the stricter pass-3 panel (abstract-only items: Zhong 2017, Li 2016 — medium confidence).
Experiment protocol (suggested order)
- Baseline dump (P0-2): v6 unchanged on the new data → per-parent table (heights, counts, NoR shares, gate decisions) + truth_v4 recall/FP. Keep this table; every later change is diffed against it.
- Mask statistics (P2-2): mrf / mr3 / mean_nor / slab profile per v6 cluster class. Publish as a table; this replaces the spot-level numbers in the report.
- Feature ablation (P1-1/P1-2): RF with and without echo features, with and without augmentation; segment-grouped CV; strict + lenient metrics. Watch precision on sign / rail classes as much as tree recall.
- Stripper sweep (P2-1): τ ∈ {0.3, 0.4, 0.5} on sign/rail candidates; report plate/rail feature stability and FP change.
- Stem splitter (P1-3): on chains > 15–20 m; per-tree RF re-score; count stems per 10 m for the gate.
- Hedge gate re-fit (P1-4): only after 1–5; compare gate on/off and with/without the mrf and stem conjuncts on Miro's confirmed hedges (002 c0, 018 c0, 060 c5, 060 c12) and the soft-label set.
- Backward compatibility check: run the retrained model on old A1B0 (angle-filtered) NPZs to see whether the augmentation kept it usable there.
Report every step with the same table shape (per-segment recall, FP count, and the specific truth spots gained/lost) so second-tier surprises are visible, not averaged away.
Risks and open questions
- risk Rail-backed hedges and hedges on verges inflate half-post and rail mrf; the stripper (P2-1) is the mitigation, and geometry must stay primary for fence/wall vs hedge.
- risk Sign-plate support is tiny (v6 finds few
signdetections in the truth segments); do not let the RF learn "low mrf = sign" from a handful of plates. - open Sign and offset of the scanner-frame
scan_angle; whether a ±128 wrap occurs. Treat the extremes with suspicion. - open Which Riegl head produced the data; whether the provider can export Riegl reflectance / pulse-deviation attributes (they would be cleaner hard-edge cues than NoR = 2).
- open Leaf-on vs leaf-off: the 5 % MLS rule is leaf-off; our measurements are on one season. Expect the mrf of deciduous hedges to move.
- open Whether Miro's soft labels harden on complete canopies (ask after P0-2, not before).
Suggested skills / tools
battlebox-sshfor every run on the data;zettelkasten-vaultif notes need updating; the deep-research templatedeep-research-codex-v71-opus-grok.js(session scripts dir) if more literature is needed — Grok fetches, Opus + Grok vote, Fable only scopes and synthesizes.