How to apply a geometric anisotropic filter 1/cos²(theta) to a raster?

I am downscaling (i.e., increasing the spatial resolution) VIIRS nighttime lights imagery. The transfer function between the coarse and fine resolution is approximated by a Gaussian filter whose width σ varies with the per‑column viewing angle θ. This is an analytical, geometric relationship, not an estimate.

In the cross‑track direction (left→right), the sigma scales as

image

where θ is the per‑pixel viewing angle. The filter should act only horizontally (along x), pooling all y‑values within the column uniformly. My earlier attempt simply multiplied the raster values by

image

but I now realise the operation is a convolution with a 1‑D Gaussian kernel whose width depends on the column’s θ.

Given a value raster x and an angle raster theta with identical dimensions, what is the correct and efficient way in R (using terra) to apply a 1‑D Gaussian blur on each row, where the kernel’s standard deviation is determined by theta at the central pixel’s column?

Current (incomplete) attempt:

library(terra)

# ---- 1. Create example rasters ----
set.seed(42)
nrows <- 5
ncols <- 200

x <- rast(nrows = nrows, ncols = ncols, vals = runif(nrows * ncols))
theta_vals <- rep(seq(20, 26, length.out = ncols), each = nrows)
theta <- rast(nrows = nrows, ncols = ncols, vals = theta_vals)

# ---- 2. Spatially varying Gaussian blur function ----
# sigma0 = standard deviation of Gaussian at nadir (in pixel units)
# theta: raster of angles in degrees (same dimensions as x)
anisotropic_blur <- function(x, theta, sigma0 = 10) {
  # Convert to matrix (rows = y, cols = x)
  mat_x <- as.matrix(x, wide = TRUE)
  mat_angle <- as.matrix(theta, wide = TRUE)  # same dimensions
  nr <- nrow(mat_x)
  nc <- ncol(mat_x)
  mat_out <- matrix(NA_real_, nr, nc)
  
  # For each row, apply convolution with column-dependent sigma
  for (r in 1:nr) {
    row_vals <- mat_x[r, ]
    for (c in 1:nc) {
      theta_c <- mat_angle[r, c]               # angle at this pixel (degrees)
      sigma <- sigma0 / (cos(theta_c * pi/180)^2)
      halfwin <- ceiling(3 * sigma)            # kernel half‑width
      col_idx <- max(1, c - halfwin) : min(nc, c + halfwin)
      dist <- abs(col_idx - c)
      w <- exp(-0.5 * (dist / sigma)^2)
      w <- w / sum(w)
      mat_out[r, c] <- sum(w * row_vals[col_idx])
    }
  }
  # Return as raster with same properties
  rast(mat_out, crs = crs(x), ext = ext(x))
}

# ---- 3. Apply the filter ----
x_blurred <- anisotropic_blur(x, theta, sigma0 = 10)

# ---- 4. Plot side‑by‑side ----
par(mfrow = c(1, 2))
plot(x, main = "Original predictor")
plot(x_blurred, main = expression("Filtered: Gaussian "*sigma*" = "*sigma[0]/cos^2*theta))

I need to actually perform a 1‑D Gaussian convolution along rows with a column‑varying (\sigma). What is an idiomatic way to do this in terra?

1 Like