logs <- qsf_log(qsf)
logs
#> [W3] warning Small tree allometry n = 11 trees
#> [W2] warning No valid measure n = 7 trees8 Quantitative Structure Forest
A Quantitative Structure Model (QSM) is a three-dimensional representation of a single individual tree that explicitly describes its geometry and topology (stems, branches, radii, volumes). QSMs provide a physically meaningful model, rather than a purely statistical description.
In this book, we extend this concept from individual trees to entire forests by introducing the Quantitative Structure Forest (QSF). A QSF is a coherent collection of QSMs that together reconstruct the three-dimensional structure of all trees within a forested area.
This conceptual extension is made possible by our advances in QSM algorithms that are fast and robust enough to reconstruct tens of thousands of trees, enabling full 3D modelling of forests spanning several hectares. The QSF concept bridges the gap between tree-level and forest-level analysis, providing a unified framework to study tree architecture, stand structure, competition, biomass distribution, and canopy organisation within a single 3D representation.
8.1 Building a QSF
Building a QSF is relatively straightforward. Users could simply loop over all trees and compute a QSM for each one. Arbor includes a function, qsf(), that performs this task in parallel and in an optimized manner.
As described in previous chapters, the variable las contains the segmented scene.
qsf = qsf(las)The qsf function is blazingly fast and can produce several hundred QSMs per minute! qsf() computes one QSM per tree but excludes trees that are in the buffer zone (see flag_buffer() in Section 6.6) or that are flagged as low understory (see flag_small_trees() in Section 6.5). This makes sense since flag_* functions are designed to exclude trees for which we cannot obtain good results.
A qsf object is simply a list with an additional class to enable convenient printing and plotting methods. Thus, qsf[[1]] contains a qsm object.
Be careful: by default, qsf() produces a QSM for every instance taller than 2 m. This can create a large number of QSMs with negligible volume, which is not always desirable. The function provides a parameter to control this threshold e.g. qsf(las, min_height = 4).
8.2 QSF Log Analysis
As mentioned earlier, arbor’s QSM module is not built like other QSM software on the market. It does not process one tree fed by a user but instead processes thousands of tree instances fed by a machine, including (among others) small saplings that are not measurable considering the accuracy of MLS sensors. In these cases, arbor’s function qsm() creates allometry-based QSMs with a warning (see section Section 7.7).
However, qsf() does not throw any warnings. Indeed, on a plot with e.g. 400 saplings and 200 young trees, this would generate at least 600 warnings. This is unmanageable for users. Instead, all QSMs record a message. Users can call qsf_log() to obtain a troubleshooting summary.
In the example below, we have 11 trees too short to be measurable according to allometry. These QSMs are purely allometry-based. We also have 7 trees without any measurement. Those trees are tall enough to be measured (in theory), but for some reason the QSM module found 0 valid measurements. Those trees should be inspected (spoiler: they are short trees with low foliage and indistinct trunks).
Users can query the logs. It is just a nested list. E.g.:
logs$W2The function qsf_filter() and its user-friendly derivatives allow the QSF object to be subset to retain only the trees of interest, without querying the log manually.
saplings <- qsf_filter_sapling(qsf)
nomeasure <- qsf_filter_nomeasure(qsf)
goodtrees <- qsf_filter_ok(qsf)The function qsm_treeid() allows the user to retrieve the tree ID of a QSM. It also applies to a QSF and thus allows the corresponding trees to be extracted from the point cloud. For example:
ids <- qsm_treeid(nomeasure)
cloud <- filter_poi(las, treeID %in% ids)
x <- plot_instance(cloud)
plot(nomeasure, add = x, pal = "chocolate4")It is also posible to use cloud = las %in% nomeasure as shortcut.
8.3 What To Do With a QSF?
8.3.1 3D Rendering
The first thing we can do with a QSF is render it in 3D within R.
plot(qsf, pal = "chocolate4") |> lidR::add_dtm3d(dtm)8.3.2 Tree Map & Volume Inventory
A QSF can also be used to perform complete forest inventories, producing a tree map with detailed information for each tree, such as basal area, volume, height, and other structural attributes.
tm <- qsf_treemap(qsf)8.3.3 Export to 3D Software
The qsf() function includes an option to export all trees into separate folders. Users can also use qsf_write() to export individual trees to formats such as .qsm, .obj, .stl, .ply, .csv or other formats supported by 3D software. For example, Blender can be used as an open-source solution for realistic rendering while CloudCompare can be use for casual rendering.
8.3.4 Reclassifying Wood and Foliage
This feature is experimental; it may or may not be removed in future versions.
Our semantic segmentation is not perfect. It intentionally over-segments in order to ensure that the entire tree branching structure is captured. As a result, this approach produces numerous false positives in foliage (by design) and some false negatives (not by design). The semantic segmentation is therefore not intended for direct scientific use; it is primarily designed to support QSM reconstruction.
With a QSF, it becomes possible to reclassify points based on each individual QSM. This reclassification can be useful for downstream analyses such as estimating Plant Area Index (PAI) or Leaf Area Index (LAI), where a more accurate separation between wood and foliage is required.
las <- qsf_segment_semantic(las, qsf)8.3.5 Merchandable Volume
See Chapter 9 for more information about forestry operations.
8.4 Code
8.4.1 Function list
qsf()to build a QSFqsf_write()for saving the QSMs in.obj,.ply.csvor.qsmfor analyzing or for rendering in 3D software.qsf_treemap()for saving forest in GIS vector filesqsf_log()to get a overview of troubleshootingqsf_segment_semantic()for more accurate semantic segmentationqsf_merchantable()(see Chapter 9)qsf_filter()to select QSMs of interest.plot()for 3D renderingqsm_stat()works with a QSF
8.4.2 Full Code
library(lidR)
library(arbor)
params <- arbor_parameters_default
params$global$cut_above_ground = 0.25
filter <- "-keep_random_fraction 0.25"
buffer <- 5
las <- readTLS(file, select = "xyz", filter = filter)
las <- hybrid_homogeneization(las)
las <- segment_ground(las)
las <- wood_likelihood(las, params)
las <- segment_semantic(las, params)
see <- find_seeds(las, params)
las <- segment_instance(las, see, params)
las <- flag_small_trees(las, max_height = 2)
las <- flag_buffer(las, see, -buffer)
qsf <- qsf(las, params = params)
log <- qsf_log(qsf)
plot(qsf)







