
This R package contains functions to predict crown
scorch from Terrestrial Lidar scans acquired with a RIEGL vz400i,
following methods in Cannon et
al. 2025
Citation
Cannon, Jeffery B., Nicole E. Zampieri, Andrew W. Whelan, Timothy M. Shearman, Andrew J. Sánchez Meador, and J. Morgan Varner. “Terrestrial Lidar Scanning Provides Efficient Measurements of Fire-Caused Crown Scorch in Longleaf Pine.” Fire Ecology 21, no. 1 (2025): 71. https://doi.org/10.1186/s42408-025-00420-0.
The core functionality is to automatically
LAS objects using TreeLS::stemPointsrandomForest model from Cannon et al. 2025 to
estimate crown scorch
Fig. 1.
Schematic methodology for estimating canopy scorch volume using
terrestrial lidar scans trained on (A) ocular scorch measurements. We
(B) manually segmented pre- and post-burn trees for training, (C)
isolated crowns, and (D) generated histograms of return intensity. We
(E) calculated Δ intensity from changes in pre- and post-burn
histograms, and (F) combined these with training data to model canopy
scorch using random forests and beta regression. In application, (G)
scanned areas can be (H) automatically segmented, (I) crowns isolated
and prediction model applied, resulting in (J) individual crown scorch
estimates at an operational scale.
Get the latest released version of CrownScorchTLS from
github
install.packages('remotes')
remotes::install_github('jbcannon/CrownScorchTLS')You will also need lidR and randomForest
packages from CRAN
install.packages('lidR')
install.packages('randomForest')Load required packages after installing
library(lidR)
library(CrownScorchTLS)LASLoad a LAS object representing a post-burn scan of an
individual tree. The recommended time since burn is 15-20 days. Model
predictions are based on relative intensity from a RIEGL vz400i
terrestrial lidar scanner
# Download external file to a temporary .las/.laz file from data repo
url = "https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/D-03-10867_post.laz"
las_file <- tempfile(fileext = paste0(".", tools::file_ext(url)))
download.file(url, las_file, mode = "wb", quiet = TRUE)
las <- readLAS(las_file)
# Or use your own data
las <- readLAS("C:/path/to/your/file.laz")
plot(las, color='Intensity') Fig. 2.
LAS representation of Pinus palustris tree D-03-10867 from
Cannon et al. 2025, approximately 2 weeks after prescribed burn
Generate a histogram based on reflectance intensites to be used in
randomForest prediction
crown = remove_stem(las)
crown = add_reflectance(crown) # add reflectance since its missing
histogram = get_histogram(crown)
plot(density ~ intensity, data = histogram, xlab='Reflectance (dB)', type='l') Fig. 3.
Histogram of relative reflectance of Pinus palustris crown D-03-10867
from Cannon et al. 2025, approximately 2 weeks after prescribed burn
LAS objectPredict scorch from post-burn LAS object using
randomForest model from Cannon et al. 2025
predict_scorch(las)Model output
predicted_scorch
0.9402951
The CrownScorchTLS package contains data from six
example trees following Cannon et al. 2025 (Figure 3). They can be
accessed from the extdata directory in the package
# CrownScorchTLS Demo Workflow -----------------------------------------------
# This example demonstrates how to run predict_scorch() on TLS data.
# By default, it uses example .laz files hosted on GitHub.
# Users can easily switch to their own local files or URLs (see below).
library(CrownScorchTLS)
library(lidR)
# -------------------------------------------------------------------------
# OPTION A: Use external demo data hosted on GitHub
# -------------------------------------------------------------------------
filenames <- c(
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/M-04-15549_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/L-05-14669_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/E-08-9269_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/B-04-4286_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/D-03-10867_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/C-04-11029_post.laz"
)
# -------------------------------------------------------------------------
# OPTION B: Use YOUR OWN TLS files instead of demo data
#
# For local files:
# directory <- "C:/path/to/my/postfire_tls/"
# filenames <- list.files(directory, pattern="\\.laz$|\\.las$", full.names=TRUE)
#
# For your own remote URLs:
# filenames <- c(
# "https://myserver.org/data/tree1.laz",
# "https://myserver.org/data/tree2.laz"
# )
# -------------------------------------------------------------------------
# Run loop to compute scorch and plot histograms ----------------------------
par(mfrow = c(3,2), mar = c(4,4,1,1))
for (f in filenames) {
# Use this for remote download of urls
ext <- tools::file_ext(f) #detect file extension (laz|las)
ext <- paste0(".", ext)
tf = tempfile(fileext = ext) #create local fiel
download.file(f, tf, mode = "wb")
las <- readLAS(tf)
# Use this for using local files (more common)
# las <- readLAS(f)
# Compute scorch, plot histogram
scorch <- suppressMessages(predict_scorch(las, plot = TRUE))
# Print results
cat(
"file:\t", basename(f),
"\tscorch:\t", round(scorch, 3), "\n"
)
}Model output:
file: tree_001.laz scorch: 0.051
file: tree_002.laz scorch: 0.035
file: tree_003.laz scorch: 0.479
file: tree_004.laz scorch: 0.577
file: tree_005.laz scorch: 0.94
file: tree_006.laz scorch: 0.948
Fig. 4.
Histograms of lidar return intensity from six longleaf pines with
varying degrees of crown scorch
The steps for creating a customized model are similar as above. The steps are to:
spanner.# ###################
# 1. Option A — Use external example data (downloaded .las/.laz files)
urls <- c(
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/M-04-15549_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/L-05-14669_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/E-08-9269_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/B-04-4286_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/D-03-10867_post.laz",
"https://raw.githubusercontent.com/jbcannon/CrownScorchTLS-data/main/data/manual-clip-trees/C-04-11029_post.laz"
)
# Download each file to a temporary location
filenames <- sapply(urls, function(u) {
ext <- paste0(".", tools::file_ext(u))
tf <- tempfile(fileext = ext) # preserves .las/.laz
download.file(u, tf, mode = "wb", quiet = TRUE)
return(tf)
})
print("Using downloaded example data:")
print(filenames)
# ###################
# 1. Option B — User loads their own directory of segmented trees
# (leave this commented out for the vignette)
# directory = "C:/data/mytrees/"
# filenames = list.files(directory, pattern = '\\.(las|laz)$', full.names = TRUE)
# print("Using user-supplied local files:")
# print(filenames)
library(lidR)
library(tidyr)
library(tidyverse)
prediction_data = list() #initate list to hold all feature data
for(f in filenames) { # Loop through all files for prcoessing
las = readLAS(f) # read segmented
crown = remove_stem(las) #remove bole
crown = add_reflectance(crown) # add reflectance since its missing
histogram = get_histogram(crown) #summarize features from hisotgram
histogram$intensity = round(histogram$intensity,1) #1 sig fig on intensity
features = pivot_wider(histogram, names_from = 'intensity', values_from = 'density', names_prefix='intensity_') # pivot table on histograms
prediction_data[[length(prediction_data) + 1 ]] = features # save to list
cat('completed', basename(f), '\n')
}
prediction_data = do.call(rbind, prediction_data) #convert list to dataframe rows
view(prediction_data)
# Load field-measured scorch for training (same order as files above)
scorch_training = c(0.05, 0.2, 0.50, 0.75, 0.95, 0.95)
# Model using machine learning, random forests
library(randomForest)
library(Boruta)
#use boruta feature selection to reduce set of features
boruta.sel = Boruta(x = prediction_data, y = scorch_training)
important_features = names(boruta.sel$finalDecision[boruta.sel$finalDecision != 'Rejected'])
#random forests using only important features
model.RF = randomForest(x = prediction_data[, important_features], y = scorch_training)
print(model.RF) #view summary and out-of-bag variance explained
varImpPlot(model.RF) #variable importance plot
library(lidR)
new_las = readLAS('C:/data/newtrees/tree1.las') # new segmented tree for prediction
scorch = predict_scorch(new_las, model = model.RF) #model = NULL will use default model form Cannon et al. 2025
print(scorch)