From 8e6199c983953e2a19440091e8ff5581657098bb Mon Sep 17 00:00:00 2001 From: Anatolii Tsyplenkov Date: Thu, 9 Jan 2025 15:25:13 +1300 Subject: [PATCH 1/6] ci: update codecov workflow --- .github/workflows/test-coverage.yaml | 37 +++++++++++++++++----------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml index e9736f1..0072f90 100644 --- a/.github/workflows/test-coverage.yaml +++ b/.github/workflows/test-coverage.yaml @@ -1,12 +1,12 @@ # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help on: + # workflow_dispatch: push: branches: [main, master] pull_request: - branches: [main, master] -name: test-coverage +name: test-coverage.yaml permissions: read-all @@ -15,8 +15,6 @@ jobs: runs-on: ubuntu-latest env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - R_KEEP_PKG_SOURCE: yes - _R_CHECK_CRAN_INCOMING_: false steps: - uses: actions/checkout@v4 @@ -27,20 +25,31 @@ jobs: - uses: r-lib/actions/setup-r-dependencies@v2 with: - pak-version: devel - upgrade: 'TRUE' - cache-version: 2 - extra-packages: | - any::covr - local::. + extra-packages: any::covr, any::xml2 needs: coverage - name: Test coverage run: | - options(crayon.enabled = TRUE) - covr::codecov( + cov <- covr::package_coverage( quiet = FALSE, clean = FALSE, - token = "${{secrets.CODECOV_TOKEN}}" + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") ) - shell: Rscript {0} \ No newline at end of file + covr::to_cobertura(cov) + shell: Rscript {0} + + - uses: codecov/codecov-action@v4 + with: + # Fail if error if not on PR, or if on PR and token is given + fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package \ No newline at end of file From 46b4539744770fc8c631220b213b1410e69343ab Mon Sep 17 00:00:00 2001 From: Anatolii Tsyplenkov Date: Thu, 9 Jan 2025 16:00:51 +1300 Subject: [PATCH 2/6] feat: min max mode filters --- NAMESPACE | 4 +- R/filters.R | 262 +++++++++++++++++++++-- R/utils_documentation.R | 28 +-- man/expect_snapshot.Rd | 16 -- man/wbw_adaptive_filter.Rd | 4 +- man/wbw_conservative_smoothing_filter.Rd | 4 +- man/wbw_high_pass_filter.Rd | 4 +- man/wbw_high_pass_median_filter.Rd | 4 +- man/wbw_majority_filter.Rd | 40 ++++ man/wbw_maximum_filter.Rd | 40 ++++ man/wbw_mean_filter.Rd | 4 +- man/wbw_median_filter.Rd | 4 +- man/wbw_minimum_filter.Rd | 40 ++++ tests/tinytest/setup.R | 5 - tests/tinytest/test_filter.R | 219 ++++++++++++------- 15 files changed, 521 insertions(+), 157 deletions(-) delete mode 100644 man/expect_snapshot.Rd create mode 100644 man/wbw_majority_filter.Rd create mode 100644 man/wbw_maximum_filter.Rd create mode 100644 man/wbw_minimum_filter.Rd diff --git a/NAMESPACE b/NAMESPACE index 0d089ee..94f7851 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -18,7 +18,6 @@ export(as_matrix) export(as_rast) export(as_vector) export(as_wbw_raster) -export(expect_snapshot) export(num_cells) export(print_geotiff_tags) export(stdev) @@ -40,9 +39,12 @@ export(wbw_install) export(wbw_is_float) export(wbw_is_int) export(wbw_is_rgb) +export(wbw_majority_filter) export(wbw_max_procs) +export(wbw_maximum_filter) export(wbw_mean_filter) export(wbw_median_filter) +export(wbw_minimum_filter) export(wbw_multidirectional_hillshade) export(wbw_random_sample) export(wbw_read_raster) diff --git a/R/filters.R b/R/filters.R index 5391975..4db9133 100644 --- a/R/filters.R +++ b/R/filters.R @@ -13,8 +13,8 @@ #' assigned the mean value. Otherwise, it retains its original value. #' #' Neighbourhood size, or filter size, is specified in the x and y dimensions -#' using `filter_size_x` and `filter_size_y` These dimensions should be odd, -#' positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). #' #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size @@ -168,8 +168,8 @@ S7::method(wbw_bilateral_filter, WhiteboxRaster) <- #' [wbw_bilateral_filter] or [wbw_gaussian_filter]. #' #' Neighbourhood size, or filter size, is specified in the x and y dimensions -#' using `filter_size_x` and `filter_size_y` These dimensions should be odd, -#' positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). #' #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size @@ -311,8 +311,8 @@ S7::method(wbw_gaussian_filter, WhiteboxRaster) <- #' minimum value. #' #' Neighbourhood size, or filter size, is specified in the x and y dimensions -#' using `filter_size_x` and `filter_size_y` These dimensions should be odd, -#' positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). #' #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size @@ -386,8 +386,8 @@ S7::method(wbw_conservative_smoothing_filter, WhiteboxRaster) <- #' #' @details #' Neighbourhood size, or filter size, is specified in the x and y dimensions -#' using `filter_size_x` and `filter_size_y` These dimensions should be odd, -#' positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). #' #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size @@ -461,8 +461,8 @@ S7::method(wbw_high_pass_filter, WhiteboxRaster) <- #' #' @details #' Neighbourhood size, or filter size, is specified in the x and y dimensions -#' using `filter_size_x` and `filter_size_y` These dimensions should be odd, -#' positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). #' #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size @@ -538,23 +538,23 @@ S7::method(wbw_high_pass_median_filter, WhiteboxRaster) <- #' @keywords image_processing #' #' @description -#' This tool performs a median filter on a raster image. Median filters, a -#' type of low-pass filter, can be used to emphasize the longer-range -#' variability in an image, effectively acting to smooth the image. This +#' This tool performs a median filter on a raster image. Median filters, a +#' type of low-pass filter, can be used to emphasize the longer-range +#' variability in an image, effectively acting to smooth the image. This #' can be useful for reducing the noise in an image. #' #' @details -#' The algorithm operates by calculating the median value (middle value in +#' The algorithm operates by calculating the median value (middle value in #' a sorted list) in a moving window centred on each grid cell. Specifically, #' this tool uses the efficient running-median filtering algorithm of -#' Huang et al. (1979). The median value is not influenced by -#' anomolously high or low values in the distribution to the extent -#' that the average is. As such, the median filter is far less sensitive +#' Huang et al. (1979). The median value is not influenced by +#' anomolously high or low values in the distribution to the extent +#' that the average is. As such, the median filter is far less sensitive #' to shot noise in an image than the mean filter. -#' +#' #' Neighbourhood size, or filter size, is specified in the x and y dimensions -#' using `filter_size_x` and `filter_size_y` These dimensions should be odd, -#' positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). #' #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size @@ -567,10 +567,10 @@ S7::method(wbw_high_pass_median_filter, WhiteboxRaster) <- #' [wbw_high_pass_median_filter()] #' #' @references -#' Huang, T., Yang, G.J.T.G.Y. and Tang, G., 1979. A fast two-dimensional +#' Huang, T., Yang, G.J.T.G.Y. and Tang, G., 1979. A fast two-dimensional #' median filtering algorithm. IEEE Transactions on Acoustics, Speech, and #' Signal Processing, 27(1), pp.13-18. -#' +#' #' @eval rd_wbw_link("median_filter") #' @eval rd_example("wbw_median_filter", #' c("filter_size_x = 3L", "filter_size_y = 3L")) @@ -629,3 +629,221 @@ S7::method(wbw_median_filter, WhiteboxRaster) <- source = out ) } + +#' Majority Filter +#' @keywords image_processing +#' +#' @description +#' Assigns each cell in the output grid the most frequently occurring value +#' (mode) in a moving window centred on each grid cell in the input raster. +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_mean_filter()], [wbw_high_pass_filter()], +#' [wbw_high_pass_median_filter()] +#' +#' @eval rd_wbw_link("majority_filter") +#' @eval rd_example("wbw_majority_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_majority_filter <- + S7::new_generic( + name = "wbw_majority_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_majority_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + + # Filter + out <- + wbe$majority_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } + + +#' Maximum Filter +#' @keywords image_processing +#' +#' @description +#' Assigns each cell in the output grid the maximum value in a moving window +#' centred on each grid cell in the input raster. +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_mean_filter()], [wbw_high_pass_filter()], +#' [wbw_high_pass_median_filter()] +#' +#' @eval rd_wbw_link("maximum_filter") +#' @eval rd_example("wbw_maximum_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_maximum_filter <- + S7::new_generic( + name = "wbw_maximum_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_maximum_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + + # Filter + out <- + wbe$maximum_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } + + +#' Minimum Filter +#' @keywords image_processing +#' +#' @description +#' Assigns each cell in the output grid the minimum value in a moving window +#' centred on each grid cell in the input raster. +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_mean_filter()], [wbw_high_pass_filter()], +#' [wbw_high_pass_median_filter()] +#' +#' @eval rd_wbw_link("minimum_filter") +#' @eval rd_example("wbw_minimum_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_minimum_filter <- + S7::new_generic( + name = "wbw_minimum_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_minimum_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + + # Filter + out <- + wbe$minimum_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } diff --git a/R/utils_documentation.R b/R/utils_documentation.R index bf6e600..a9e327f 100644 --- a/R/utils_documentation.R +++ b/R/utils_documentation.R @@ -55,30 +55,4 @@ rd_example <- # "}", sep = "\n" ) - } - -#' Tinytest Snapshot -#' https://github.com/etiennebacher/astgrepr/ -#' blob/ea91137bdb10d22c7a988a8cb1b0bc896935fb0d/R/tinytest.R -#' -#' @keywords internal -#' @export -expect_snapshot <- function(label, current) { - snapshot_file <- file.path("_snapshots", paste0(label, ".txt")) - current2 <- paste(utils::capture.output(print(current)), collapse = "\n") - - if (!dir.exists(dirname(snapshot_file))) { - dir.create(dirname(snapshot_file), showWarnings = FALSE, recursive = TRUE) - } - if (!file.exists(snapshot_file)) { - cat(current2, file = snapshot_file, sep = "\n") - message("Creating file ", snapshot_file) - return(invisible()) - } - target <- paste(readLines(snapshot_file, warn = FALSE), collapse = "\n") - tinytest::tinytest( - result = identical(current2, target), - call = sys.call(sys.parent(1)), - diff = paste0("Check content of ", snapshot_file) - ) -} + } \ No newline at end of file diff --git a/man/expect_snapshot.Rd b/man/expect_snapshot.Rd deleted file mode 100644 index 3ffd422..0000000 --- a/man/expect_snapshot.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils_documentation.R -\name{expect_snapshot} -\alias{expect_snapshot} -\title{Tinytest Snapshot -https://github.com/etiennebacher/astgrepr/ -blob/ea91137bdb10d22c7a988a8cb1b0bc896935fb0d/R/tinytest.R} -\usage{ -expect_snapshot(label, current) -} -\description{ -Tinytest Snapshot -https://github.com/etiennebacher/astgrepr/ -blob/ea91137bdb10d22c7a988a8cb1b0bc896935fb0d/R/tinytest.R -} -\keyword{internal} diff --git a/man/wbw_adaptive_filter.Rd b/man/wbw_adaptive_filter.Rd index a613a26..ec052f6 100644 --- a/man/wbw_adaptive_filter.Rd +++ b/man/wbw_adaptive_filter.Rd @@ -30,8 +30,8 @@ center cell value exceeds the user-defined threshold, the output cell is assigned the mean value. Otherwise, it retains its original value. Neighbourhood size, or filter size, is specified in the x and y dimensions -using \code{filter_size_x} and \code{filter_size_y} These dimensions should be odd, -positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). } \examples{ f <- system.file("extdata/dem.tif", package = "wbw") diff --git a/man/wbw_conservative_smoothing_filter.Rd b/man/wbw_conservative_smoothing_filter.Rd index 3fc9a7a..817e6f2 100644 --- a/man/wbw_conservative_smoothing_filter.Rd +++ b/man/wbw_conservative_smoothing_filter.Rd @@ -33,8 +33,8 @@ the corresponding grid cell in the output image is replaced with the minimum value. Neighbourhood size, or filter size, is specified in the x and y dimensions -using \code{filter_size_x} and \code{filter_size_y} These dimensions should be odd, -positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). } \examples{ f <- system.file("extdata/dem.tif", package = "wbw") diff --git a/man/wbw_high_pass_filter.Rd b/man/wbw_high_pass_filter.Rd index 07bfff9..177ac49 100644 --- a/man/wbw_high_pass_filter.Rd +++ b/man/wbw_high_pass_filter.Rd @@ -25,8 +25,8 @@ neighbourhood (i.e. window.) } \details{ Neighbourhood size, or filter size, is specified in the x and y dimensions -using \code{filter_size_x} and \code{filter_size_y} These dimensions should be odd, -positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). } \examples{ f <- system.file("extdata/dem.tif", package = "wbw") diff --git a/man/wbw_high_pass_median_filter.Rd b/man/wbw_high_pass_median_filter.Rd index 628e5cd..586cf2e 100644 --- a/man/wbw_high_pass_median_filter.Rd +++ b/man/wbw_high_pass_median_filter.Rd @@ -32,8 +32,8 @@ surrounding neighbourhood (i.e. window.) } \details{ Neighbourhood size, or filter size, is specified in the x and y dimensions -using \code{filter_size_x} and \code{filter_size_y} These dimensions should be odd, -positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). } \examples{ f <- system.file("extdata/dem.tif", package = "wbw") diff --git a/man/wbw_majority_filter.Rd b/man/wbw_majority_filter.Rd new file mode 100644 index 0000000..c705590 --- /dev/null +++ b/man/wbw_majority_filter.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_majority_filter} +\alias{wbw_majority_filter} +\title{Majority Filter} +\usage{ +wbw_majority_filter(x, filter_size_x = 11L, filter_size_y = 11L) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +Assigns each cell in the output grid the most frequently occurring value +(mode) in a moving window centred on each grid cell in the input raster. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_majority_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#majority_filter} +} +\seealso{ +\code{\link[=wbw_mean_filter]{wbw_mean_filter()}}, \code{\link[=wbw_high_pass_filter]{wbw_high_pass_filter()}}, +\code{\link[=wbw_high_pass_median_filter]{wbw_high_pass_median_filter()}} +} +\keyword{image_processing} diff --git a/man/wbw_maximum_filter.Rd b/man/wbw_maximum_filter.Rd new file mode 100644 index 0000000..b0a3a7b --- /dev/null +++ b/man/wbw_maximum_filter.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_maximum_filter} +\alias{wbw_maximum_filter} +\title{Maximum Filter} +\usage{ +wbw_maximum_filter(x, filter_size_x = 11L, filter_size_y = 11L) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +Assigns each cell in the output grid the maximum value in a moving window +centred on each grid cell in the input raster. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_maximum_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#maximum_filter} +} +\seealso{ +\code{\link[=wbw_mean_filter]{wbw_mean_filter()}}, \code{\link[=wbw_high_pass_filter]{wbw_high_pass_filter()}}, +\code{\link[=wbw_high_pass_median_filter]{wbw_high_pass_median_filter()}} +} +\keyword{image_processing} diff --git a/man/wbw_mean_filter.Rd b/man/wbw_mean_filter.Rd index cea106d..d7f7e5f 100644 --- a/man/wbw_mean_filter.Rd +++ b/man/wbw_mean_filter.Rd @@ -27,8 +27,8 @@ their smoothing compared to edge-preserving alternatives like the \link{wbw_bilateral_filter} or \link{wbw_gaussian_filter}. Neighbourhood size, or filter size, is specified in the x and y dimensions -using \code{filter_size_x} and \code{filter_size_y} These dimensions should be odd, -positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). } \examples{ f <- system.file("extdata/dem.tif", package = "wbw") diff --git a/man/wbw_median_filter.Rd b/man/wbw_median_filter.Rd index 0361e09..5220539 100644 --- a/man/wbw_median_filter.Rd +++ b/man/wbw_median_filter.Rd @@ -34,8 +34,8 @@ that the average is. As such, the median filter is far less sensitive to shot noise in an image than the mean filter. Neighbourhood size, or filter size, is specified in the x and y dimensions -using \code{filter_size_x} and \code{filter_size_y} These dimensions should be odd, -positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). } \examples{ f <- system.file("extdata/dem.tif", package = "wbw") diff --git a/man/wbw_minimum_filter.Rd b/man/wbw_minimum_filter.Rd new file mode 100644 index 0000000..6cb034f --- /dev/null +++ b/man/wbw_minimum_filter.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_minimum_filter} +\alias{wbw_minimum_filter} +\title{Minimum Filter} +\usage{ +wbw_minimum_filter(x, filter_size_x = 11L, filter_size_y = 11L) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +Assigns each cell in the output grid the minimum value in a moving window +centred on each grid cell in the input raster. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_minimum_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#minimum_filter} +} +\seealso{ +\code{\link[=wbw_mean_filter]{wbw_mean_filter()}}, \code{\link[=wbw_high_pass_filter]{wbw_high_pass_filter()}}, +\code{\link[=wbw_high_pass_median_filter]{wbw_high_pass_median_filter()}} +} +\keyword{image_processing} diff --git a/tests/tinytest/setup.R b/tests/tinytest/setup.R index 336d115..bfb5f99 100644 --- a/tests/tinytest/setup.R +++ b/tests/tinytest/setup.R @@ -17,8 +17,3 @@ skip_if_not_installed <- function(pkg) { exit_file("Package", pkg, "not available") } } - -register_tinytest_extension( - "wbw", - "expect_snapshot" -) \ No newline at end of file diff --git a/tests/tinytest/test_filter.R b/tests/tinytest/test_filter.R index 6c452aa..f1c0260 100644 --- a/tests/tinytest/test_filter.R +++ b/tests/tinytest/test_filter.R @@ -1,92 +1,163 @@ -source("setup.R") +source("tests/tinytest/setup.R") +# source("setup.R") # Test adaptive filter failures -expect_error(wbw_adaptive_filter(x, filter_size_x = 10L)) -expect_error(wbw_adaptive_filter(x, filter_size_y = 2)) -expect_error(wbw_adaptive_filter(x, filter_size_y = c(1:2))) -expect_error(wbw_adaptive_filter(x, filter_size_y = 1.5)) -expect_error(wbw_adaptive_filter(x, threshold = "a")) -expect_error(wbw_adaptive_filter("x", threshold = "a")) +# expect_error(wbw_adaptive_filter(x, filter_size_x = 10L)) +# expect_error(wbw_adaptive_filter(x, filter_size_y = 2)) +# expect_error(wbw_adaptive_filter(x, filter_size_y = c(1:2))) +# expect_error(wbw_adaptive_filter(x, filter_size_y = 1.5)) +# expect_error(wbw_adaptive_filter(x, threshold = "a")) +# expect_error(wbw_adaptive_filter("x", threshold = "a")) -# Test bilateral filter failures -expect_error(wbw_bilateral_filter(x, sigma_dist = 10L)) -expect_error(wbw_bilateral_filter(x, sigma_int = -2)) -expect_error(wbw_bilateral_filter(x, sigma_dist = c(1:2))) -expect_error(wbw_bilateral_filter(x, sigma_dist = 20.1)) -expect_error(wbw_bilateral_filter(x, sigma_dist = "a")) -expect_error(wbw_bilateral_filter("x", sigma_int = "a")) +# # Test bilateral filter failures +# expect_error(wbw_bilateral_filter(x, sigma_dist = 10L)) +# expect_error(wbw_bilateral_filter(x, sigma_int = -2)) +# expect_error(wbw_bilateral_filter(x, sigma_dist = c(1:2))) +# expect_error(wbw_bilateral_filter(x, sigma_dist = 20.1)) +# expect_error(wbw_bilateral_filter(x, sigma_dist = "a")) +# expect_error(wbw_bilateral_filter("x", sigma_int = "a")) -# Test mean filter failures -expect_error(wbw_mean_filter(x, filter_size_x = 10L)) -expect_error(wbw_mean_filter(x, filter_size_y = 2)) -expect_error(wbw_mean_filter(x, filter_size_y = c(1:2))) -expect_error(wbw_mean_filter(x, filter_size_y = 1.5)) -expect_error(wbw_mean_filter(x, filter_size_y = "a")) -expect_error(wbw_mean_filter("x", filter_size_y = "a")) +# # Test mean filter failures +# expect_error(wbw_mean_filter(x, filter_size_x = 10L)) +# expect_error(wbw_mean_filter(x, filter_size_y = 2)) +# expect_error(wbw_mean_filter(x, filter_size_y = c(1:2))) +# expect_error(wbw_mean_filter(x, filter_size_y = 1.5)) +# expect_error(wbw_mean_filter(x, filter_size_y = "a")) +# expect_error(wbw_mean_filter("x", filter_size_y = "a")) -# Test conservative smoothing filter failures -expect_error(wbw_conservative_smoothing_filter(x, filter_size_x = 10L)) -expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = 2)) -expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = c(1:2))) -expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = 1.5)) -expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = "a")) -expect_error(wbw_conservative_smoothing_filter("x", filter_size_y = "a")) +# # Test conservative smoothing filter failures +# expect_error(wbw_conservative_smoothing_filter(x, filter_size_x = 10L)) +# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = 2)) +# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = c(1:2))) +# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = 1.5)) +# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = "a")) +# expect_error(wbw_conservative_smoothing_filter("x", filter_size_y = "a")) -# Test high pass filter failures -expect_error(wbw_high_pass_filter(x, filter_size_x = 10, filter_size_y = 11)) -expect_error(wbw_high_pass_filter(x, filter_size_x = 11, filter_size_y = 10)) -expect_error(wbw_high_pass_filter(x, filter_size_x = 11.1, filter_size_y = 11)) -expect_error(wbw_high_pass_filter("x", filter_size_x = 11, filter_size_y = 11)) +# # Test high pass filter failures +# expect_error(wbw_high_pass_filter(x, filter_size_x = 10, filter_size_y = 11)) +# expect_error(wbw_high_pass_filter(x, filter_size_x = 11, filter_size_y = 10)) +# expect_error(wbw_high_pass_filter(x, filter_size_x = 11.1, filter_size_y = 11)) +# expect_error(wbw_high_pass_filter("x", filter_size_x = 11, filter_size_y = 11)) -# Test successful filter returns -expect_inherits(wbw_adaptive_filter(x), c("wbw::WhiteboxRaster", "S7_object")) -expect_inherits(wbw_bilateral_filter(x), c("wbw::WhiteboxRaster", "S7_object")) -expect_inherits(wbw_mean_filter(x), c("wbw::WhiteboxRaster", "S7_object")) -expect_inherits(wbw_conservative_smoothing_filter(x), c("wbw::WhiteboxRaster", "S7_object")) -expect_inherits(wbw_high_pass_filter(x), c("wbw::WhiteboxRaster", "S7_object")) -expect_inherits(wbw_gaussian_filter(x), c("wbw::WhiteboxRaster", "S7_object")) +# # Test high pass median filter failures +# expect_error(wbw_high_pass_median_filter(x, filter_size_x = 10, filter_size_y = 11)) +# expect_error(wbw_high_pass_median_filter(x, filter_size_x = 11, filter_size_y = 10)) +# expect_error(wbw_high_pass_median_filter(x, filter_size_x = 11.1, filter_size_y = 11)) +# expect_error(wbw_high_pass_median_filter("x", filter_size_x = 11, filter_size_y = 11)) -# Snapshots -expect_snapshot( - label = "wbw_adaptive_filter", - wbw_adaptive_filter(x) +# Test successful filter returns +expect_inherits( + wbw_adaptive_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) +expect_inherits( + wbw_bilateral_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) +expect_inherits( + wbw_conservative_smoothing_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) +expect_inherits( + wbw_gaussian_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) +expect_inherits( + wbw_high_pass_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) -expect_snapshot( - label = "wbw_bilateral_filter", - wbw_bilateral_filter(x) +expect_inherits( + wbw_high_pass_median_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) -expect_snapshot( - label = "wbw_mean_filter", - wbw_mean_filter(x) +expect_inherits( + wbw_majority_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) -expect_snapshot( - label = "wbw_conservative_smoothing_filter", - wbw_conservative_smoothing_filter(x) +expect_inherits( + wbw_maximum_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) -expect_snapshot( - label = "wbw_high_pass_filter", - wbw_high_pass_filter(x) +expect_inherits( + wbw_mean_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) -expect_snapshot( - label = "wbw_gaussian_filter", - wbw_gaussian_filter(x) +expect_inherits( + wbw_median_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) +expect_inherits( + wbw_minimum_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) + # Test filter alterations +# Here is near-equality check is happening. If two values are close to +# be equal, i.e. 2.222222226 and 2.222222225, then all.equal() returns TRUE +# In other cases the function will return the mean relative difference as +# a character vector true_median <- median(x) -# Test adaptive filter -filtered <- wbw_adaptive_filter(x, filter_size_x = 51, filter_size_y = 51) -expect_true(median(filtered) != true_median) - -# Test bilateral filter -filtered <- wbw_bilateral_filter(x, sigma_dist = 5, sigma_int = 5) -expect_true(median(filtered) != true_median) - -# Test mean filter -filtered <- wbw_mean_filter(x, filter_size_x = 51, filter_size_y = 51) -expect_true(median(filtered) != true_median) - -# Test gaussian filter -filtered <- wbw_gaussian_filter(x, sigma = 5) -expect_true(median(filtered) != true_median) +expect_true( + wbw_adaptive_filter( + x, + filter_size_x = 51, + filter_size_y = 51 + ) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_bilateral_filter( + x, + sigma_dist = 3 + ) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_conservative_smoothing_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_gaussian_filter(x, sigma = 1.5) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_high_pass_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_high_pass_median_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_majority_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_maximum_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_mean_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_median_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_minimum_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) From f0c858bea29fb60d2696361a9357272cafd6ed78 Mon Sep 17 00:00:00 2001 From: Anatolii Tsyplenkov Date: Thu, 9 Jan 2025 16:12:10 +1300 Subject: [PATCH 3/6] feat: percentile and olympic filters --- NAMESPACE | 2 + R/filters.R | 193 +++++++++++++++++++++++++++-- man/wbw_high_pass_median_filter.Rd | 3 +- man/wbw_median_filter.Rd | 3 +- man/wbw_olympic_filter.Rd | 43 +++++++ man/wbw_percentile_filter.Rd | 67 ++++++++++ tests/tinytest/test_filter.R | 21 +++- 7 files changed, 320 insertions(+), 12 deletions(-) create mode 100644 man/wbw_olympic_filter.Rd create mode 100644 man/wbw_percentile_filter.Rd diff --git a/NAMESPACE b/NAMESPACE index 94f7851..adc5e66 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -46,6 +46,8 @@ export(wbw_mean_filter) export(wbw_median_filter) export(wbw_minimum_filter) export(wbw_multidirectional_hillshade) +export(wbw_olympic_filter) +export(wbw_percentile_filter) export(wbw_random_sample) export(wbw_read_raster) export(wbw_read_vector) diff --git a/R/filters.R b/R/filters.R index 4db9133..030bc9a 100644 --- a/R/filters.R +++ b/R/filters.R @@ -447,8 +447,6 @@ S7::method(wbw_high_pass_filter, WhiteboxRaster) <- ) } - - #' High Pass Median Filter #' @keywords image_processing #' @@ -467,7 +465,8 @@ S7::method(wbw_high_pass_filter, WhiteboxRaster) <- #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size #' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size -#' @param sig_digits \code{integer} +#' @param sig_digits \code{integer}, default 2. Required for rounding of +#' floating points inputs. #' #' @return [WhiteboxRaster] object containing filtered values #' @@ -532,8 +531,6 @@ S7::method(wbw_high_pass_median_filter, WhiteboxRaster) <- ) } - - #' Median Filter #' @keywords image_processing #' @@ -559,7 +556,8 @@ S7::method(wbw_high_pass_median_filter, WhiteboxRaster) <- #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size #' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size -#' @param sig_digits \code{integer} +#' @param sig_digits \code{integer}, default 2. Required for rounding of +#' floating points inputs. #' #' @return [WhiteboxRaster] object containing filtered values #' @@ -707,7 +705,7 @@ S7::method(wbw_majority_filter, WhiteboxRaster) <- #' @keywords image_processing #' #' @description -#' Assigns each cell in the output grid the maximum value in a moving window +#' Assigns each cell in the output grid the maximum value in a moving window #' centred on each grid cell in the input raster. #' #' @details @@ -780,7 +778,7 @@ S7::method(wbw_maximum_filter, WhiteboxRaster) <- #' @keywords image_processing #' #' @description -#' Assigns each cell in the output grid the minimum value in a moving window +#' Assigns each cell in the output grid the minimum value in a moving window #' centred on each grid cell in the input raster. #' #' @details @@ -847,3 +845,182 @@ S7::method(wbw_minimum_filter, WhiteboxRaster) <- source = out ) } + +#' Olympic Filter +#' @keywords image_processing +#' +#' @description +#' This filter is a modification of the [wbw_mean_filter], whereby +#' the highest and lowest values in the kernel are dropped, and the remaining +#' values are averaged to replace the central pixel. The result is a +#' low-pass smoothing filter that is more robust than the [wbw_mean_filter], +#' which is more strongly impacted by the presence of outlier values. +#' It is named after a system of scoring Olympic events. +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_mean_filter()] +#' +#' @eval rd_wbw_link("olympic_filter") +#' @eval rd_example("wbw_olympic_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_olympic_filter <- + S7::new_generic( + name = "wbw_olympic_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_olympic_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + + # Filter + out <- + wbe$olympic_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } + + +#' Percentile Filter +#' @keywords image_processing +#' +#' @description +#' This tool calculates the percentile of the center cell in a moving filter +#' window applied to an input image (\code{x}). This indicates the value +#' below which a given percentage of the neighbouring values in within the +#' filter fall. For example, the 35th percentile is the value below which 35% +#' of the neighbouring values in the filter window may be found. As such, +#' the percentile of a pixel value is indicative of the relative location +#' of the site within the statistical distribution of values contained +#' within a filter window. +#' +#' When applied to input digital elevation models, percentile is a measure of +#' local topographic position, or elevation residual. +#' +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' This tool takes advantage of the redundancy between overlapping, +#' neighbouring filters to enhance computationally efficiency, using a +#' method similar to Huang et al. (1979). This efficient method of +#' calculating percentiles requires rounding of floating-point inputs, +#' and therefore the user must specify the number of significant +#' digits (\code{sig_digits}) to be used during the processing. +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' @param sig_digits \code{integer}, default 2. Required for rounding of +#' floating points inputs. +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_median_filter()] +#' +#' @references +#' Huang, T., Yang, G.J.T.G.Y. and Tang, G., 1979. A fast two-dimensional +#' median filtering algorithm. IEEE Transactions on Acoustics, Speech, and +#' Signal Processing, 27(1), pp.13-18. +#' +#' @eval rd_wbw_link("percentile_filter") +#' @eval rd_example("wbw_percentile_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_percentile_filter <- + S7::new_generic( + name = "wbw_percentile_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L, + sig_digits = 2L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_percentile_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L, + sig_digits = 2L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + sig_digits <- + checkmate::asInteger( + sig_digits, + lower = 0L, + len = 1L + ) + # Filter + out <- + wbe$percentile_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y, + sig_digits = sig_digits + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } diff --git a/man/wbw_high_pass_median_filter.Rd b/man/wbw_high_pass_median_filter.Rd index 586cf2e..52c361c 100644 --- a/man/wbw_high_pass_median_filter.Rd +++ b/man/wbw_high_pass_median_filter.Rd @@ -18,7 +18,8 @@ wbw_high_pass_median_filter( \item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} -\item{sig_digits}{\code{integer}} +\item{sig_digits}{\code{integer}, default 2. Required for rounding of +floating points inputs.} } \value{ \link{WhiteboxRaster} object containing filtered values diff --git a/man/wbw_median_filter.Rd b/man/wbw_median_filter.Rd index 5220539..7ca31d9 100644 --- a/man/wbw_median_filter.Rd +++ b/man/wbw_median_filter.Rd @@ -13,7 +13,8 @@ wbw_median_filter(x, filter_size_x = 11L, filter_size_y = 11L, sig_digits = 2L) \item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} -\item{sig_digits}{\code{integer}} +\item{sig_digits}{\code{integer}, default 2. Required for rounding of +floating points inputs.} } \value{ \link{WhiteboxRaster} object containing filtered values diff --git a/man/wbw_olympic_filter.Rd b/man/wbw_olympic_filter.Rd new file mode 100644 index 0000000..61c85f7 --- /dev/null +++ b/man/wbw_olympic_filter.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_olympic_filter} +\alias{wbw_olympic_filter} +\title{Olympic Filter} +\usage{ +wbw_olympic_filter(x, filter_size_x = 11L, filter_size_y = 11L) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +This filter is a modification of the \link{wbw_mean_filter}, whereby +the highest and lowest values in the kernel are dropped, and the remaining +values are averaged to replace the central pixel. The result is a +low-pass smoothing filter that is more robust than the \link{wbw_mean_filter}, +which is more strongly impacted by the presence of outlier values. +It is named after a system of scoring Olympic events. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_olympic_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#olympic_filter} +} +\seealso{ +\code{\link[=wbw_mean_filter]{wbw_mean_filter()}} +} +\keyword{image_processing} diff --git a/man/wbw_percentile_filter.Rd b/man/wbw_percentile_filter.Rd new file mode 100644 index 0000000..cbf8f1c --- /dev/null +++ b/man/wbw_percentile_filter.Rd @@ -0,0 +1,67 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_percentile_filter} +\alias{wbw_percentile_filter} +\title{Percentile Filter} +\usage{ +wbw_percentile_filter( + x, + filter_size_x = 11L, + filter_size_y = 11L, + sig_digits = 2L +) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} + +\item{sig_digits}{\code{integer}, default 2. Required for rounding of +floating points inputs.} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +This tool calculates the percentile of the center cell in a moving filter +window applied to an input image (\code{x}). This indicates the value +below which a given percentage of the neighbouring values in within the +filter fall. For example, the 35th percentile is the value below which 35\% +of the neighbouring values in the filter window may be found. As such, +the percentile of a pixel value is indicative of the relative location +of the site within the statistical distribution of values contained +within a filter window. + +When applied to input digital elevation models, percentile is a measure of +local topographic position, or elevation residual. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). + +This tool takes advantage of the redundancy between overlapping, +neighbouring filters to enhance computationally efficiency, using a +method similar to Huang et al. (1979). This efficient method of +calculating percentiles requires rounding of floating-point inputs, +and therefore the user must specify the number of significant +digits (\code{sig_digits}) to be used during the processing. +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_percentile_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +Huang, T., Yang, G.J.T.G.Y. and Tang, G., 1979. A fast two-dimensional +median filtering algorithm. IEEE Transactions on Acoustics, Speech, and +Signal Processing, 27(1), pp.13-18. + +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#percentile_filter} +} +\seealso{ +\code{\link[=wbw_median_filter]{wbw_median_filter()}} +} +\keyword{image_processing} diff --git a/tests/tinytest/test_filter.R b/tests/tinytest/test_filter.R index f1c0260..5c9ca0a 100644 --- a/tests/tinytest/test_filter.R +++ b/tests/tinytest/test_filter.R @@ -1,5 +1,4 @@ -source("tests/tinytest/setup.R") -# source("setup.R") +source("setup.R") # Test adaptive filter failures # expect_error(wbw_adaptive_filter(x, filter_size_x = 10L)) @@ -79,6 +78,12 @@ expect_inherits( expect_inherits( wbw_minimum_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) +expect_inherits( + wbw_olympic_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) +expect_inherits( + wbw_percentile_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) # Test filter alterations @@ -161,3 +166,15 @@ expect_true( all.equal(true_median) |> is.character() ) +expect_true( + wbw_olympic_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_percentile_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) From f3f5c7fb6438d8060e5fbc933b20fd99b39d0a23 Mon Sep 17 00:00:00 2001 From: Anatolii Tsyplenkov Date: Thu, 9 Jan 2025 16:20:09 +1300 Subject: [PATCH 4/6] feat: total and range filters --- NAMESPACE | 2 + R/filters.R | 145 +++++++++++++++++++++++++++++++++++ man/wbw_range_filter.Rd | 40 ++++++++++ man/wbw_total_filter.Rd | 40 ++++++++++ tests/tinytest/test_filter.R | 23 +++++- 5 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 man/wbw_range_filter.Rd create mode 100644 man/wbw_total_filter.Rd diff --git a/NAMESPACE b/NAMESPACE index adc5e66..effc7e7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -49,6 +49,7 @@ export(wbw_multidirectional_hillshade) export(wbw_olympic_filter) export(wbw_percentile_filter) export(wbw_random_sample) +export(wbw_range_filter) export(wbw_read_raster) export(wbw_read_vector) export(wbw_res) @@ -57,6 +58,7 @@ export(wbw_ruggedness_index) export(wbw_slope) export(wbw_to_degrees) export(wbw_to_radians) +export(wbw_total_filter) export(wbw_version) export(wbw_write_raster) export(wbw_xres) diff --git a/R/filters.R b/R/filters.R index 030bc9a..9a487bd 100644 --- a/R/filters.R +++ b/R/filters.R @@ -1024,3 +1024,148 @@ S7::method(wbw_percentile_filter, WhiteboxRaster) <- source = out ) } + + +#' Range Filter +#' @keywords image_processing +#' +#' @description +#' A range filter assigns to each cell in the output grid the range +#' (maximum - minimum) of the values contained within a moving window +#' centred on each grid cell. +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_minimum_filter()], [wbw_maximum_filter()] +#' +#' @eval rd_wbw_link("range_filter") +#' @eval rd_example("wbw_range_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_range_filter <- + S7::new_generic( + name = "wbw_range_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_range_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + + # Filter + out <- + wbe$range_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } + +#' Total Filter +#' @keywords image_processing +#' +#' @description +#' A total filter assigns to each cell in the output grid the total (sum) +#' of all values in a moving window centred on each grid cell. +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_minimum_filter()], [wbw_maximum_filter()], +#' [wbw_range_filter()], [wbw_majority_filter()] +#' +#' @eval rd_wbw_link("total_filter") +#' @eval rd_example("wbw_total_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_total_filter <- + S7::new_generic( + name = "wbw_total_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_total_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + + # Filter + out <- + wbe$total_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } diff --git a/man/wbw_range_filter.Rd b/man/wbw_range_filter.Rd new file mode 100644 index 0000000..24637f9 --- /dev/null +++ b/man/wbw_range_filter.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_range_filter} +\alias{wbw_range_filter} +\title{Range Filter} +\usage{ +wbw_range_filter(x, filter_size_x = 11L, filter_size_y = 11L) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +A range filter assigns to each cell in the output grid the range +(maximum - minimum) of the values contained within a moving window +centred on each grid cell. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_range_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#range_filter} +} +\seealso{ +\code{\link[=wbw_minimum_filter]{wbw_minimum_filter()}}, \code{\link[=wbw_maximum_filter]{wbw_maximum_filter()}} +} +\keyword{image_processing} diff --git a/man/wbw_total_filter.Rd b/man/wbw_total_filter.Rd new file mode 100644 index 0000000..0f6ba11 --- /dev/null +++ b/man/wbw_total_filter.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_total_filter} +\alias{wbw_total_filter} +\title{Total Filter} +\usage{ +wbw_total_filter(x, filter_size_x = 11L, filter_size_y = 11L) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +A total filter assigns to each cell in the output grid the total (sum) +of all values in a moving window centred on each grid cell. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_total_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#total_filter} +} +\seealso{ +\code{\link[=wbw_minimum_filter]{wbw_minimum_filter()}}, \code{\link[=wbw_maximum_filter]{wbw_maximum_filter()}}, +\code{\link[=wbw_range_filter]{wbw_range_filter()}}, \code{\link[=wbw_majority_filter]{wbw_majority_filter()}} +} +\keyword{image_processing} diff --git a/tests/tinytest/test_filter.R b/tests/tinytest/test_filter.R index 5c9ca0a..573ae3b 100644 --- a/tests/tinytest/test_filter.R +++ b/tests/tinytest/test_filter.R @@ -1,4 +1,6 @@ -source("setup.R") +# FIXME: +source("tests/tinytest/setup.R") +# source("setup.R") # Test adaptive filter failures # expect_error(wbw_adaptive_filter(x, filter_size_x = 10L)) @@ -84,7 +86,12 @@ expect_inherits( expect_inherits( wbw_percentile_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) - +expect_inherits( + wbw_range_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) +expect_inherits( + wbw_total_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) # Test filter alterations # Here is near-equality check is happening. If two values are close to @@ -178,3 +185,15 @@ expect_true( all.equal(true_median) |> is.character() ) +expect_true( + wbw_range_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) +expect_true( + wbw_total_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +) From f52976fbeec2ff87b09cd5a1221bbb2b6fc5fbc8 Mon Sep 17 00:00:00 2001 From: Anatolii Tsyplenkov Date: Thu, 9 Jan 2025 16:20:47 +1300 Subject: [PATCH 5/6] feat: total and range filters --- tests/tinytest/test_filter.R | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/tinytest/test_filter.R b/tests/tinytest/test_filter.R index 573ae3b..7a2dbfd 100644 --- a/tests/tinytest/test_filter.R +++ b/tests/tinytest/test_filter.R @@ -1,6 +1,4 @@ -# FIXME: -source("tests/tinytest/setup.R") -# source("setup.R") +source("setup.R") # Test adaptive filter failures # expect_error(wbw_adaptive_filter(x, filter_size_x = 10L)) From c1d21586b1f825e3618190d8bb786baafba83691 Mon Sep 17 00:00:00 2001 From: Anatolii Tsyplenkov Date: Thu, 9 Jan 2025 16:23:31 +1300 Subject: [PATCH 6/6] feat: sd filter --- NAMESPACE | 1 + R/filters.R | 121 +++++++++++++++++++++------ man/wbw_standard_deviation_filter.Rd | 41 +++++++++ tests/tinytest/test_filter.R | 53 ++---------- 4 files changed, 148 insertions(+), 68 deletions(-) create mode 100644 man/wbw_standard_deviation_filter.Rd diff --git a/NAMESPACE b/NAMESPACE index effc7e7..ca8b103 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -56,6 +56,7 @@ export(wbw_res) export(wbw_rows) export(wbw_ruggedness_index) export(wbw_slope) +export(wbw_standard_deviation_filter) export(wbw_to_degrees) export(wbw_to_radians) export(wbw_total_filter) diff --git a/R/filters.R b/R/filters.R index 9a487bd..2955761 100644 --- a/R/filters.R +++ b/R/filters.R @@ -465,7 +465,7 @@ S7::method(wbw_high_pass_filter, WhiteboxRaster) <- #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size #' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size -#' @param sig_digits \code{integer}, default 2. Required for rounding of +#' @param sig_digits \code{integer}, default 2. Required for rounding of #' floating points inputs. #' #' @return [WhiteboxRaster] object containing filtered values @@ -556,7 +556,7 @@ S7::method(wbw_high_pass_median_filter, WhiteboxRaster) <- #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size #' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size -#' @param sig_digits \code{integer}, default 2. Required for rounding of +#' @param sig_digits \code{integer}, default 2. Required for rounding of #' floating points inputs. #' #' @return [WhiteboxRaster] object containing filtered values @@ -926,35 +926,35 @@ S7::method(wbw_olympic_filter, WhiteboxRaster) <- #' @keywords image_processing #' #' @description -#' This tool calculates the percentile of the center cell in a moving filter -#' window applied to an input image (\code{x}). This indicates the value -#' below which a given percentage of the neighbouring values in within the -#' filter fall. For example, the 35th percentile is the value below which 35% -#' of the neighbouring values in the filter window may be found. As such, -#' the percentile of a pixel value is indicative of the relative location -#' of the site within the statistical distribution of values contained -#' within a filter window. -#' -#' When applied to input digital elevation models, percentile is a measure of +#' This tool calculates the percentile of the center cell in a moving filter +#' window applied to an input image (\code{x}). This indicates the value +#' below which a given percentage of the neighbouring values in within the +#' filter fall. For example, the 35th percentile is the value below which 35% +#' of the neighbouring values in the filter window may be found. As such, +#' the percentile of a pixel value is indicative of the relative location +#' of the site within the statistical distribution of values contained +#' within a filter window. +#' +#' When applied to input digital elevation models, percentile is a measure of #' local topographic position, or elevation residual. -#' -#' +#' +#' #' @details #' Neighbourhood size, or filter size, is specified in the x and y dimensions #' using \code{filter_size_x} and \code{filter_size_y} These dimensions should #' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). -#' -#' This tool takes advantage of the redundancy between overlapping, -#' neighbouring filters to enhance computationally efficiency, using a -#' method similar to Huang et al. (1979). This efficient method of -#' calculating percentiles requires rounding of floating-point inputs, -#' and therefore the user must specify the number of significant -#' digits (\code{sig_digits}) to be used during the processing. +#' +#' This tool takes advantage of the redundancy between overlapping, +#' neighbouring filters to enhance computationally efficiency, using a +#' method similar to Huang et al. (1979). This efficient method of +#' calculating percentiles requires rounding of floating-point inputs, +#' and therefore the user must specify the number of significant +#' digits (\code{sig_digits}) to be used during the processing. #' #' @eval rd_input_raster("x") #' @param filter_size_x \code{integer}, X dimension of the neighbourhood size #' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size -#' @param sig_digits \code{integer}, default 2. Required for rounding of +#' @param sig_digits \code{integer}, default 2. Required for rounding of #' floating points inputs. #' #' @return [WhiteboxRaster] object containing filtered values @@ -1031,7 +1031,7 @@ S7::method(wbw_percentile_filter, WhiteboxRaster) <- #' #' @description #' A range filter assigns to each cell in the output grid the range -#' (maximum - minimum) of the values contained within a moving window +#' (maximum - minimum) of the values contained within a moving window #' centred on each grid cell. #' #' @details @@ -1102,7 +1102,7 @@ S7::method(wbw_range_filter, WhiteboxRaster) <- #' @keywords image_processing #' #' @description -#' A total filter assigns to each cell in the output grid the total (sum) +#' A total filter assigns to each cell in the output grid the total (sum) #' of all values in a moving window centred on each grid cell. #' #' @details @@ -1169,3 +1169,76 @@ S7::method(wbw_total_filter, WhiteboxRaster) <- source = out ) } + +#' Standard Deviation Filter +#' @keywords image_processing +#' +#' @description +#' A standard deviation filter assigns to each cell in the output grid the +#' standard deviation, a measure of dispersion, of the values contained within +#' a moving window centred on each grid cell. +#' +#' @details +#' Neighbourhood size, or filter size, is specified in the x and y dimensions +#' using \code{filter_size_x} and \code{filter_size_y} These dimensions should +#' be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +#' +#' @eval rd_input_raster("x") +#' @param filter_size_x \code{integer}, X dimension of the neighbourhood size +#' @param filter_size_y \code{integer}, Y dimension of the neighbourhood size +#' +#' @return [WhiteboxRaster] object containing filtered values +#' +#' @seealso [wbw_minimum_filter()], [wbw_maximum_filter()], +#' [wbw_range_filter()], [wbw_majority_filter()], [wbw_total_filter()] +#' +#' @eval rd_wbw_link("standard_deviation_filter") +#' @eval rd_example("wbw_standard_deviation_filter", +#' c("filter_size_x = 3L", "filter_size_y = 3L")) +#' +#' @export +wbw_standard_deviation_filter <- + S7::new_generic( + name = "wbw_standard_deviation_filter", + dispatch_args = "x", + fun = function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + S7::S7_dispatch() + } + ) + +S7::method(wbw_standard_deviation_filter, WhiteboxRaster) <- + function(x, + filter_size_x = 11L, + filter_size_y = 11L) { + # Checks + check_env(wbe) + filter_size_x <- + checkmate::asInteger( + filter_size_x, + lower = 0L, + len = 1L + ) + filter_size_y <- + checkmate::asInteger( + filter_size_y, + lower = 0L, + len = 1L + ) + checkmate::assert_true(filter_size_x %% 2 == 1) + checkmate::assert_true(filter_size_y %% 2 == 1) + + # Filter + out <- + wbe$standard_deviation_filter( + raster = x@source, + filter_size_x = filter_size_x, + filter_size_y = filter_size_y + ) + # Return Raster + WhiteboxRaster( + name = x@name, + source = out + ) + } diff --git a/man/wbw_standard_deviation_filter.Rd b/man/wbw_standard_deviation_filter.Rd new file mode 100644 index 0000000..dd72e69 --- /dev/null +++ b/man/wbw_standard_deviation_filter.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/filters.R +\name{wbw_standard_deviation_filter} +\alias{wbw_standard_deviation_filter} +\title{Standard Deviation Filter} +\usage{ +wbw_standard_deviation_filter(x, filter_size_x = 11L, filter_size_y = 11L) +} +\arguments{ +\item{x}{Raster object of class \link{WhiteboxRaster}. See \code{\link[=wbw_read_raster]{wbw_read_raster()}} for more details.} + +\item{filter_size_x}{\code{integer}, X dimension of the neighbourhood size} + +\item{filter_size_y}{\code{integer}, Y dimension of the neighbourhood size} +} +\value{ +\link{WhiteboxRaster} object containing filtered values +} +\description{ +A standard deviation filter assigns to each cell in the output grid the +standard deviation, a measure of dispersion, of the values contained within +a moving window centred on each grid cell. +} +\details{ +Neighbourhood size, or filter size, is specified in the x and y dimensions +using \code{filter_size_x} and \code{filter_size_y} These dimensions should +be odd, positive integer values (e.g. 3L, 5L, 7L, 9L, etc.). +} +\examples{ +f <- system.file("extdata/dem.tif", package = "wbw") +wbw_read_raster(f) |> + wbw_standard_deviation_filter(filter_size_x = 3L, filter_size_y = 3L) +} +\references{ +For more information, see \url{https://www.whiteboxgeo.com/manual/wbw-user-manual/book/tool_help.html#standard_deviation_filter} +} +\seealso{ +\code{\link[=wbw_minimum_filter]{wbw_minimum_filter()}}, \code{\link[=wbw_maximum_filter]{wbw_maximum_filter()}}, +\code{\link[=wbw_range_filter]{wbw_range_filter()}}, \code{\link[=wbw_majority_filter]{wbw_majority_filter()}}, \code{\link[=wbw_total_filter]{wbw_total_filter()}} +} +\keyword{image_processing} diff --git a/tests/tinytest/test_filter.R b/tests/tinytest/test_filter.R index 7a2dbfd..c307c91 100644 --- a/tests/tinytest/test_filter.R +++ b/tests/tinytest/test_filter.R @@ -1,49 +1,5 @@ source("setup.R") -# Test adaptive filter failures -# expect_error(wbw_adaptive_filter(x, filter_size_x = 10L)) -# expect_error(wbw_adaptive_filter(x, filter_size_y = 2)) -# expect_error(wbw_adaptive_filter(x, filter_size_y = c(1:2))) -# expect_error(wbw_adaptive_filter(x, filter_size_y = 1.5)) -# expect_error(wbw_adaptive_filter(x, threshold = "a")) -# expect_error(wbw_adaptive_filter("x", threshold = "a")) - -# # Test bilateral filter failures -# expect_error(wbw_bilateral_filter(x, sigma_dist = 10L)) -# expect_error(wbw_bilateral_filter(x, sigma_int = -2)) -# expect_error(wbw_bilateral_filter(x, sigma_dist = c(1:2))) -# expect_error(wbw_bilateral_filter(x, sigma_dist = 20.1)) -# expect_error(wbw_bilateral_filter(x, sigma_dist = "a")) -# expect_error(wbw_bilateral_filter("x", sigma_int = "a")) - -# # Test mean filter failures -# expect_error(wbw_mean_filter(x, filter_size_x = 10L)) -# expect_error(wbw_mean_filter(x, filter_size_y = 2)) -# expect_error(wbw_mean_filter(x, filter_size_y = c(1:2))) -# expect_error(wbw_mean_filter(x, filter_size_y = 1.5)) -# expect_error(wbw_mean_filter(x, filter_size_y = "a")) -# expect_error(wbw_mean_filter("x", filter_size_y = "a")) - -# # Test conservative smoothing filter failures -# expect_error(wbw_conservative_smoothing_filter(x, filter_size_x = 10L)) -# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = 2)) -# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = c(1:2))) -# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = 1.5)) -# expect_error(wbw_conservative_smoothing_filter(x, filter_size_y = "a")) -# expect_error(wbw_conservative_smoothing_filter("x", filter_size_y = "a")) - -# # Test high pass filter failures -# expect_error(wbw_high_pass_filter(x, filter_size_x = 10, filter_size_y = 11)) -# expect_error(wbw_high_pass_filter(x, filter_size_x = 11, filter_size_y = 10)) -# expect_error(wbw_high_pass_filter(x, filter_size_x = 11.1, filter_size_y = 11)) -# expect_error(wbw_high_pass_filter("x", filter_size_x = 11, filter_size_y = 11)) - -# # Test high pass median filter failures -# expect_error(wbw_high_pass_median_filter(x, filter_size_x = 10, filter_size_y = 11)) -# expect_error(wbw_high_pass_median_filter(x, filter_size_x = 11, filter_size_y = 10)) -# expect_error(wbw_high_pass_median_filter(x, filter_size_x = 11.1, filter_size_y = 11)) -# expect_error(wbw_high_pass_median_filter("x", filter_size_x = 11, filter_size_y = 11)) - # Test successful filter returns expect_inherits( wbw_adaptive_filter(x), c("wbw::WhiteboxRaster", "S7_object") @@ -90,6 +46,9 @@ expect_inherits( expect_inherits( wbw_total_filter(x), c("wbw::WhiteboxRaster", "S7_object") ) +expect_inherits( + wbw_standard_deviation_filter(x), c("wbw::WhiteboxRaster", "S7_object") +) # Test filter alterations # Here is near-equality check is happening. If two values are close to @@ -195,3 +154,9 @@ expect_true( all.equal(true_median) |> is.character() ) +expect_true( + wbw_standard_deviation_filter(x) |> + median() |> + all.equal(true_median) |> + is.character() +)