r-lidar / lidR

Airborne LiDAR data manipulation and visualisation for forestry application
https://CRAN.R-project.org/package=lidR
GNU General Public License v3.0
601 stars 131 forks source link

Add "by_echo" parameter to other *metrics functions #482

Closed ptompalski closed 3 years ago

ptompalski commented 3 years ago

JR, the new by_echo parameter in grid_metrics() is great, thanks for adding that functionality!

I think it is also needed in at least some of the other metrics functions. For example, when working on ABA predictions, the same sets of metrics are calculated for plots and for wall-to-wall rasters. If a user decides to use only first returns for wall-to-wall with grid_metrics(), they will also want to do the same when running plot_metrics() and possibly cloud_metrics(), tree_metrics() etc.

Jean-Romain commented 3 years ago

I do agree, and it is more or less already planned.

The way lidR is built implies a lot of work to achieve what you are asking because of the way lidR has been built (successive iteration and function additions). This is because all *_metrics() functions are independents and do not share a common code base. I only implemented your grid_metrics() request and I did it the dirty way to ensure minimal efforts in changing grid_metrics().

I started an important work for v4.0.0. I'll try to take advantage of the fact that I have to redesign stuff in depth to built more robust and versatile stuff around *_metrics(). My idea is to design a master layout_metrics() function that accept any kind of layout (raster, polygon, stars, ...). Then grid_metrics() and so on will only be convenient wrapper functions around the same base of code. It looks like that in my plan:

# cloud_metrics
layout_metrics(las, layout = NULL)
layout_metrics(las, layout = st_bbox(las))

# grid_metrics
layout_metrics(las, layout = raster)

# plot_metrics
layout_metrics(las, layout = spatial_points)

# hexbin_metrics
hexagon_tessellation = sf::st_make_grid(..., square = FALSE)
layout_metrics(las, layout = hexagon_tessellation)

# polygon_metrics (no equivalent)
layout_metrics(las, layout = polygons)

# grid_metrics but also with support of rotated, sheared and rectilinear grids
layout_metrics(las, layout = stars)

If layout_metrics() supports by_echo then by inheritance every other function will too. That's my plan

ptompalski commented 3 years ago

Thanks, JR, all of this makes sense of course.

Jean-Romain commented 3 years ago

So I worked on it today and I came with something very modular and very powerful driven by a single core code easily extendable. That's very promising:

library(lidR)
LASfile <- system.file("extdata", "MixedConifer.laz", package="lidR")
las = readLAS(LASfile)

layout = RasterLayer

Equivalent (but more versatile) to grid_metrics()

layout = raster::raster(extent(las), nrow = 10, ncol = 10)
u = layout_metrics(las, ~list(maxz = max(Z)), layout)
v = layout_metrics(las, ~list(maxz = max(Z)), layout, by_echo = c("all", "single"))
w = layout_metrics(las, ~list(maxz = max(Z), minz = min(Z)), layout, by_echo = c("all", "single"))
x = grid_metrics(las, ~list(maxz = max(Z), minz = min(Z)), 10, by_echo = c("single",  "multiple"))
plot(x, col = height.colors(25))

layout = bbox

Equivalent (but more versatile) to cloud_metrics()

layout = st_bbox(las)
u = layout_metrics(las, ~list(maxz = max(Z)), layout)
v = layout_metrics(las, ~list(maxz = max(Z)), layout, by_echo = c("all", "single"))
v = layout_metrics(las, ~list(maxz = max(Z), minz = min(Z)), layout, by_echo = c("all", "single"))
x = cloud_metrics(las, ~list(maxz = max(Z), minz = min(Z)), by_echo = c("single",  "multiple"))
print(x)
#> $maxz.single
#> [1] 32.07
#> 
#> $minz.single
#> [1] 0
#> 
#> $maxz.multiple
#> [1] 31.94
#> 
#> $minz.multiple
#> [1] 3.02

layout == stars

Does not support yet rotated, sheared and rectilinear grids but will be very versatile

layout = lidR:::st_grid_layout(las, 10)
u = layout_metrics(las, ~list(maxz = max(Z)), layout)
v = layout_metrics(las, ~list(maxz = max(Z)), layout, by_echo = c("all", "single"))
w = layout_metrics(las, ~list(maxz = max(Z), minz = min(Z)), layout, by_echo = c("all", "single"))
plot(w)
#> downsample set to c(0,0,1)

layout = custom groups

Drives tree_metrics() or voxel_metrics() and anything we can imagine

layout = las$treeID
u = layout_metrics(las, ~list(maxz = max(Z)), layout)
v = layout_metrics(las, ~list(maxz = max(Z)), layout, by_echo = c("all", "single"))
w = layout_metrics(las, ~list(maxz = max(Z), minz = min(Z)), layout, by_echo = c("all", "single"))

x = voxel_metrics(las, ~list(avgi = mean(Intensity)), res = 5, by_echo = c("all", "single"))
y = tree_metrics(las, ~list(avgi = mean(Intensity)), by_echo = c("all", "single"))
spplot(y)

layout = polygons

Many possible application including hexagon_metrics()

sfc = sf::st_as_sfc(st_bbox(las))
layout = sf::st_make_grid(sfc, cellsize = 10, square = FALSE)
u = layout_metrics(las, ~list(maxz = max(Z)), layout)
v = layout_metrics(las, ~list(maxz = max(Z)), layout, by_echo = c("all", "single"))
w = layout_metrics(las, ~list(maxz = max(Z), minz = min(Z)), layout, by_echo = c("all", "single"))
plot(w)

layout = layout[sample(1:length(layout), 15)]
u = layout_metrics(las, ~list(maxz = max(Z)), layout)
plot(u)

Rplot Rplot2

ptompalski commented 3 years ago

JR this is fantastic. Can I run it myself? Is it in devel already?

My only suggestion would be to use a different name for the function instead of layout_metrics() (and to keep function names somewhat consistent). If we try to use verb-noun style as with some other functions, then perhaps something like calculate_metrics() is better? (or calc_metrics() to make it shorter).

Jean-Romain commented 3 years ago

No you can't test that yet it is not in devel. It is in my local branch v4.0.0 in which I'm removing sp and raster based code. But I can put it online if you want.

I don't understand your problem with the name. layout_metrics seems consistent with grid_metrics, cloud_metrics, plot_metrics, voxel_metrics, point_metrics. And I will probably introduce hexagon_metrics and polygon_metrics

Jean-Romain commented 3 years ago

I pushed a pre-pre-pre-alpha version of lidR 4.0.0 in the branch v4. All functions have filter and by_echo. They all share the same core code. This gave me so much work and troubles :smile: . See changelog.