Skip to content

Fix/no split single tree#49

Closed
tim-devereux wants to merge 4 commits into
csiro-robotics:mainfrom
tim-devereux:fix/no-split-single-tree
Closed

Fix/no split single tree#49
tim-devereux wants to merge 4 commits into
csiro-robotics:mainfrom
tim-devereux:fix/no-split-single-tree

Conversation

@tim-devereux
Copy link
Copy Markdown
Contributor

No description provided.

NMS on the root-density grid was finding multiple peaks within one
tree's crown, causing a single large tree to be output as 8-15
separate trees.

raysegment.cpp:
- Clamp all four grid-index computations to [0, dims-1] to fix
  out-of-bounds Eigen assertions on boundary points (ceil vs floor
  mismatch in grid sizing).
- After NMS, merge all root clusters into one when (a) the canopy
  cells form a single connected BFS region and (b) the canopy
  footprint is consistent with a single tree crown
  (canopy_area / max_height^2 < 1.5). Forests with multiple canopy
  components, or whose combined footprint exceeds the single-tree
  threshold, are left unmerged.

raytrees.cpp:
- At trunk level, pass add_offshoots=false to bifurcate() so that
  when a merged single-tree root cluster has multiple sub-paths, only
  the dominant trunk path is kept and no new tree sections are spawned.
When raysegment merges multiple NMS root clusters into one (single-tree
case), raytrees found multiple spatial sub-clusters at the trunk level.
With add_offshoots=false those sub-clusters were silently dropped,
leaving most of the tree's points unassigned. With the original
add_offshoots=true they spawned new root sections (extra trees).

Fix: in bifurcate(), when par == -1 (trunk level), set the new
section's parent to sec_ (the current trunk) instead of inheriting -1,
and register it in sections_[sec_].children. This makes trunk-level
sub-clusters branches of the same tree rather than new roots, so all
points are retained in one complete reconstruction.

Forest behaviour is unchanged: each forest tree has its own root
cluster, so trunk-level bifurcation never fires for them.
tests/validate_trees.py validates rayextract trees output quality:

  - Tree count (must match --expected-trees)
  - Point retention (segmented output vs input count)
  - Height completeness (mesh zmax >= 85% of cloud zmax)
  - XY spatial coverage (mesh footprint vs cloud footprint)
  - Coverage: fraction of sampled input points within 1/2/4 m of the
    nearest mesh vertex (primary check: >= 70% within 2 m)
  - Phantom branch detection: mesh vertices far from all input points

Usage:
  python3 tests/validate_trees.py \
    --binary build/bin/rayextract \
    --cloud  test_data/split_trees/1.ply \
    --mesh   test_data/split_trees/1_mesh.ply \
    [--no-run]  # skip extraction, check existing outputs

Exit 0 on pass, 1 on failure. Pure Python stdlib, no dependencies.
…ing writes

Patch the LAS 1.2 legacy point count field (offset 107) on disk after closing,
since laszip_close_writer overwrites it when the count was unknown at open time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant