PiC: Pointcloud Interactive Computation for Forest Structure Analysis

Version License: GPL v3 R

Overview

PiC (Pointcloud Interactive Computation) is an R package for the processing, segmentation, and analysis of terrestrial and mobile laser scanning (TLS and MLS) forest point-cloud data. It provides fast voxel-based processing, classification of the point cloud into forest floor, understory, wood and canopy components, and algorithms for single-tree analysis and stand-level structural characterization.

The methods are designed to handle large and dense point clouds efficiently, supporting applications in forest structure assessment, connectivity analysis, and fire-risk evaluation. In particular, Forest_seg() was built for very large datasets: it can process clouds of up to ~600 million points (over 10 GB) on a workstation such as a MacBook Pro M3 with 36 GB of RAM, in reasonable times — a throughput of roughly one million points per second. Input data are provided as .xyz, .txt, .las, or .laz point-cloud files.

Key Features

Installation

From CRAN

install.packages("PiC")

Development version

# install.packages("devtools")
devtools::install_github("rupppy/PiC")

System requirements

Optional dependency. Reading and writing .las/.laz files relies on the lidR package, listed under Suggests. If lidR is not installed, PiC still runs on .xyz/.txt data and the LAS/LAZ routines stop with an informative message. Install it with install.packages("lidR").

Quick Start

Forest segmentation

library(PiC)

# Load a point cloud (x, y, z)
data <- data.table::fread("forest_plot.xyz", header = FALSE)
colnames(data)[1:3] <- c("x", "y", "z")

# Full segmentation + tree/plot metrics
results <- Forest_seg(
  a             = data,
  filename      = "plot_001",
  dimVox        = 2,        # voxel size (cm) for LOR wood segmentation
  eps           = 2,        # DBSCAN epsilon (voxel units)
  mpts          = 9,        # DBSCAN minimum points
  calculate_cbh = TRUE,     # estimate crown base height (GAB)
  output_format = "las",    # single classified LAS (default) or "xyz"
  output_path   = "./results"
)

results$tree_metrics      # per-tree table
results$plot_report_file  # path to the plot-level report
results$las_file          # path to the classified LAS file

Single-tree analysis

tree_results <- SegOne(
  a                 = data,
  filename          = "MyTree",
  dimVox            = 2,
  eps               = 1,
  mpts              = 4,
  calculate_metrics = TRUE,
  output_path       = "./output"
)

print(tree_results$metrics)

Metrics from an already-classified cloud

# Recompute tree- and plot-level metrics from a classified LAS
# (classes: ground = 2, understory = 3, wood = 4, crown = 5)
m <- metrics_from_las(
  las_file    = "plot_001_classified.las",
  output_path = "./output"
)

Point-cloud diagnostics (pre-processing)

# Assess density and structure before segmentation
diag <- pic_analyze_cloud(a = data, output_path = "./output")

Interactive Shiny application

run_PiC()

Main Functions

Function Purpose
Forest_seg() End-to-end plot segmentation and tree/plot metrics (8-stage pipeline).
SegOne() Single-tree wood–leaf segmentation and metrics; writes a classified LAS.
metrics_from_las() Recomputes tree/plot metrics from an already-classified LAS, without re-segmenting.
pic_analyze_cloud() Pre-processing diagnostics (point/voxel density, structure), with optional PDF report.
Voxels() Standalone voxelization of a point cloud.
Floseg() Forest-floor (ground) segmentation.
run_PiC() Launches the interactive Shiny interface.

Processing Pipeline (Forest_seg, 8 stages)

  1. Load & shift — input is coerced to an x, y, z table; a global coordinate shift is applied for numerical precision (reversed for the outputs).
  2. Forest floor extraction — a coarse/fine ground model (DTM) separates the forest floor from low vegetation and above-ground biomass (AGB).
  3. LOR (Ligneous Object Recognition) — woody points (trunks and branches) are identified.
  4. Tree detection & metrics — tree positions and heights are derived from the woody clusters.
  5. DBH — diameter at breast height fitted at multiple heights.
  6. DAV (Directional Anisotropy of Vegetation) — foliage is split into crown and understory.
  7. GAB (Geometrical Aggregation of Biomass) — crown base height (CBH) is estimated.
  8. Output — classified point cloud, reports, and parameters log are written.

Point classification scheme

Each point is assigned an integer class, consistent with the codes stored in the classified LAS file:

Code Component
2 Forest floor (ground / vegetal soil)
3 Understory (low vegetation and shrubs below the crown)
4 Wood (trunks and branches)
5 Crown / foliage
6 Non-valid trees (clusters that failed DBH validation)
7 Noise (isolated or non-classifiable points)

Algorithm Details

LOR — Ligneous Object Recognition

AGB points are voxelized (dimVox, th) and grouped with density-based clustering (DBSCAN, parameters eps/mpts). Clusters are retained as woody structures when they exceed a minimum size (N), satisfy a linearity/quality filter (w_linear), and reach a minimum trunk length (h_trunk). A dedicated rescue pass (h_rescue_minh_rescue_max) recovers woody points in the height band where trunks and low branches are most often missed.

DAV — Directional Anisotropy of Vegetation

The remaining foliage is voxelized at canopy_vox_dim and filtered by a minimum density (canopy_min_density). Voxels are clustered (dav_eps/dav_minPts) and split into crown and understory using an adaptive height threshold derived from the median tree height (dav_height_factor, dav_median_height_factor, capped by dav_understory_max_start). Low vegetation from the forest-floor stage is assigned directly to the understory.

GAB — Geometrical Aggregation of Biomass (CBH)

When calculate_cbh = TRUE, crown and wood voxels are merged for spatial continuity and projected onto a hexagonal tessellation (cbh_hex_side). Each foliage cell is assigned to the nearest tree by Voronoi partitioning on voxel coordinates, and a breadth-first connectivity trace (trunk → branches → foliage) identifies the lowest continuously foliated branch, whose height is reported as the crown base height. The step runs in parallel across the available cores. A minimum foliage extent (cbh_min_branch_length) guards against spurious detections.

DBH calculation (multi-height, dual method)

At each priority height (1.3 m, with fallbacks at 1.8 m and 2.3 m) both Pratt and Landau circle-fitting methods are computed; when both are valid, the fit with the lower RMSE is kept. This handles occluded trunks, irregular sections and scan artefacts. A fit is accepted only within the radius range (dbh_min_radiusdbh_max_radius, i.e. ≈ 5–100 cm diameter) and below the maximum RMSE (dbh_max_rmse, default 5 cm). Trees failing all heights are flagged as non-valid (class 6).

Parameters

Parameters are grouped by the forest component they affect. Defaults are those of Forest_seg() 3.3.1.

1. Input / Output

2. Forest floor (DTM-based extraction)

3. Wood detection (LOR)

4. DBH (multi-height diameter)

5. Crown–understory separation (DAV)

6. Crown base height (GAB / CBH)

Output Files

Classified point cloud

Reports (when generate_reports = TRUE)

*_tree_report.csv — one row per tree:

Column Units Description
Tree_n Tree identifier
cls Internal cluster id
X, Y, Z m Position (original coordinates)
Height m Total height above the DTM
DBH (cm) cm Diameter at the selected height
RMSE (cm) cm Circle-fit quality
valid_tree TRUE/FALSE (DBH validation)
CBH m Crown base height (when computed)

*_plot_report.csv — stand-level summary in long format (metric; value), including: area_of_interest_m2, coverage_area_m2, coverage_percentage, crown_volume_m3, understory_volume_m3, min/max/mean/median/sd_height_m, mean/median_dbh_cm, mean/median_cbh_m, tree_count, valid_tree_count, trees_per_hectare, basal_area_m2_ha.

plot_summary <- data.table::fread(results$plot_report_file, sep = ";")
plot_summary[metric == "basal_area_m2_ha", value]

Other outputs

Usage Examples

Example 1 — Standard forest inventory

library(PiC)

data <- data.table::fread("mature_stand.xyz")
colnames(data)[1:3] <- c("x", "y", "z")

results <- Forest_seg(
  a                 = data,
  filename          = "inventory_2025",
  output_path       = "./results",
  integer_precision = "mm",
  dimVox            = 2, th = 2, eps = 2, mpts = 9,
  calculate_cbh     = TRUE,
  output_format     = "las"
)

trees        <- results$tree_metrics
plot_summary <- data.table::fread(results$plot_report_file, sep = ";")

cat(sprintf("Trees: %d\n", nrow(trees)))
cat(sprintf("Mean DBH: %.1f cm\n", mean(trees[valid_tree == TRUE, `DBH (cm)`], na.rm = TRUE)))
cat(sprintf("Basal area: %.2f m2/ha\n", plot_summary[metric == "basal_area_m2_ha", value]))

Example 2 — Fire-risk assessment (ladder fuels)

results <- Forest_seg(
  a                     = "aleppo_pine.xyz",
  filename              = "fire_risk",
  calculate_cbh         = TRUE,    # critical for ladder-fuel detection
  cbh_min_branch_length = 1.2,     # conservative foliage extent
  cbh_save_points       = TRUE,
  output_format         = "las"
)

trees     <- results$tree_metrics
valid_cbh <- trees[CBH > 0 & CBH < 900, CBH]

cat(sprintf("Mean CBH: %.2f m\n", mean(valid_cbh)))
cat(sprintf("Trees with CBH < 2 m: %d  [ladder-fuel risk]\n", sum(valid_cbh < 2)))

Example 3 — Large dataset / speed

results <- Forest_seg(
  a                 = "large_plot.xyz",
  filename          = "optimized",
  integer_precision = "cm",   # coarser coordinate rounding
  dimVox            = 3,       # larger wood voxels
  canopy_vox_dim    = 0.20,    # coarser DAV resolution
  calculate_cbh     = FALSE    # skip the most intensive stage
)

Parameter Tuning Guidelines

By scanner type

Scanner integer_precision dimVox dav_eps
High-res TLS mm 2 2
Standard TLS mm 2–3 2
Mobile LS (MLS) cm 3–4 3
UAV-LiDAR cm 4–5 3–4

By forest type

Type N (voxels) h_trunk (m) w_linear
Young regeneration 2000 1.5 0.4
Mature conifers 3000–5000 3.0 0.5
Broadleaf 3000 2.5 0.45
Sparse / degraded 2000 2.0 0.4

By analysis goal

Fast inventory (DBH + height only):

calculate_cbh = FALSE
generate_reports = TRUE

Fire-risk assessment:

calculate_cbh   = TRUE       # ladder-fuel detection
cbh_save_points = TRUE       # export crown/understory layers
output_format   = "las"

Research (full detail):

calculate_cbh    = TRUE
cbh_save_points  = TRUE
generate_reports = TRUE
Vox_print        = TRUE      # diagnostic voxel grid

Troubleshooting

No trees detected (tree_count = 0)

# Relax wood segmentation
dimVox   = 2     # finer voxels
eps      = 3     # larger clustering radius
N        = 2000  # lower cluster-size threshold
w_linear = 0.4   # accept less linear clusters

Many invalid DBH (valid_tree = FALSE)

# Inspect the fit-quality distribution
hist(results$tree_metrics[["RMSE (cm)"]])

# If systematically high, relax (cautiously):
dbh_max_rmse = 7
# If clusters are noisy, tighten the wood filters (N, h_trunk, w_linear).

Many CBH failures (CBH = -999)

cbh_min_branch_length = 1.0   # accept shorter foliated branches
canopy_min_density    = 300   # lower density requirement
# Verify the DAV crown is well populated (output_format = "xyz" exports it).

Memory errors

integer_precision = "cm"      # coarser coordinate rounding
dimVox            = 3
canopy_vox_dim    = 0.20
# Or subsample very large inputs:
data_sub <- data[sample(.N, 50e6)]

Coordinate Precision & Performance

PiC processes the cloud on native float coordinates and applies a single global shift for numerical stability. The integer_precision argument controls the rounding granularity used throughout the pipeline: "mm" keeps 3 decimals (recommended for high-resolution TLS), while "cm" keeps 2 decimals, which lightens memory use and voxel bookkeeping on large or coarser MLS/UAV clouds. The CBH stage (GAB) runs in parallel across the available cores.

What’s New in 3.3.1

See NEWS.md for the full history, including the 3.3 features (metrics_from_las(), pic_analyze_cloud(), crown/understory volumes, the revised GAB/CBH connectivity and the LOR rescue pass).

Citation

Package

Ferrara, R., & Arrizza, S. (2025). PiC: Pointcloud Interactive Computation
for Forest Structure Analysis. R package version 3.3.1.
https://hdl.handle.net/20.500.14243/533471

Methodological reference

Ferrara, R., Virdis, S.G.P., Ventura, A., Ghisu, T., Duce, P., & Pellizzaro, G. (2018).
An automated approach for wood-leaf separation from terrestrial LIDAR point clouds
using the density based clustering algorithm DBSCAN.
Agricultural and Forest Meteorology, 262, 434-444.
https://doi.org/10.1016/j.agrformet.2018.04.008

Circle-fitting algorithm

Pratt, V. (1987). Direct least-squares fitting of algebraic surfaces.
ACM SIGGRAPH Computer Graphics, 21(4), 145-152.

Authors

Roberto Ferrara (Maintainer) — CNR-IBE (National Research Council of Italy – Institute for BioEconomy) Email: roberto.ferrara@cnr.it ORCID: 0009-0000-3627-6867

Stefano Arrizza (Contributor) — CNR-IBE Email: stefano.arrizza@cnr.it ORCID: 0009-0009-2290-3650

License

Licensed under the GNU General Public License v3.0 or later. See the GPL-3 license for details.

Acknowledgments

Development supported by CNR-IBE research on Mediterranean forest structure and fire-risk assessment using terrestrial and mobile laser scanning.