Canopy Height

Author

Seamus Murphy

Published

February 24, 2022

3.0 Overview

Canopy Height Models (CHM) represent vertical distance from ground to canopy top across the landscape. CHMs derived from normalised LiDAR point clouds serve as primary inputs for tree detection, biomass estimation, and forest inventory. This chapter documents operational workflows for CHM aggregation, dominant-height extraction, and quality control using the Carr Fire 1 ha AOI normalised in Chapter 1.


Environment Setup

pacman::p_load(
  "curl",
  "dplyr",
  "fontawesome",
  "htmltools", "httr2",
  "janitor",
  "kableExtra", "knitr",
  "lidR", "lutz",
  "openxlsx",
  "PROJ",
  "raster", "rasterVis", "reproj",
  "sf",
  "tinytex", "tmap", "tmaptools", "terra",
  "useful", "usethis"
  )

3.1 Prepare CHM

Create 1 m resolution CHM from the normalized point cloud using triangulation.

# Import outputs from previous chapter
las   <- lidR::readALSLAS("../data/las_1ha.laz")
ttops <- sf::read_sf("../data/ttops_1ha.gpkg", quiet = TRUE)

# Generate 1 m CHM
chm <- lidR::rasterize_canopy(las, res = 1, dsmtin(max_edge = 8))
terra::plot(chm, col = height.colors(50))


3.2 Extract Dominant Height

Points Extraction

Extract dominant height directly from point clouds without rasterizing. This method is slower but preserves full point information. The ttops object is derived in Chapter 2: Stem Detection.

# Create a 1 m grid
grid       <- sf::st_make_grid(ttops, cellsize = 1, square = TRUE)
grid_sf    <- sf::st_sf(grid_id = 1:length(grid), geometry = grid)
ttops_grid <- sf::st_join(ttops, grid_sf)

ttops_95h <- ttops_grid |>
    sf::st_drop_geometry() |>
    dplyr::group_by(grid_id) |>
    dplyr::summarise(
      count = dplyr::n(),
      `95thQuantile` = quantile(Z, 0.95, na.rm = TRUE),
      Max = max(Z, na.rm = TRUE),
      .groups = "drop"
    ) |>
    dplyr::right_join(grid_sf, by = "grid_id") |>
    sf::st_as_sf()

# Convert to raster
ttops_95h_rast <- terra::rasterize(
  terra::vect(ttops_95h),
  terra::rast(terra::ext(ttops_95h), res = 1, crs = terra::crs(ttops_95h)),
  field = "95thQuantile"
)

terra::plot(ttops_95h_rast, main = "95th Percentile Height")


Raster Extraction

Aggregate CHM using 95th percentile statistics. This method is faster for large catalogs.

# Define 95th percentile function
quant95 <- function(x, ...) {
    quantile(x, probs = 0.95, na.rm = TRUE)
}

# Aggregate using q95 (2x means a 2 m output cell from 1 m input)
chm_95h <- terra::aggregate(
    chm,
    fact = 2,
    fun = quant95
)

terra::plot(chm_95h, main = "95th Percentile Height")

terra::writeRaster(chm_95h, "../data/chm_95h.tif", overwrite = TRUE)

3.3 Validation

Compare 95th percentile CHM against detected tree heights from Chapter 2.

# Extract CHM values at tree locations
ttops$chm_95h <- terra::extract(terra::rast(chm_95h), terra::vect(ttops))[, 2]
## Error:
## ! [extract] raster has no values

# Statistics
validation <- ttops |>
    dplyr::summarise(
        correlation = cor(Z, chm_95h, use = "complete.obs"),
        rmse = sqrt(mean((Z - chm_95h)^2, na.rm = TRUE)),
        mae = mean(abs(Z - chm_95h), na.rm = TRUE),
        bias = mean(Z - chm_95h, na.rm = TRUE)
    )
## Error in `dplyr::summarise()`:
## ℹ In argument: `correlation = cor(Z, chm_95h, use =
##   "complete.obs")`.
## Caused by error in `cor()`:
## ! 'y' must be numeric

print(validation)
## Error in `h()`:
## ! error in evaluating the argument 'x' in selecting a method for function 'print': object 'validation' not found

3.4 Build Mosaic

Merge all operating areas into a single product for Chapter 4. For the Carr demonstration the AOI is a single tile, so the 95th-percentile raster from §3.2 is the input to the biomass model in Chapter 4.

# With multi-area datasets the mosaic step loops over per-area CHM rasters.
# area_chms <- lapply(areas, function(a) raster(sprintf("../data/chm-tiles/%s.tif", a)))
# chm_95h_final <- do.call(merge, c(area_chms, tolerance = 1))
# writeRaster(chm_95h_final, "../data/chm_95h.tif", overwrite = TRUE)

This raster is the dominant height input for biomass models in Chapter 4.


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)
##    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)
##    lazyeval       0.2.2     2019-03-15 [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)
##    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)
##    rlas           1.8.4     2026-01-29 [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.
## 
## ──────────────────────────────────────────────────────────────────