Search code examples
c++rrasterrcppterra

Develop a custom Rcpp function to be used with terra::focalCpp to calculate the median of values within a moving window


I'm trying to replicate the use of median within R that includes na.rm=TRUE as Rcpp code. I found this really useful link that includes the exact code I need for implementing Rccp median with na.rm=TRUE within my code here: https://github.com/RcppCore/Rcpp/issues/424. The median_dbl function from this link works great directly within R but implementing the function within a function I'm using for focalCpp has not worked for me.

Here's some example data

nr <- nc <- 50
r <- rast(ncols=nc, nrows=nr, ext= c(0, nc, 0, nr))
values(r) <- rep(c(rep(NA, 10), rep(1, 10), seq(1,8), rep(-999,12), rep(NA,5), rep(1,15),seq(1,8), rep(NA,12), seq(1,20)), 50)
 

Here is my moving window code

# moving window matrix 
mat = matrix(1,15,15) # create matrix of 1's that envelopes the extent of the buffer
gr = expand.grid(1:nrow(mat), 1:nrow(mat)) # df of all pairwise values based on row/col index
center = 8 # centroid index of the square grid
gr$dist = sqrt((gr$Var1-center)^2 + (gr$Var2-center)^2) # euclidean distance calucation 
threshold = 200/30 # 200m threshold is converted into number of pixels from center
gr$inside = ifelse(gr$dist < threshold, 1, NA) # if distance is less than threshold, grid value is one, otherwise NA
w = matrix(gr$inside, 15,15)  # Using gr$inside, place indexed values into matrix of original dimensions

R function I want to replicate using Rcpp

  testFunction = function(x) {
    q=x # make a copy of window matrix
    test.ind = which(q==-999) # find dummy indicies
    q[test.ind] = NA
    median(q, na.rm=T)
  }

Here is my Rcpp code with using the median function from the link https://github.com/RcppCore/Rcpp/issues/424

I think there is an issue with having NAs within Rcpp code and running the focalCpp function. When running this function I have this error Error: [focalCpp] test failed . Which is not helpful, when looking at the source code. The function works if I have an if statement to ignore all of the nas if (!std::isnan(x[j])) but this does not create my desired output.

There seems to be some issue with having NAs while calculating by cells within the moving window

// [[Rcpp::export]]
Rcpp::NumericVector testFunction (Rcpp::NumericVector x, size_t ni, size_t nw) {
  Rcpp::NumericVector out(ni);
    // loop over cells
    size_t start = 0;
    for (size_t i=0; i<ni; i++) {
        size_t end = start + nw;
        // compute something for a window
        Rcpp::NumericVector v;
        // loop over the values of a window
        for (size_t j=start; j<end; j++) {
            double q = x[j];
            if (q == -999) {q = NA_REAL;}
            v[j] = q;
        }
        Rcpp::NumericVector v2 = na_omit(v);
        int n = v2.length();
        if(n == 0){
            out[i] = 0;
        }else{
          std::sort(v2.begin(), v2.end());
          if (n % 2 == 0) {
            out[i] = (v2[(n / 2) - 1] + v2[(n / 2)]) / 2;
          } else {
            out[i] = v2[(n / 2)];
          }
        }
        start = end;
        }
        return out;
    }

focalCpp function to run Rcpp function

  output<- focalCpp(r, w=w, fun=testFunction, fillvalue = 0)

Solution

  • Starting simple helped to get to the solution and get past R crashing. The second loop and going from a double to a NumericVector seemed to be the issue. I'm using range instead for the window and that worked for me.

    // [[Rcpp::export]]
    Rcpp::NumericVector fmediantempC (Rcpp::NumericVector x, size_t ni, size_t nw) {
        Rcpp::NumericVector out(ni);
        // loop over cells
        for (size_t i=0; i<ni; i++) {
            size_t start = i*nw;
            size_t end = start+nw-1;
            Rcpp::NumericVector zw = x[Rcpp::Range(start,end)]; //Current window
            Rcpp::NumericVector v2 = Rcpp::na_omit(zw);
            int n = v2.length();
            if(n == 0){
                out[i] = NA_REAL; //if all NA values then marked as NA
            }else{
              std::sort(v2.begin(), v2.end());
              if (n % 2 == 0) {
                out[i] = (v2[(n / 2) - 1] + v2[(n / 2)]) / 2;
              } else {
                out[i] = v2[(n / 2)];
              }
        
            }
        }
        return out;
    }