pacman::p_load(
"curl",
"dplyr",
"fontawesome", "ForestTools",
"htmltools", "httr2",
"janitor",
"kableExtra", "knitr",
"lidR", "lutz",
"openxlsx",
"PROJ",
"raster", "rasterVis", "reproj", "RCSF", "RColorBrewer",
"sf",
"tinytex", "tmap", "tmaptools", "terra",
"useful", "usethis"
)Stem Detection
2.0 Overview
Individual tree detection (ITD) spatially locates trees and extracts height information from LiDAR point clouds or canopy height models (CHM). Individual tree segmentation (ITS) delineates crown boundaries for detected trees. This chapter demonstrates both approaches using the local maximum filter (lmf) method and compares point cloud-based versus raster-based workflows on the 1 ha Carr Fire clip normalised in Chapter 1.
It is important to include the uniqueness parameter in locate_trees() operations when processing tiled data. This is essential to prevent duplicate tree detection at tile boundaries. Trees detected in overlapping buffer zones can be counted multiple times without this parameter. Always use uniqueness="bitmerge" for tiled workflows.
Note: Tree detection across large catalogs is the most time-intensive stage. Dense canopies and high point densities substantially increase processing time. Expect multi-hour runtimes for operational-scale projects.
Environment Setup
2.1 Point Cloud Method
The local maxima filter (lmf()) algorithm identifies tree tops directly from normalized point clouds1 without rasterization. For each point, the algorithm analyzes neighborhood points within a specified radius to determine if the point represents a local maximum.
Fixed Windows
Constant window size detection is computationally efficient but may over-detect in dense canopies or miss suppressed trees in heterogeneous stands.
- Small windows (
ws=3): High detection rate, increased false positives - Large windows (
ws=11): Detects only dominant trees, omits understory
# Load the normalised Carr 1 ha AOI written in Chapter 1
las_ha <- lidR::readALSLAS("../data/las_1ha.laz")
# Detect tree tops with a fixed 5 m window
ttops <- locate_trees(las_ha, lmf(ws = 5))
# Visualise on a half-metre CHM
chm <- rasterize_canopy(las_ha, res = 0.5, pitfree(subcircle = 0.2))
plot(chm, col = height.colors(50))
plot(st_geometry(ttops), add = TRUE, pch = 3)
# Save outputs
terra::writeRaster(chm, "../data/chm_1m.tif", overwrite = TRUE)
sf::st_write(ttops, "../data/ttops_1ha.gpkg", delete_dsn = TRUE)
Variable Windows
Dynamic window functions adapt search radius to point height, accounting for allometric relationships between tree height and crown diameter. This approach improves detection accuracy in mixed-structure stands.
- Minimum window ≥ 0.5 m to avoid spurious detections
- Threshold heights <2 m handled by the lower bound in
pmax - Functions must return positive values for all input heights
# Window function tuned for the Carr Fire stand (mixed conifer regrowth,
# scattered residual overstory)
wf <- function(x) {
y <- 0.07 * x + 0.6
pmax(y, 0.5)
}
# Visualize window behaviour
heights <- seq(0, 40, 0.5)
window <- wf(heights)
plot(heights, window, type = "l",
xlab = "point elevation (m)",
ylab = "window diameter (m)")
# Detect tree stems
ttops <- locate_trees(las_ha, lmf(wf), uniqueness = "bitmerge")
ttops_sf <- sf::st_as_sf(ttops)
mypalette <- RColorBrewer::brewer.pal(8, "Greens")
plot(chm, col = mypalette, alpha = 0.6)
plot(st_geometry(ttops_sf["treeID"]), add = TRUE, cex = 0.5,
pch = 20, col = "red")

2.2 Rasterized Method
CHM-based detection is faster than point cloud methods due to reduced data volume, but results depend on CHM resolution, interpolation algorithm, and post-processing steps.

2.3 Crown Segmentation
Crown delineation assigns tree IDs to individual points or pixels using detected tree tops as seeds. Boundaries are grown with Dalponte’s algorithm (Dalponte & Coomes, 2016): each tree top seeds a region that expands outward until local minima in the CHM close the crown. The result is convex polygons representing individual crown extents.
Before handing the tree tops to dalponte2016(), renumber them as a plain integer sequence. Uniqueness strategies like "bitmerge" produce 64-bit IDs that the segmentation algorithm cannot accept in a single-tile context.
ttops$treeID <- seq_len(nrow(ttops))
algo <- dalponte2016(chm, ttops)
las_seg <- segment_trees(las_ha, algo)
crowns <- crown_metrics(las_seg, func = .stdtreemetrics,
geom = "convex")
plot(las_seg, bg = "white", size = 2.5, color = "treeID")
plot(sf::st_geometry(crowns))

For a denser, fully-canopied reference, the lidR Megaplot.laz sample yields the familiar “lasso” of coloured crowns below — useful for sanity-checking that your segmentation recovers recognisable tree outlines when the input is tree-rich.


2.4 Validation
Detection accuracy varies by forest structure and parameterisation. The following is a quick sanity check against a nominal field count for the Carr 1 ha AOI.
Runtime Log
devtools::session_info()
## ─ Session info ───────────────────────────────────────────────────
## setting value
## version R version 4.4.1 (2024-06-14)
## os macOS 15.7.5
## system aarch64, darwin20
## ui X11
## language (EN)
## collate en_CA.UTF-8
## ctype en_CA.UTF-8
## tz America/Vancouver
## date 2026-04-23
## pandoc 3.9.0.2 @ /opt/local/bin/ (via rmarkdown)
## quarto 1.7.33 @ /usr/local/bin/quarto
##
## ─ Packages ───────────────────────────────────────────────────────
## ! package * version date (UTC) lib source
## abind 1.4-8 2024-09-12 [1] CRAN (R 4.4.1)
## base64enc 0.1-6 2026-02-02 [1] CRAN (R 4.4.3)
## cachem 1.1.0 2024-05-16 [1] CRAN (R 4.4.1)
## P class 7.3-22 2023-05-03 [?] CRAN (R 4.4.1)
## classInt 0.4-11 2025-01-08 [1] CRAN (R 4.4.1)
## cli 3.6.5 2025-04-23 [1] CRAN (R 4.4.1)
## P codetools 0.2-20 2024-03-31 [?] CRAN (R 4.4.1)
## colorspace 2.1-2 2025-09-22 [1] CRAN (R 4.4.1)
## cols4all 0.10 2025-10-27 [1] CRAN (R 4.4.1)
## crosstalk 1.2.2 2025-08-26 [1] CRAN (R 4.4.1)
## curl * 7.0.0 2025-08-19 [1] CRAN (R 4.4.1)
## data.table 1.18.2.1 2026-01-27 [1] CRAN (R 4.4.3)
## DBI 1.3.0 2026-02-25 [1] CRAN (R 4.4.3)
## deldir 2.0-4 2024-02-28 [1] CRAN (R 4.4.1)
## devtools 2.5.0 2026-03-14 [1] CRAN (R 4.4.3)
## digest 0.6.39 2025-11-19 [1] CRAN (R 4.4.3)
## dplyr * 1.2.0 2026-02-03 [1] CRAN (R 4.4.3)
## e1071 1.7-17 2025-12-18 [1] CRAN (R 4.4.3)
## ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.4.1)
## evaluate 1.0.5 2025-08-27 [1] CRAN (R 4.4.1)
## farver 2.1.2 2024-05-13 [1] CRAN (R 4.4.1)
## fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.4.1)
## fontawesome * 0.5.3 2024-11-16 [1] CRAN (R 4.4.1)
## ForestTools * 1.0.3 2025-02-04 [1] CRAN (R 4.4.1)
## fs 1.6.7 2026-03-06 [1] CRAN (R 4.4.3)
## generics 0.1.4 2025-05-09 [1] CRAN (R 4.4.1)
## ggplot2 * 4.0.2 2026-02-03 [1] CRAN (R 4.4.3)
## glue 1.8.0 2024-09-30 [1] CRAN (R 4.4.1)
## gtable 0.3.6 2024-10-25 [1] CRAN (R 4.4.1)
## hexbin 1.28.5 2024-11-13 [1] CRAN (R 4.4.1)
## htmltools * 0.5.9 2025-12-04 [1] CRAN (R 4.4.3)
## htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.4.0)
## httr2 * 1.2.2 2025-12-08 [1] CRAN (R 4.4.3)
## interp 1.1-6 2024-01-26 [1] CRAN (R 4.4.1)
## janitor * 2.2.1 2024-12-22 [1] CRAN (R 4.4.1)
## jpeg 0.1-11 2025-03-21 [1] CRAN (R 4.4.1)
## jsonlite 2.0.0 2025-03-27 [1] CRAN (R 4.4.1)
## kableExtra * 1.4.0 2024-01-24 [1] CRAN (R 4.4.0)
## P KernSmooth 2.23-24 2024-05-17 [?] CRAN (R 4.4.1)
## knitr * 1.51 2025-12-20 [1] CRAN (R 4.4.3)
## P lattice * 0.22-6 2024-03-20 [?] CRAN (R 4.4.1)
## latticeExtra 0.6-31 2025-09-10 [1] CRAN (R 4.4.1)
## leafem 0.2.5 2025-08-28 [1] CRAN (R 4.4.1)
## leaflegend 1.2.1 2024-05-09 [1] CRAN (R 4.4.0)
## leaflet 2.2.3 2025-09-04 [1] CRAN (R 4.4.1)
## leafsync 0.1.0 2019-03-05 [1] CRAN (R 4.4.0)
## lidR * 4.2.3 2026-01-08 [1] CRAN (R 4.4.3)
## lifecycle 1.0.5 2026-01-08 [1] CRAN (R 4.4.3)
## logger 0.4.1 2025-09-11 [1] CRAN (R 4.4.1)
## lubridate 1.9.5 2026-02-04 [1] CRAN (R 4.4.3)
## lutz * 0.3.2 2023-10-17 [1] CRAN (R 4.4.0)
## lwgeom 0.2-15 2026-01-12 [1] CRAN (R 4.4.3)
## magrittr 2.0.4 2025-09-12 [1] CRAN (R 4.4.1)
## maptiles 0.11.0 2025-12-12 [1] CRAN (R 4.4.3)
## memoise 2.0.1 2021-11-26 [1] CRAN (R 4.4.0)
## openxlsx * 4.2.8.1 2025-10-31 [1] CRAN (R 4.4.1)
## otel 0.2.0 2025-08-29 [1] CRAN (R 4.4.1)
## P pacman 0.5.1 2019-03-11 [?] CRAN (R 4.4.0)
## pillar 1.11.1 2025-09-17 [1] CRAN (R 4.4.1)
## pkgbuild 1.4.8 2025-05-26 [1] CRAN (R 4.4.1)
## pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.4.1)
## pkgload 1.5.0 2026-02-03 [1] CRAN (R 4.4.3)
## plyr 1.8.9 2023-10-02 [1] CRAN (R 4.4.1)
## png 0.1-9 2026-03-15 [1] CRAN (R 4.4.3)
## PROJ * 0.6.0 2025-04-03 [1] CRAN (R 4.4.1)
## proj4 1.0-15 2025-03-21 [1] CRAN (R 4.4.1)
## proxy 0.4-29 2025-12-29 [1] CRAN (R 4.4.3)
## purrr 1.2.1 2026-01-09 [1] CRAN (R 4.4.3)
## R6 2.6.1 2025-02-15 [1] CRAN (R 4.4.1)
## rappdirs 0.3.4 2026-01-17 [1] CRAN (R 4.4.3)
## raster * 3.6-32 2025-03-28 [1] CRAN (R 4.4.1)
## rasterVis * 0.51.7 2025-09-01 [1] CRAN (R 4.4.1)
## RColorBrewer * 1.1-3 2022-04-03 [1] CRAN (R 4.4.1)
## Rcpp 1.1.1 2026-01-10 [1] CRAN (R 4.4.3)
## RCSF * 1.0.2 2020-02-04 [1] CRAN (R 4.4.0)
## renv 1.1.5 2025-07-24 [1] CRAN (R 4.4.1)
## reproj * 0.7.0 2024-06-11 [1] CRAN (R 4.4.0)
## rlang 1.1.7 2026-01-09 [1] CRAN (R 4.4.3)
## rmarkdown 2.30 2025-09-28 [1] CRAN (R 4.4.1)
## rstudioapi 0.18.0 2026-01-16 [1] CRAN (R 4.4.3)
## s2 1.1.9 2025-05-23 [1] CRAN (R 4.4.1)
## S7 0.2.1 2025-11-14 [1] CRAN (R 4.4.3)
## scales 1.4.0 2025-04-24 [1] CRAN (R 4.4.1)
## sessioninfo 1.2.3 2025-02-05 [1] CRAN (R 4.4.1)
## sf * 1.1-0 2026-02-24 [1] CRAN (R 4.4.3)
## snakecase 0.11.1 2023-08-27 [1] CRAN (R 4.4.0)
## sp * 2.2-1 2026-02-13 [1] CRAN (R 4.4.3)
## spacesXYZ 1.6-0 2025-06-06 [1] CRAN (R 4.4.1)
## stars 0.7-1 2026-02-13 [1] CRAN (R 4.4.3)
## stringi 1.8.7 2025-03-27 [1] CRAN (R 4.4.1)
## stringr 1.6.0 2025-11-04 [1] CRAN (R 4.4.1)
## svglite 2.2.2 2025-10-21 [1] CRAN (R 4.4.1)
## systemfonts 1.3.2 2026-03-05 [1] CRAN (R 4.4.3)
## terra * 1.9-1 2026-03-08 [1] CRAN (R 4.4.3)
## textshaping 1.0.5 2026-03-06 [1] CRAN (R 4.4.3)
## tibble 3.3.1 2026-01-11 [1] CRAN (R 4.4.3)
## tidyselect 1.2.1 2024-03-11 [1] CRAN (R 4.4.0)
## timechange 0.4.0 2026-01-29 [1] CRAN (R 4.4.3)
## tinytex * 0.58 2025-11-19 [1] CRAN (R 4.4.3)
## tmap * 4.2 2025-09-10 [1] CRAN (R 4.4.1)
## tmaptools * 3.3 2025-07-24 [1] CRAN (R 4.4.1)
## units 1.0-1 2026-03-11 [1] CRAN (R 4.4.3)
## useful * 1.2.7 2026-02-26 [1] CRAN (R 4.4.3)
## usethis * 3.2.1 2025-09-06 [1] CRAN (R 4.4.1)
## vctrs 0.7.2 2026-03-21 [1] CRAN (R 4.4.3)
## viridisLite 0.4.3 2026-02-04 [1] CRAN (R 4.4.3)
## withr 3.0.2 2024-10-28 [1] CRAN (R 4.4.1)
## wk 0.9.5 2025-12-18 [1] CRAN (R 4.4.3)
## xfun 0.57 2026-03-20 [1] CRAN (R 4.4.3)
## XML 3.99-0.23 2026-03-20 [1] CRAN (R 4.4.3)
## xml2 1.5.2 2026-01-17 [1] CRAN (R 4.4.3)
## yaml 2.3.12 2025-12-10 [1] CRAN (R 4.4.3)
## zip 2.3.3 2025-05-13 [1] CRAN (R 4.4.1)
## zoo 1.8-15 2025-12-15 [1] CRAN (R 4.4.3)
##
## [1] /Users/seamus/repos/lidar-forestry/renv/library/macos/R-4.4/aarch64-apple-darwin20
## [2] /Users/seamus/Library/Caches/org.R-project.R/R/renv/sandbox/macos/R-4.4/aarch64-apple-darwin20/93405863
##
## * ── Packages attached to the search path.
## P ── Loaded and on-disk path mismatch.
##
## ──────────────────────────────────────────────────────────────────-
Data used in this chapter are a public-domain USGS 3DEP release:
- https://rockyweb.usgs.gov/vdelivery/Datasets/Staged/Elevation/LPC/Projects/CA_CarrHirzDeltaFires_2019_B19/CA_CarrHirzDeltaFires_2_2019/ — Quality Level 1 airborne lidar point cloud over the Carr / Hirz / Delta Fires burn scar, Shasta County, CA. Tile 10SGH1587 (UTM 10N, NAD83(2011) / NAVD88 Geoid18) is used for the 1 ha working example.