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.
install.packages("PiC")# 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").
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 filetree_results <- SegOne(
a = data,
filename = "MyTree",
dimVox = 2,
eps = 1,
mpts = 4,
calculate_metrics = TRUE,
output_path = "./output"
)
print(tree_results$metrics)# 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"
)# Assess density and structure before segmentation
diag <- pic_analyze_cloud(a = data, output_path = "./output")run_PiC()| 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. |
Forest_seg, 8 stages)x, y, z table; a global coordinate shift is applied for
numerical precision (reversed for the outputs).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) |
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_min–h_rescue_max)
recovers woody points in the height band where trunks and low branches
are most often missed.
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.
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.
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_radius–dbh_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 are grouped by the forest component they affect. Defaults
are those of Forest_seg() 3.3.1.
a — input point cloud: a file path
(.xyz/.txt/.las/.laz)
or a data.frame/data.table with at least 3 columns (x, y, z).
Required.filename — output file prefix (default
"XXX"); e.g. "plot_001" →
plot_001_tree_report.csv.integer_precision — coordinate
rounding precision, "mm" (3 decimals, high-resolution TLS)
or "cm" (2 decimals, lighter for MLS/UAV). Default
"mm".output_format — "las"
(single classified LAS file, default) or "xyz" (one
plain-text file per class).output_path — output directory
(default tempdir()); created if missing.generate_reports — write the CSV
reports and the parameters log (default TRUE).Vox_print /
Woodpoints_print — export the voxel grid
(default FALSE) / the wood points (default
TRUE).dtm_coarse_res — coarse DTM grid size
in m (default 0.5); the fine resolution is set internally
to half of this.tolerance — vertical threshold above
the DTM in m (default 0.4); points within this band are
classified as forest floor.dimVox — voxel size in cm (default
2). TLS: 2 cm; sparser data: larger.th — minimum points per voxel (default
2).eps — DBSCAN radius in voxel units
(default 2); metric distance ≈
eps × dimVox.mpts — DBSCAN minimum neighbours
(default 9).h_trunk — minimum trunk length in m
(default 3); shorter clusters are discarded (removes
shrubs).N — minimum voxels per wood cluster
(default 3000).w_linear — linearity/quality filter,
0–1 (default 0.5); higher = stricter cylindrical
shape.h_rescue_min /
h_rescue_max — normalized height band (m
above DTM) for the rescue pass (defaults 1 /
5).dbh_tolerance — vertical slice
half-thickness in m (default 0.05, i.e. ±5 cm).dbh_max_rmse — maximum fitting RMSE in
cm (default 5).dbh_min_radius /
dbh_max_radius — valid radius range in m
(defaults 0.025 / 0.5, i.e. ≈ 5–100 cm
DBH).dbh_heights = c(1.3, 1.8, 2.3) m,
dbh_min_points = 8.canopy_vox_dim — DAV voxel size in m
(default 0.15).canopy_min_density — minimum canopy
density in pts/m³ (default 500; shared with the CBH
step).dav_understory_max_start — maximum
start height for the understory in m (default 1.3).dav_height_factor /
dav_median_height_factor — internal DAV
thresholds based on the median tree height (defaults 0.8 /
0.50).dav_eps /
dav_minPts — DBSCAN parameters for DAV
(defaults 2 / 4).calculate_cbh — enable CBH estimation
(default TRUE).cbh_hex_side — hexagon edge length in
m (default 0.15).cbh_min_branch_length — minimum
horizontal foliage extent in m (default 2.0).cbh_save_points — save CBH points
(default TRUE).cbh_hex_side,
canopy_vox_dim, canopy_min_density); vertical
gap tolerance and core count.output_format = "las" (default): a single
*_classified.las carrying the class codes above.output_format = "xyz": one plain-text file per
class — forest floor, wood, crown, understory, low/high noise, and
non-valid trees.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]calculate_cbh = TRUE). A CBH of -999 flags a
failed estimate.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]))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)))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
)| 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 |
| 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 |
Fast inventory (DBH + height only):
calculate_cbh = FALSE
generate_reports = TRUEFire-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 gridtree_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 clustersvalid_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).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).integer_precision = "cm" # coarser coordinate rounding
dimVox = 3
canopy_vox_dim = 0.20
# Or subsample very large inputs:
data_sub <- data[sample(.N, 50e6)]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.
lidR moved from Imports to
Suggests: LAS/LAZ reading and writing are now
optional and guarded; the package installs and works on
.xyz/.txt data without lidR.lidR is declared via
Additional_repositories.Forest_seg() documentation
(8-stage pipeline, classification scheme, LOR/DAV/GAB, DBH, outputs,
return value).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).
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.
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
Licensed under the GNU General Public License v3.0 or later. See the GPL-3 license for details.
?PiC (or
?Forest_seg) in the R console after installationDevelopment supported by CNR-IBE research on Mediterranean forest structure and fire-risk assessment using terrestrial and mobile laser scanning.