Single tree segmentation output

R-CMD-check CRAN GitHub licence Downloads Build Status

FuelDeep3D: An R package for Fire Fuels Segmentation in 3D Using Terrestrial Laser Scanning and Deep Learning

Authors: Venkata Siva Reddy Naga, Alexander John Gaskins, and Carlos Alberto Silva.

FuelDeep3D provides tools for processing, feature extraction, and classification of 3D forest point clouds for fuel assessment applications. The package supports creating training datasets, computing height-derived metrics, segmenting vegetation structures, and writing per-point fuel classes back to LAS/LAZ files. These functions streamline TLS-based fuel mapping workflows and enable integration with forest inventory, wildfire modeling, and ecological analysis pipelines in R.

The package enables users to move efficiently from raw .las files to classified fuel layers, supporting applications in forest structure assessment, wildfire behavior modeling, and fuel complexity analysis.

FuelDeep3D offers tools to:


1. Getting Started

1.1 Installation of the FuelDeep3D package

install.packages(
  "FuelDeep3D",
  repos = c("https://venkatasivanaga.r-universe.dev/FuelDeep3D",
            "https://cloud.r-project.org")
)

library(FuelDeep3D)

1.1.1 Manual installation using Anaconda/Miniconda (Only if not already installed)

i) Download

OR

ii) Install

  1. Download the Windows installer (64-bit) for Python 3.x.

  2. Run the installer:

  3. Click Next - Install and wait for the installation to finish.

  4. Open Anaconda Prompt from the Start menu and run:

    ```bash conda –version

1.1.2 Create the pointnext Conda environment

You can create the Python environment directly from R using reticulate and install all Python dependencies.

# 0) Install FuelDeep3D and reticulate if not already
install.packages("FuelDeep3D")    # from r-universe or other repo
install.packages("reticulate")

library(FuelDeep3D)
library(reticulate)

# 1) Create or reuse the "pointnext" env and install deps if needed
ensure_py_env(envname = "pointnext", reinstall = TRUE, cpu_only = FALSE)   # creates env + installs deps the first time
# Use "reinstall = TRUE" to force-reinstall.
# ensure_py_env(envname = "pointnext", reinstall = TRUE, cpu_only = TRUE) # Installs only cpu dependencies

# 2) Sanity check – should show Python from the "pointnext" env
py_config()

Note (Troubleshooting): If you run into any Conda/reticulate issues while creating or activating the pointnext environment (e.g., R picks the wrong Python, py_config() shows an unexpected interpreter, or you see missing-module/DLL errors), refer to the Conda + R (reticulate) Troubleshooting Guide for step-by-step diagnostics and fixes.


2. Visualization of a 3D point cloud

FuelDeep3D integrates smoothly with the lidR package, enabling users to quickly explore LiDAR scenes, height structures, and model-predicted segmentations.
This section provides simple commands to visualize .las / .laz files during your workflow.


2.1 Visualize LiDAR data by Height (Z)

This visualization shows the unclassified LiDAR point cloudwith points colored by height (Z value). This height-based coloring helps reveal canopy layers, trunk structure, and differences in ground elevation.

library(lidR)

las <- readLAS(system.file("extdata", "las", "trees.laz",
                           package = "FuelDeep3D"))

# las <- readLAS("path/to/your_file.laz")

# 1) Default plot (black bg, legend on, thickness by height)

plot_3d(las)

# 2) Custom palette + white background

plot_3d(
  las,
  bg = "white",
  height_palette = c("purple","blue","cyan","yellow","red"),
  title = "Custom palette"
)
  
# 3) Fixed Z color scale for comparisons + no legend

plot_3d(
  las,
  zlim = c(0, 40),
  add_legend = FALSE,
  title = "Fixed Z (0-40), no legend"
)
  
# 4) Turn OFF thickness-by-height; use a single point size

plot_3d(
  las,
  size_by_height = FALSE,
  size = 4,
  title = "Uniform thicker points"
)
  
# 5) Legend on the LEFT and thicker legend bar

plot_3d(
  las,
  legend_side = "left",
  legend_width_frac = 0.05,
  title = "Legend left"
)
  
# 6) Make everything thicker (multiplies size_range when size_by_height=TRUE)

plot_3d(
  las,
  size = 1.8,
  size_range = c(1, 7),
  size_power = 1.2,
  title = "Thicker points by height"
)

Raw LiDAR visualization

This view helps inspect canopy structure, terrain variation, and overall point-cloud quality.


3. Predict on new data using a pre-trained model

library(FuelDeep3D)
library(reticulate)
use_condaenv("pointnext", required = TRUE)

cfg <- config(
  las_path     = system.file("extdata", "las", "trees.laz", package = "FuelDeep3D"),  # any LAS or LAZ you want to segment
  out_pred_dir = "output_predictions",
  model_path   = system.file("extdata", "model", "best_model.pth", package = "FuelDeep3D"),       # your pre-trained checkpoint
  num_classes = 3
)

predict(cfg, mode = "overwrite", setup_env = FALSE)
# or keep original classification and add 'pred_label':
# predict(cfg, mode = "extra", setup_env = FALSE)

3.1 Predicted Result

Visualizing predicted classes in R

FuelDeep3D stores per-point predictions in the LAS attribute Classification (the standard LAS classification field). You can visualize these predictions directly in R using an interactive rgl window with predicted_plot3d(). Points are colored by any discrete field stored in las@data (e.g., "Classification" for predictions or "label" for original labels).

Note: FuelDeep3D intentionally does not draw a fixed legend inside the rgl window. Instead, when verbose = TRUE, the function prints a clear class → name → color mapping in the R console.

Predicted output (Classification)

library(lidR)
library(FuelDeep3D)

# Read the predicted LAS/LAZ (predictions stored in las@data$Classification)
las_pred <- readLAS("trees_predicted.las")

predicted_plot3d(
  las_pred,
  field = "Classification",
  bg    = "white",
  title = "Predicted classes (Classification)",
  verbose = TRUE
)

Compare raw labels vs predicted classes

las_raw <- readLAS("trees.las")

# Original labels (ground truth) stored in las@data$label
predicted_plot3d(las_raw, field = "label", bg = "white", title = "Original labels")

# Predicted labels stored in las@data$Classification
predicted_plot3d(las_pred, field = "Classification", bg = "white", title = "Predicted classes")

Custom colors and custom class names

my_cols <- c(
  "0" = "#1F77B4",  # blue
  "1" = "#8B4513",  # brown
  "2" = "#228B22"   # green
)

my_labs <- c(
  "0" = "Ground vegetation",
  "1" = "Branch/Stem",
  "2" = "Leaves/Foliage"
)

predicted_plot3d(
  las_pred,
  field = "Classification",
  class_colors = my_cols,
  class_labels = my_labs,
  bg = "white",
  verbose = TRUE
)

Downsampling (optional for large point clouds): The default setting plots every point (downsample = “none”). For large point clouds, use downsampling to speed up plotting and maintain responsiveness.

predicted_plot3d(
  las_pred,
  field = "Classification",
  downsample = "voxel",
  voxel_size = 0.10,
  size = 2,
  bg = "white"
)

Important note about color names

Base R does not recognize some CSS color names (for example, lime).
To avoid errors, hex codes are recommended, though valid base R color names also work.

# ✅ hex is safest
predicted_plot3d(las_pred, field="Classification",
                  class_colors = c("black","red","#00FF00"))

# ✅ valid base R name example: "limegreen"
predicted_plot3d(las_pred, field="Classification",
                  class_colors = c("black","red","limegreen"))
Example segmentation output

An example of the vegetation segmentation applied to a labeled LAS file. Each point is colored by its predicted class (e.g., ground/understory, stem, canopy foliage).

Single tree segmentation output

In this example, the model was trained on trees.las and then used to predict labels for the same scene. The output LAS (trees_predicted.las) stores predictions in the Classification field, which can be visualized in tools like CloudCompare or QGIS using a class-based color ramp.


4. Pre-processing and Training

Pre-processing prepares raw TLS point clouds for deep learning–based fuel segmentation. This step focuses on removing obvious outliers, standardizing point attributes, and improving the quality of model inputs prior to tiling and feature extraction.


4.1 Optional noise filtering

TLS point clouds may contain isolated outlier points, particularly in sparse regions of the scene. To reduce the influence of these points, FuelDeep3D provides a utility function based on Statistical Outlier Removal (SOR).

The filtering is applied selectively to points above a user-defined height threshold, while points below this threshold are preserved. This helps remove sparse artifacts without affecting ground or lower vegetation structure.

Parameters: - height_thresh: height (in meters) above which SOR is applied - k: number of nearest neighbors used to estimate local point spacing - zscore: standard deviation multiplier controlling outlier rejection

Example: apply noise filtering

library(FuelDeep3D)
library(lidR)

# Load TLS point cloud
las <- readLAS(system.file("extdata", "las", "trees.laz",
                           package = "FuelDeep3D"))

# Apply SOR-based filtering
las_clean <- remove_noise_sor(
  las,
  height_thresh = 5,
  k = 20,
  zscore = 2.5
)

# Inspect the filtered point cloud
plot(las_clean, color = "Z", pal = height.colors(30), bg = "white")

4.2 Train a new model on your own labelled LAS data

library(FuelDeep3D)
library(reticulate)
use_condaenv("pointnext", required = TRUE)

cfg <- config(
  las_path     = system.file("extdata", "las", "trees.laz", package = "FuelDeep3D"),
  out_dir      = system.file("extdata", "npz_files", package = "FuelDeep3D"),
  out_pred_dir = system.file("extdata", "output_directory", package = "FuelDeep3D"),
  model_path   = system.file("extdata", "model", "best_model.pth", package = "FuelDeep3D"),
  epochs       = 2, batch_size = 16,
  learning_rate = 1e-5, weight_decay = 1e-4,
  block_size = 6, stride = 1, sample_n = 4096,
  repeat_per_tile = 4, min_pts_tile = 512,
  cell_size = 0.25, quantile = 0.05
)

res <- train(cfg, setup_env = FALSE)        # trains & saves best .pth
predict(cfg, mode = "overwrite", setup_env = FALSE)  # writes trees_predicted.las

5. Evaluation of Predicted LAS Files

You can compute accuracy, confusion matrix, precision, recall, and F1 directly.

FuelDeep3D includes evaluation utilities to measure segmentation quality using LAS/LAZ files. These tools compute:


### 5.1.1 Evaluate Performance on a Single LAS File

This function allows users to evaluate segmentation performance directly from a single LAS file that contains both ground-truth labels and predicted classes. Simply specify which attribute stores the true labels (e.g., “label”) and which stores the predictions (e.g., “Classification”), and the function computes accuracy, confusion matrix, precision, recall, and F1 scores automatically.

Use this when ground truth and predictions are in the same LAS file, stored in two different fields.

library(FuelDeep3D)
library(lidR)

# LAS contains both GT (label) and predictions (Classification)
las <- readLAS("trees_predicted.las")

results <- evaluate_single_las(
  las,
  truth_col = "label",
  pred_col  = "Classification"
)
results$accuracy
results$confusion_matrix
results$precision
results$recall
results$f1

### 5.1.2 Evaluate Performance on Two LAS Files

Use this when ground truth labels and predicted classes are in separate LAS/LAZ files. Both files must be point-wise aligned (same points in the same order, same number of points). The function compares truth_col vs pred_col and returns accuracy, confusion matrix, and per-class precision/recall/F1.

library(FuelDeep3D)
library(lidR)

truth_las <- readLAS("trees_groundtruth.las")  # contains truth_col (e.g., label)
pred_las  <- readLAS("trees_predicted.las")    # contains pred_col  (e.g., Classification)

results <- evaluate_two_las(
  truth_las,
  pred_las,
  truth_col = "label",
  pred_col  = "Classification"
)

results$accuracy
results$confusion_matrix
results$precision
results$recall
results$f1

This helps to return a list with:


### 5.2 Print Confusion Matrix

print_confusion_matrix(results$confusion)

This prints a clean, aligned table such as:

| True \ Pred |       0 |       1 |       2 |
|-------------|---------|---------|---------|
|    0        | 528404  |    1005 |   3253  |
|    1        | 25457   | 2598520 | 140186  |
|    2        | 24931   |  449195 | 867824  |

### 5.3 Print Precision, Recall, F1, and Accuracy in a Table

print_metrics_table(results)

This produces an easy-to-read table:

| Class   | Precision | Recall | F1_Score | Accuracy |
|---------|-----------|--------|----------|----------|
| 0       | 0.9508    | 0.9535 | 0.9521   | 0.9535   |
| 1       | 0.8940    | 0.9450 | 0.9188   | 0.9450   |
| 2       | 0.7375    | 0.6552 | 0.6941   | 0.7552   | 
| Overall | 0.8608    | 0.8512 | 0.8550   | 0.9012   |
|---------|-----------|--------|----------|----------|

The Overall row shows macro-averaged precision, recall, and F1 across all classes.


### 5.4 Plot Confusion Matrix (Heatmap)

FuelDeep3D can also plot the confusion matrix as a heatmap in R (requires ggplot2).
These plots help quickly identify which classes are most frequently confused and whether errors are concentrated in specific rows/columns.

cm <- table(True = las@data$label, Pred = las@data$Classification)

plot_confusion_matrix(
  cm,
  row_normalize = FALSE,
  las_name = "trees.laz",
  title = "Confusion Matrix (Row-normalized)",
  class_names = c("0" = "Ground", "1" = "Leaves", "2" = "Branch"),
  palette_type = "viridis",
  palette_name = "cividis"
)

Confusion Matrix

Row-normalized heatmap (proportion per true class; easier to compare classes)

cm <- table(True = las@data$label, Pred = las@data$Classification)

plot_confusion_matrix(
  cm,
  row_normalize = TRUE,
  las_name = "trees.laz",
  title = "Confusion Matrix (Row-normalized)",
  class_names = c("0" = "Ground", "1" = "Leaves", "2" = "Branch"),
  palette_type = "viridis",
  palette_name = "cividis"
)

Confusion Matrix


### 5.5 Class Distribution Summary

las <- lidR::readLAS("C:/path/to/your_file.laz")

# Predicted class summary
las_class_distribution(las, field = "Classification")

# Raw/original label summary
las_class_distribution(las, field = "label")

# With readable names
labs <- c("0"="Ground vegetation", "1"="Branch/Stem", "2"="Leaves/Foliage")
las_class_distribution(las, field = "Classification", class_labels = labs)

# Drop NA class (if any)
las_class_distribution(las, field = "Classification", include_na = FALSE)

Shows how many points belong to each predicted class.


These tools simplify evaluating segmentation performance directly from LAS files without external scripts or reformatting.


Acknowledgements

FuelDeep3D was supported by:

Reporting Issues

Please report any issue regarding the FuelDeep3D package to Venkata Siva Reddy Naga (vs.naga@ufl.edu) or Dr. Silva (c.silva@ufl.edu).

Disclaimer

FuelDeep3D package comes with no guarantee, expressed or implied, and the authors hold no responsibility for its use or the reliability of its outputs.