The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
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.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.