--- title: "Introduction to imagine package" vignette: > %\VignetteIndexEntry{Introduction to imagine package} %\VignetteEngine{quarto::html} %\VignetteEncoding{UTF-8} knitr: opts_chunk: collapse: true comment: '#>' --- ```{r} #| label: setup library(imagine) ``` # IMAGing engINE: Tools for Application of Image Filters to Data Matrices The **imagine** package streamlines the application of image-filtering algorithms to numeric data matrices. It implements efficient median-filtering and 2D convolution routines in C++ via Rcpp, enabling fast processing of large datasets. ## Installation For installing **imagine**, as follows: ```{r} #| eval: false install.packages("imagine") ``` ## Engines The **imagine** package relies on C++-based algorithms, referred to as engines, implemented using the Rcpp and RcppArmadillo packages to accelerate filtering operations. These engines provide substantial performance improvements when working with large matrices. As of version 2.1.0, the following engines are available: * Engine 1: Standard 2D convolution (kernel applied to each cell neighborhood and summed). * Engine 2: Same as Engine 1, but returns a quantile defined by the `probs` argument. * Engine 3: Computes the mean over a square neighborhood defined by `radius` argument. * Engine 4: Same as Engine 3, but returns a quantile defined by `probs` argument. * Engine 5: Contextual Median Filter described in [Belkin and O'Reilly (2009)](http://dx.doi.org/10.1016/j.jmarsys.2008.11.018). * Engines 6 and 7: Gradient filters described in [Agenbag et al. (2003)](https://doi.org/10.1016/j.pocean.2003.07.004). Since version 2.0.0, the radius argument can accept two values, allowing rectangular (non-square) neighborhoods. ## Main functions The package provides five main functions and two wrappers. ### Convolution functions ```{r} #| eval: false # Build kernels # Kernel 1: For bottom edge recognition kernel1 <- matrix( data = c(-1, -2, -1, 0, 0, 0, 1, 2, 1), nrow = 3 ) # Kernel 2: Diagonal weighting kernel2 <- matrix( data = c(-2, 0, 0, 0, 1, 0, 0, 0, 2), nrow = 3 ) # Apply filters convolutionExample <- convolution2D(X = wbImage, kernel = kernel1) convQuantileExample <- convolutionQuantile(X = wbImage, kernel = kernel2, probs = 0.1) ``` To compare results, we plot the filtered outputs using the `image` function (@fig-2d-quantile-conv). **Original vs filtered outputs** ```{r} #| message: false #| label: fig-2d-quantile-conv #| fig-width: 5.33 #| fig-height: 8.5 #| fig-cap: "2D vs 2D quantile convolutions" #| fig-pos: "h" #| results: hide #| echo: false # Defining a copy of wbImage myMatrix <- wbImage # Defining color palette cols <- gray.colors(n = 1e3, start = 0, end = 1) # Build kernels # Kernel 1: For bottom edge recognition kernel1 <- matrix( data = c(-1, -2, -1, 0, 0, 0, 1, 2, 1), nrow = 3 ) # Kernel 2: Diagonal weighting kernel2 <- matrix( data = c(-2, 0, 0, 0, 1, 0, 0, 0, 2), nrow = 3 ) # Apply filters convolutionExample <- convolution2D(X = myMatrix, kernel = kernel1) convQuantileExample <- convolutionQuantile( X = myMatrix, kernel = kernel2, probs = 0.8 ) # Make plots par( mar = c(0, 0.5, 0, 0.5), oma = c(0, 0, 2, 0), mfrow = c(3, 1) ) image(wbImage, col = cols, axes = FALSE) mtext(text = "Original", side = 3, line = -1.5, font = 2, adj = 0.99) image(convolutionExample, col = cols, axes = FALSE) mtext( text = "2D convolution", side = 3, line = -1.5, col = "white", font = 2, adj = 0.99 ) image(convQuantileExample, col = cols, axes = FALSE) mtext( text = "2D quantile convolution", side = 3, line = -1.5, col = "black", font = 2, adj = 0.99 ) ``` ### Median-filter asociated functions ```{r} #| eval: false # Add some noise (NA) to the image (matrix) set.seed(7) naIndex <- sample( x = seq(prod(dim(myMatrix))), size = as.integer(0.4*prod(dim(myMatrix))), replace = FALSE ) myMatrix[naIndex] <- NA # Build kernel radius <- 3 # Apply filters meanfilterExample <- meanFilter(X = myMatrix, radius = radius) quantilefilterExample <- quantileFilter(X = myMatrix, radius = radius, probs = 0.1) medianfilterExample <- medianFilter(X = myMatrix, radius = radius) ``` We now compare the original and filtered matrices (@fig-basic-filter-comparison). **Original and Filtered** ```{r} #| message: false #| label: fig-basic-filter-comparison #| fig-width: 7.5 #| fig-height: 5 #| fig-cap: "Basic filters comparison" #| fig-pos: "h" #| results: hide #| echo: false # Defining a copy of wbImage myMatrix <- wbImage # Defining color palette cols <- gray.colors(n = 1e3, start = 0, end = 1) # Add some noise (NA) to the image (matrix) set.seed(7) naIndex <- sample( x = seq(prod(dim(myMatrix))), size = as.integer(0.4*prod(dim(myMatrix))) ) myMatrix[naIndex] <- NA # Apply filters meanfilterExample <- meanFilter(X = myMatrix, radius = 3) medianfilterExample <- medianFilter(X = myMatrix, radius = 3) # Make plots par(mar = rep(0, 4), oma = rep(0.5, 4), mfrow = c(2, 2)) image(wbImage, col = cols, axes = FALSE) mtext(text = "Original", side = 3, line = -1.5, font = 2, adj = 0.99) image(myMatrix, col = cols, axes = FALSE) mtext( text = "Original with noise (NA)", side = 3, line = -1.5, font = 2, adj = 0.99 ) # meanfilterExample[meanfilterExample < 0] <- 0 image(meanfilterExample, col = cols, axes = FALSE) mtext( text = "Mean filter", side = 3, line = -1.5, font = 2, adj = 0.99 ) # medianfilterExample[medianfilterExample < 0] <- 0 image(medianfilterExample, col = cols, axes = FALSE) mtext( text = "2D median filter", side = 3, line = -1.5, font = 2, adj = 0.99 ) ``` ## Kernel application In image processing, convolution is one of the most widely used operations. It consists of combining two arrays: the image (a matrix) and a kernel (a smaller matrix), which weights each pixel based on its neighborhood. Different kernels produce different effects, such as smoothing, shifting, or edge detection. Users should be cautious with kernel size, as larger neighborhoods reduce the number of valid pixels near the edges. All functions in **imagine** support recursive application through the times argument (@fig-recursive-filtering). ```{r} #| eval: false medianFilter(X = wbImage, radius = 5, times = 50) ``` ```{r} #| message: false #| label: fig-recursive-filtering #| fig-width: 7.5 #| fig-height: 5 #| fig-cap: "Filters with several time settings" #| fig-pos: "h" #| results: hide #| echo: false times <- c(1, 5, 15) # Defining color palette cols <- gray.colors(n = 1e3, start = 0, end = 1) # Apply filters median_times1 <- medianFilter(X = wbImage, radius = 5, times = times[1]) median_times2 <- medianFilter(X = wbImage, radius = 5, times = times[2]) median_times3 <- medianFilter(X = wbImage, radius = 5, times = times[3]) # Make plots par(mar = rep(0, 4), oma = rep(0.5, 4), mfrow = c(2, 2)) image(wbImage, col = cols, axes = FALSE) mtext(text = "Original", side = 3, line = -1.5, font = 2, adj = 0.99) image(median_times1, col = cols, axes = FALSE) mtext(text = paste("2D median filter\ntimes =", times[1]), side = 3, line = -2.5, font = 2, adj = 0.99) image(median_times2, col = cols, axes = FALSE) mtext(text = paste("2D median filter\ntimes =", times[2]), side = 3, line = -2.5, font = 2, adj = 0.99) image(median_times3, col = cols, axes = FALSE) mtext(text = paste("2D median filter\ntimes =", times[3]), side = 3, line = -2.5, font = 2, adj = 0.99) ``` ## Filters based on published articles Since version 2.1.0, **imagine** includes implementations of algorithms from the literature for detecting oceanographic gradients: * contextualMF: Based on [Belkin and O'Reilly (2009)](https://doi.org/10.1016/j.jmarsys.2008.11.018). * agenbagFilters: Based on [Agenbag et al. (2003)](https://doi.org/10.1016/j.pocean.2003.07.004). Although both functions are available directly in **imagine**, their use is recommended through the [grec](https://cran.r-project.org/package=grec) package for more specialized workflows.