I want to make QSMs of individual trees that were already segmented manually out of Arbor. I get errors.
Arbor expects point cloud prepared by Arbor that were segmented (semantic) by Arbor, hybrid decimated by Arbor and, super important, that have an attribute hag for Height Above Ground. Thus, applying qsm() on a single tree point cloud the users may get an error that stipulate that wood/foliage classification is missing or hag is missing. It means that segment_ground(), wood_likelihood() and segment_semantic() must be applied first. Sadly without ground ground points it is impossible to perform these operations. The solution is to use add_single_tree_ground() to add fake ground points.
Our dataset is already voxel-decimated at 1 cm, which is discouraged in this book. We do not have access to the raw point cloud. Can we still work with these data?
You are more likely to obtain acceptable results with TLS data than with MLS. Read the point cloud without applying additional read-time decimation (readTLS(x, select = "xyz")).
However, performance may be reduced compared with our recommended pipeline, particularly for instance segmentation, as some structural information has already been lost through prior voxel decimation. Any benchmarking or comparison should therefore clearly acknowledge this limitation and avoid directly comparing results with benchmarks or claims reported in this book that were obtained from our decimation pipeline.
11.3 Processing the Entire Point Cloud
Arbor is never processing the entire point cloud through its decimations pipeline. This is ok if the desired end product is the QSMs. But in our case the desired end product is a classified point cloud.
Arbor as a function transfer_attributes() designed for this very purpose. It transfers the attributes from a lower density point cloud to a higher density point cloud
11.4 Massive Datasets
Our dataset is massive (3 ha). The point cloud is very dense, and we do not have enough RAM to run Arbor.
Arbor is indeed memory-intensive and does not yet include built-in mechanisms for chunked processing. Below is the workflow we use to process such large datasets on a laptop.
Show the code
library(lidR)library(arbor)file ="file.las"display =FALSErandom_fraction =1cut_above_ground =0.15buffer_size =10par = arbor_parameters_defaultpar$global$cut_above_ground = cut_above_groundo <- tools::file_path_sans_ext(file)dir <-paste0(o, "_output")o =basename(o)tmp =paste0(dir, "/", "tmp")dir.create(dir, showWarnings =FALSE)dir.create(tmp, showWarnings =FALSE)ctg =readTLScatalog(file, select ="xyz0", filter =paste("-keep_random_fraction", random_fraction))opt_chunk_size(ctg) =50opt_chunk_buffer(ctg) = buffer_sizeplot(ctg, chunk = T)chunks = lidR::engine_chunks(ctg)maxID =0for (i inseq_along(chunks)){cat("==============\n")cat("Chunk", i, "of", length(chunks), "\n")cat("==============\n")# Regular pipeline chk <- chunks[[i]] las <-readLAS(chk) las <-hybrid_homogeneization(las) las <-segment_ground(las, par) las <-wood_likelihood(las, par) las <-segment_semantic(las, par) see <-find_seeds(las, par) las <-segment_instance(las, see, par) las <-colorize_trees(las, FALSE)# Custom tree remove bb <-st_bbox(chk) bb <- sf::st_as_sfc(bb) tm <- see@data[, .(x =mean(X), y =mean(Y)), by = treeID] tm <- sf::st_as_sf(tm, coords =c("x", "y")) seeds_roi <- sf::st_contains(bb, tm) seeds_roi <- tm[seeds_roi[[1]], ] seeds_roi_ids <-unique(seeds_roi$treeID) seeds_roi <-filter_poi(seeds, treeID %in% seeds_roi_ids) las <-filter_poi(las, treeID %in% seeds_roi_ids) las$treeID <- las$treeID + maxID maxID <-max(las@data$treeID) dtm <-rasterize_terrain(las, 0.1) dtm <- terra::crop(dtm, terra::vect(bb))# Export s <-paste0(tmp, "/", o, "_segmented_part", i, ".laz") r <-paste0(tmp, "/", o, "_dtm_part", i, ".tif")writeLAS(las, s) terra::writeRaster(dtm, r)# Clean uprm(las)rm(see)rm(dtm)gc()}gc()# Build the full DTMfiles =list.files(tmp, pattern ="dtm", full.names = T)dtm = terra::vrt(files)terra::writeRaster(dtm, paste0(dir, "/", o, "_dtm.tif"))# Build the full point cloud without bufferctg =readLAScatalog(tmp, pattern ="segmented", select ="xyzic0")las =readLAS(ctg)las =flag_buffer(las, buffer =-buffer_size)las =filter_poi(las, treeID >0)writeLAS(las, paste0(dir, "/", o, "_full.laz"))q("no")
11.5 Custom Allometric Model
We would like to change the allometric model used in Section 7.6. How can we use our own equation?
Because Arbor is a standalone C++ library, allowing users to inject arbitrary R code is technically challenging. Therefore, this feature has not been implemented yet.
However, feel free to contact us and we will gladly add your equation directly to the library.