3 Rain stacking
We follow a similar approach as outlined in the rain segmentation approach, but this time we ‘stack’ the segmented across the different elevation scans, in essence to filter all scans from rain using the calculated maximum spatial extent of a rain cloud.
3.1 Problem
Let’s load a pvol
with rain and show what it looks like after range-bias correction.
We can see most rain is removed, but speckles remain in areas which mostly should be clear from precipitation.
classify_rain <- function(scan) {
if (scan$geo$elangle < 90) {
dpr <- as.cimg(scan$params[["DPR"]])
(dpr <= -12 & !is.na(dpr)) %>%
medianblur(4) %>% # Works adequate for de-speckling
threshold(0) %>% # Set all pixels that have been affected by median blurring to 1, rest to 0
split_connected() %>% # Split image in contiguous areas classified as rain
purrr::keep(~ sum(.) > 100) %>% # Only keep contiguous rain areas if area is > 50 pixels
parany() -> contiguous # Merge to 1 image again
if (!is.null(contiguous)) { # Only buffer if any rain areas of > 50 pixels are retained
contiguous %>%
distance_transform(1, 2) %>% # Calculate Euclidean distance (2nd argument) to pixels classified as 1
threshold(5) -> dpr_filter
dpr_filter <- as.matrix(-dpr_filter)
dpr_filter[dpr_filter == -1] <- NA
scan$params$RAIN <- dpr_filter
class(scan$params$RAIN) <- c("param", "matrix")
}
}
return(scan)
}
pvol$scans <- lapply(pvol$scans, function(x) {
x$params[["DBZH"]][is.na(x$params[["DPR"]])] <- NA
x <- classify_rain(x)
return(x)
})
ranges <- lapply(pvol$scans[1:15], function(x) {
data <- do.call(function(y) x$params[[y]], list("RAIN"))
class(data) <- "matrix"
range <- (1:dim(data)[1]) * x$geo$rscale
groundrange <- round(beam_distance(range, x$geo$elangle))
list(raster::flip(raster::raster(t(data), ymn = 0, ymx = 360, xmn = 0, xmx = max(range)), direction = "y"),
raster::flip(raster::raster(t(data), ymn = 0, ymx = 360, xmn = 0, xmx = max(groundrange)), direction = "y"))
})
get_largest_extent <- function(rasters) {
# https://gis.stackexchange.com/a/287081
extents <- sapply(rasters, FUN = function(x) {
raster::extent(x)
})
r <- raster(ext = extents[[1]], nrows = rasters[[1]]@nrows, ncols = rasters[[1]]@ncols)
max_extent <- sapply(extents, FUN = function(x) {
r <<- raster::extend(r, x)
})
raster::extent(r)
}
target_extent <- get_largest_extent(lapply(ranges, function(x) x[[2]]))
groundranges_points <- lapply(ranges, function(x) rasterToPoints(x[[2]], spatial = TRUE))
groundranges_coverage <- mapply(function(x, i) {
r <- x[[2]]
r[!is.na(r)] <- i
rasterToPoints(r, spatial = TRUE)
}, ranges, get_elevation_angles(pvol)[1:15])
gr <- do.call(rbind, groundranges_points)
gr_cov <- do.call(rbind, groundranges_coverage)
g <- rasterize(gr, raster(target_extent, res = c(500, 1)), "layer", fun = "count")
# g_cov <- rasterize(gr_cov, raster(target_extent, res = c(500, 1)), "layer", fun = "sum")
g_cov <- rasterize(gr_cov, raster(target_extent, res = c(500, 1)), "layer", fun = mean, na.rm = TRUE)
g <- g_cov
g_dist <- as.cimg(g_cov)
pvol$scans[1:15] <- mapply(function(scan, rainmask) {
cropped <- crop(g, rainmask[[2]])
resampled <- resample(cropped, rainmask[[1]])
scan$params$RAINSTACK <- t(as.matrix(flip(resampled, "y")))
class(scan$params$RAINSTACK) <- c("param", "matrix")
attributes(scan$params$RAINSTACK) <- attributes(scan$params$DBZH)
attr(scan$params$RAINSTACK, "param") <- "RAINSTACK"
return(scan)
}, pvol$scans[1:15], ranges, SIMPLIFY = FALSE)
pvol$scans <- lapply(pvol$scans, function(x) {
x$params[["DBZH"]][!is.na(x$params[["RAINSTACK"]])] <- NA
return(x)
})
ppi_filtered <- integrate_to_ppi(pvol, vp, xlim = c(-180000, 180000), ylim = c(-180000, 180000), res = 500, param = "DBZH")
## Warning in integrate_to_ppi(pvol, vp, xlim = c(-180000, 180000), ylim =
## c(-180000, : ignoring 90 degree birdbath scan
ppi_rainmask <- project_as_ppi(get_param(pvol$scans[[1]], "RAINSTACK"), grid_size = 500, range_max = 180000)
# (plot(ppi_filtered)) + plot(ppi_rainmask) +