/
fa_treepos.R
323 lines (287 loc) · 13.7 KB
/
fa_treepos.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
if (!isGeneric('treepos')) {
setGeneric('treepos', function(x, ...)
standardGeneric('treepos'))
}
#'@name treepos_GWS
#'@title Find potential tree positions using a canopy height model
#'
#'@description
#' Find potential tree positions using a canopy height model by using an iterative watershed algorithm. Basically returns a vector data sets with the tree crown geometries and a bunch of corresponding indices.
#'
#'@author Chris Reudenbach
#'
#'@param chm raster* canopy height model
#'@param minTreeAlt numeric. minimum height of trees to be integrated in the analysis
#'@param minTreeAltParam character. code for the percentile that is used as tree height treshold. It is build using the key letters \code{chmQ} and adding the percentile i.e. "10". Default is \code{chmQ20}
#'@param minCrownArea numeric. minimum area in square meter (if you use projected data) of the projected tree crowns
#'@param maxCrownArea numeric. maximum area in square meter (if you use projected data) of the projected tree crowns
#'@param join numeric. Join Segments based on Threshold Value, 0=no join, 1=treepos_GWS2saddle diff, 2=treepos_GWS2treepos diff. see also \href{http://www.saga-gis.org/saga_tool_doc/6.2.0/imagery_segmentation_0.html}{SAGA GIS Help}
#'@param thresh numeric. Specify a threshold value as minimum difference between neighboured segments in meter. see also \href{http://www.saga-gis.org/saga_tool_doc/6.2.0/imagery_segmentation_0.html}{SAGA GIS Help}
#'@param split split polygons default is TRUE
#'@param cores number of cores to be used
#'@param giLinks list. of GI tools cli paths
#'@return raster* object
#'
#'return raster* object
#'@export treepos_GWS
#'@examples
#'\dontrun{
#'
#' # required packages
#' require(uavRst)
#' require(link2GI)
#'
#' # create and check the links to the GI software
#' giLinks<-uavRst::linkGI()
#' if (giLinks$saga$exist & giLinks$otb$exist & giLinks$grass$exist) {
#'
#' # project folder
#' projRootDir<-tempdir()
#'
#' # create subfolders please mind that the pathes are exported as global variables
#' paths<-link2GI::initProj(projRootDir = projRootDir,
#' projFolders = c("data/","data/ref/","output/","run/","las/"),
#' global = TRUE,
#' path_prefix = "path_")
#'
#' data(chm_seg)
#'
#' # calculate treepos using uavRst generic approach
#' tPos <- uavRst::treepos_GWS(chm = chm_seg[[1]],
#' minTreeAlt = 2,
#' maxCrownArea = 150,
#' join = 1,
#' thresh = 0.35,
#' split=TRUE,
#' cores=1,
#' giLinks = giLinks )
#'}
#'##+}
#'
treepos_GWS <- function(chm = NULL,
minTreeAlt = 10,
minTreeAltParam = "chmQ20",
minCrownArea = 3,
maxCrownArea = 150,
join = 1, # 0=no join, 1=treepos2saddle diff, 2=treepos2treepos diff
thresh = 0.10, # threshold for join difference in m
split = TRUE,
cores = parallel::detectCores(),
giLinks = NULL
) {
if (!exists("path_run")) path_run = tempdir()
message("\n:: start crown identification...\n")
options(warn=-1)
if (is.null(giLinks)){
giLinks <- linkGI()
}
gdal <- giLinks$gdal
saga <- giLinks$saga
sagaCmd<-saga$sagaCmd
raster::writeRaster(chm,file.path(R.utils::getAbsolutePath(path_run),"chm.sdat"),bylayer=TRUE,overwrite = TRUE,NAflag = 0)
raster::writeRaster(chm,file.path(R.utils::getAbsolutePath(path_run),"chm.tif"),overwrite = TRUE,NAflag = 0)
#r2saga(chm,"chm")
message(":: run pre-segmentation...\n")
# first segment run is a simple watershed segmentation just for deriving more reliable treepos´
# TODO improve different advanceds treepos finding algorithms
ret <- system(paste0(sagaCmd, " imagery_segmentation 0 ",
" -GRID " ,file.path(R.utils::getAbsolutePath(path_run),"chm.sgrd"),
" -SEGMENTS " ,file.path(R.utils::getAbsolutePath(path_run),"dummyCrownSegments.sgrd"),
" -SEEDS " ,file.path(R.utils::getAbsolutePath(path_run),"treepos.shp"),
" -OUTPUT 0",
" -DOWN 1" ,
" -JOIN " ,join,
" -THRESHOLD ",thresh,
" -EDGE 0")
,intern = TRUE)
# convert filtered crown clumps to shape format for descriptive running statitics
ret <- system(paste0(sagaCmd, " shapes_grid 6 ",
" -GRID " ,file.path(R.utils::getAbsolutePath(path_run),"dummyCrownSegments.sgrd"),
" -POLYGONS ",file.path(R.utils::getAbsolutePath(path_run),"dummyCrownSegment.shp"),
" -CLASS_ALL 1",
" -CLASS_ID 1.000000",
" -SPLIT 1"),
intern = TRUE)
message(":: filter results...\n")
#
message(":: find max height position...\n")
if (cores<2) para<-0
else para = 1
dummycrownsStat <- uavRst::poly_stat(c("chm"),
spdf =file.path(R.utils::getAbsolutePath(path_run),"dummyCrownSegment.shp"),
parallel = para)
trees_crowns <- crown_filter(crownFn = dummycrownsStat,
minTreeAlt = minTreeAlt,
minCrownArea = minCrownArea,
maxCrownArea = maxCrownArea,
minTreeAltParam = minTreeAltParam
)
rgdal::writeOGR(obj = trees_crowns[[2]],
layer = "dummyCrownSegment",
driver = "ESRI Shapefile",
dsn = file.path(R.utils::getAbsolutePath(path_run)),
overwrite_layer = TRUE)
message(":: find max height position...\n")
ts <- poly_maxpos(file.path(R.utils::getAbsolutePath(path_run),"chm.tif"),
file.path(R.utils::getAbsolutePath(path_run),"dummyCrownSegment"),
polySplit = split,
cores = cores)
# create raw zero mask ts[[1]] = seeds ts[[2]] = maxpos
treepos <- ts[[1]] * chm
raster::writeRaster(treepos,
file.path(R.utils::getAbsolutePath(path_run),"treepos0.sdat"),
overwrite = TRUE,
NAflag = 0)
# reclass extracted treeposs to minTreeAlt
ret <- system(paste0(sagaCmd, " grid_tools 15 ",
" -INPUT " ,file.path(R.utils::getAbsolutePath(path_run),"treepos0.sgrd"),
" -RESULT " ,file.path(R.utils::getAbsolutePath(path_run),"treepos.sgrd"),
" -METHOD 0 ",
" -OLD " ,minTreeAlt ,
" -NEW 0.00000",
" -SOPERATOR 1",
" -NODATAOPT 0",
" -OTHEROPT 0",
" -RESULT_NODATA_CHOICE 1 ",
" -RESULT_NODATA_VALUE 0.000000")
,intern = TRUE)
# TODO SF
# trees <- sf::st_read(paste0(path_run,"treepos.shp"))
localmaxima<-raster::raster(file.path(R.utils::getAbsolutePath(path_run),"treepos.sdat"))
localmaxima@crs <- chm@crs
# workaround for strange effects with SAGA
# even if all params are identical it is dealing with different grid systems
localmaxima<-raster::resample(localmaxima, chm , method = 'bilinear')
localmaxima[localmaxima<=0]<-0
# remove temporary files
flist<-list()
flist<-append(flist, Sys.glob(file.path(R.utils::getAbsolutePath(path_run),"treepos0.*")))
flist<-append(flist, Sys.glob(file.path(R.utils::getAbsolutePath(path_run),"dummyCrownSegment*")))
flist<-append(flist, Sys.glob(file.path(R.utils::getAbsolutePath(path_run),"treepos.*")))
flist<-append(flist, Sys.glob(file.path(R.utils::getAbsolutePath(path_run),"chmStat.*")))
flist<-append(flist, Sys.glob(file.path(R.utils::getAbsolutePath(path_run),"polyStat.*")))
res<-file.remove(unlist(flist))
return(localmaxima)
}
#' 'rLiDAR' based tree detection of a LiDAR-derived Canopy Height Model (CHM)
#' @description Detects and computes the location and height of individual trees within
#' the LiDAR-derived Canopy Height Model (CHM). The algorithm implemented in this function
#' is local maximum with a fixed window size. Carlos A. Silva et all.: R package \href{https://CRAN.R-project.org/package=rLiDAR}{rLiDAR}\cr
#' @param chm Canopy height model in \code{raster} or \code{SpatialGridDataFrame} file format. Should be the same that was used to create
#' the input for \code{treepos}.
#' @param movingWin Size (in pixels) of the moving window to detect local maxima.
#' @param minTreeAlt Height threshold (m) below a pixel cannot be a local maximum. Local maxima values are used to define tree tops.
#' @export treepos_RL
#' @return raster* object
#' @examples
#' \dontrun{
#' ## required packages
#' require(uavRst)
#'
#' ## load data
#' data(chm_seg)
#'
#' ## find trees
#' tPosRL <- treepos_RL(chm = chm_seg[[1]],
#' movingWin = 3,
#' minTreeAlt = 10)
#' ## visualisation
#' raster::plot(tPosRL)
#' }
treepos_RL <- function(chm =NULL,
movingWin = 7,
minTreeAlt = 2) {
# if (class(treepos) %in% c("RasterLayer", "RasterStack", "RasterBrick")) {
# chm <- raster::raster(chm)
# }
localmaxima <- raster::rasterFromXYZ(rLiDAR::FindTreesCHM(chm, fws = movingWin, minht=minTreeAlt))
localmaxima@crs <- chm@crs
return(localmaxima)
}
#' @title tree top detection based on local maxima filters as provided by 'lidR'
#' @description Tree top detection based on local maxima filters. There are two types of filter. The first,
#' called for gridded objects, works on images with a matrix-based algorithm. And the second one, called for
#' point clouds, works at the point cloud level without any rasterization. Jean-Romain Roussel and David Auty:
#' R package \href{https://CRAN.R-project.org/package=lidR}{lidR}\cr
#' @param chm Canopy height model in \code{raster}, \code{lasmetrics}, \code{matrix} or object of \code{class LAS}.
#' Should be the same that was used to create
#' the input for \code{treepos}.
#' @param movingWin Size (in pixels) of the moving window to detect local maxima.
#' @param minTreeAlt Height threshold (m) below a pixel cannot be a local maximum. Local maxima values are used to define tree tops.
#' @return raster* object
#' @export treepos_lidR
#' @examples
#' \dontrun{
#'require(uavRst)
#'## required packages
#'require(uavRst)
#'
#' data(chm_seg)
#'
#'## find trees
#'tPoslidR <- treepos_lidR(chm = chm_seg[[1]],
#' movingWin = 3,
#' minTreeAlt = 15)
#'## visualisation
#' # mapview::mapview(tPoslidR)
#' raster::plot(tPoslidR)
#' }
treepos_lidR <- function(chm =NULL,
movingWin = 7,
minTreeAlt = 2) {
# if (class(treepos) %in% c("RasterLayer", "RasterStack", "RasterBrick")) {
# chm <- raster::raster(chm)
# }
localmaxima <- lidR::tree_detection(x = chm, ws=movingWin, hmin = minTreeAlt)
localmaxima@crs <- chm@crs
return(localmaxima)
}
#' 'ForestTools' tree top detection
#' @description Implements the variable window filter algorithm (Popescu & Wynne, 2004)
#' for detecting treetops from a canopy height model. Andrew Plowright:
#' R package \href{https://CRAN.R-project.org/package=ForestTools}{ForestTools}\cr
#' @param chm Canopy height model in \code{raster}, \code{lasmetrics}, \code{matrix} or object of \code{class LAS}.
#' Should be the same that was used to create
#' the input for \code{treepos}.
#' @param winFun function. The function that determines the size of the window at any given
#' location on the canopy. It should take the value of a given CHM pixel as its only argument,
#' and return the desired radius of the circular search window when centered on that pixel.
#' @param minTreeAlt Height threshold (m) below a pixel cannot be a local maximum. Local maxima values are used to define tree tops.
#' @param maxCrownArea numeric. A single value of the maximum individual tree crown radius expected.
#' @param verbose quiet (1)
#' height of \code{treepos}.
#' @return raster* object
#' @export treepos_FT
#' @examples
#' ##- required packages
#' require(uavRst)
#'
#' data(chm_seg)
#'
#' ##- call ForestTools treepos function
#' tposFT <- treepos_FT(chm = chm_seg[[1]],
#' minTreeAlt = 10,
#' maxCrownArea = 150)
#'
#' ##- visualize it
#' # mapview::mapview(tposFT)
#' raster::plot(tposFT)
treepos_FT <- function(chm =NULL,
winFun = function(x){0.5 * ((x^2) * 0.0090 + 2.51)},
minTreeAlt = 2,
maxCrownArea = maxCrownArea,
verbose = TRUE) {
# if (class(treepos) %in% c("RasterLayer", "RasterStack", "RasterBrick")) {
# chm <- raster::raster(chm)
# }
maxcrown <- sqrt(maxCrownArea/ pi) * 4 * 1/raster::res(chm)[[1]]
localmaxima <- ForestTools::vwf(CHM = chm,
winFun = winFun,
minHeight = minTreeAlt,
maxWinDiameter = ceiling(maxcrown),
verbose = verbose)
# create raw zero mask
treepos <- 0 * chm
localmaxima<-raster::rasterize(localmaxima,treepos)
return(localmaxima)
}