Raw File
images.R
#
#       images.R
#
#      $Revision: 1.129 $     $Date: 2015/05/16 03:38:48 $
#
#      The class "im" of raster images
#
#     im()     object creator
#
#     is.im()   tests class membership
#
#     rasterx.im(), rastery.im()    
#                      raster X and Y coordinates
#
#     nearest.pixel()   
#     lookup.im()
#                      facilities for looking up pixel values
#
################################################################
########   basic support for class "im"
################################################################
#
#   creator 

im <- function(mat, xcol=seq_len(ncol(mat)), yrow=seq_len(nrow(mat)), 
               xrange=NULL, yrange=NULL,
               unitname=NULL) {

  typ <- typeof(mat)
  if(typ == "double")
    typ <- "real"

  miss.xcol <- missing(xcol)
  miss.yrow <- missing(yrow)
  
  # determine dimensions
  if(is.matrix(mat)) {
    nr <- nrow(mat)
    nc <- ncol(mat)
    if(length(xcol) != nc)
      stop("Length of xcol does not match ncol(mat)")
    if(length(yrow) != nr)
      stop("Length of yrow does not match nrow(mat)")
  } else {
    if(miss.xcol || miss.yrow)
      stop(paste(sQuote("mat"),
                 "is not a matrix and I can't guess its dimensions"))
    stopifnot(length(mat) == length(xcol) * length(yrow))
    nc <- length(xcol)
    nr <- length(yrow)
  }

  # deal with factor case
  if(is.factor(mat)) {
    typ <- "factor"
  } else if(!is.null(lev <- levels(mat))) {
    typ <- "factor"
    mat <- factor(mat, levels=lev)
  }

  # Ensure 'mat' is a matrix (without destroying factor information)
  if(!is.matrix(mat))
    dim(mat) <- c(nr, nc)

  # set up coordinates
  if((miss.xcol || length(xcol) <= 1) && !is.null(xrange) ) {
    # use 'xrange' 
    xstep <- diff(xrange)/nc
    xcol <- seq(from=xrange[1] + xstep/2, to=xrange[2] - xstep/2, length.out=nc)
  } else if(length(xcol) > 1) {
    # use 'xcol'
    # ensure spacing is constant
    xcol <- seq(from=min(xcol), to=max(xcol), length.out=length(xcol))
    xstep <- diff(xcol)[1]
    xrange <- range(xcol) + c(-1,1) * xstep/2
  } else stop("Cannot determine pixel width")
  
  if((miss.yrow || length(yrow) <= 1) && !is.null(yrange)) {
    # use 'yrange'
    ystep <- diff(yrange)/nr
    yrow <- seq(from=yrange[1] + ystep/2, to=yrange[2] - ystep/2, length.out=nr)
  } else if(length(yrow) > 1) {
    # use 'yrow'
    # ensure spacing is constant
    yrow <- seq(from=min(yrow), to=max(yrow), length.out=length(yrow))
    ystep <- diff(yrow)[1]
    yrange <- range(yrow) + c(-1,1) * ystep/2
  }  else stop("Cannot determine pixel height")

  unitname <- as.units(unitname)

  out <- list(v   = mat,
              dim = c(nr, nc),
              xrange   = xrange,
              yrange   = yrange,
              xstep    = xstep,
              ystep    = ystep,
              xcol    = xcol,
              yrow    = yrow,
              type    = typ,
              units   = unitname)
  class(out) <- "im"
  return(out)
}

is.im <- function(x) {
  inherits(x,"im")
}

levels.im <- function(x) {
  levels(x$v)
}

"levels<-.im" <- function(x, value) {
  if(x$type != "factor") 
    stop("image is not factor-valued")
  levels(x$v) <- value
  x
}

################################################################
########   methods for class "im"
################################################################

shift.im <- function(X, vec=c(0,0), ..., origin=NULL) {
  verifyclass(X, "im")
  if(!is.null(origin)) {
    stopifnot(is.character(origin))
    if(!missing(vec))
      warning("argument vec ignored; overruled by argument origin")
    origin <- pickoption("origin", origin, c(centroid="centroid",
                                             midpoint="midpoint",
                                             bottomleft="bottomleft"))
    W <- as.owin(X)
    locn <- switch(origin,
                   centroid={ unlist(centroid.owin(W)) },
                   midpoint={ c(mean(W$xrange), mean(W$yrange)) },
                   bottomleft={ c(W$xrange[1], W$yrange[1]) })
    return(shift(X, -locn))
  }
  X$xrange <- X$xrange + vec[1]
  X$yrange <- X$yrange + vec[2]
  X$xcol <- X$xcol + vec[1]
  X$yrow <- X$yrow + vec[2]
  attr(X, "lastshift") <- vec
  return(X)
}

"Frame<-.im" <- function(X, value) {
  stopifnot(is.rectangle(value))
  if(!is.subset.owin(value, Frame(X))) {
    ## first expand
    X <- X[value, drop=FALSE]
  }
  X[value, drop=TRUE]
}


"[.im" <- local({

  disjoint <- function(r, s) { (r[2] < s[1]) || (r[1] > s[2])  }
  clip <- function(r, s) { c(max(r[1],s[1]), min(r[2],s[2])) }
  inrange <- function(x, r) { (x >= r[1]) & (x <= r[2]) }

  Extract.im <- function(x, i, j, ...,
                         drop=TRUE, tight=FALSE, raster=NULL,
                         rescue=is.owin(i)) {

    ## detect 'blank' arguments like second argument in x[i, ] 
    ngiven <- length(sys.call())
    nmatched <- length(match.call())
    nblank <- ngiven - nmatched
    itype <- if(missing(i)) "missing" else "given"
    jtype <- if(missing(j)) "missing" else "given"
    if(nblank == 1) {
      if(!missing(i)) jtype <- "blank"
      if(!missing(j)) itype <- "blank"
    } else if(nblank == 2) {
      itype <- jtype <- "blank"
    }

    if(missing(rescue) && itype != "given")
      rescue <- FALSE
    
    if(itype == "missing" && jtype == "missing") {
      ## no indices: return entire image 
      out <- if(is.null(raster)) x else as.im(raster)
      xy <- expand.grid(y=out$yrow,x=out$xcol)
      if(!is.null(raster)) {
        ## resample image on new pixel raster
        values <- lookup.im(x, xy$x, xy$y, naok=TRUE)
        out <- im(values, out$xcol, out$yrow, unitname=unitname(out))
      }
      if(!drop)
        return(out)
      else {
        v <- out$v
        return(v[!is.na(v)])
      }
    }

    if(itype == "given") {
      ## .................................................................
      ## Try spatial index
      ## .................................................................
      if(verifyclass(i, "owin", fatal=FALSE)) {

        if(jtype == "given")
          warning("Argument j ignored")
      
        ## 'i' is a window
        ## if drop = FALSE, just set values outside window to NA
        ## if drop = TRUE, extract values for all pixels inside window
        ##                 as an image (if 'i' is a rectangle)
        ##                 or as a vector (otherwise)

        ## determine pixel raster for output
        if(!is.null(raster)) {
          out <- as.im(raster)
          do.resample <- TRUE
        } else if(is.subset.owin(i, as.owin(x))) {
          out <- x
          do.resample <- FALSE
        } else {
          ## new window does not contain data window: expand it
          bb <- boundingbox(as.rectangle(i), as.rectangle(x))
          rr <- if(is.mask(i)) i else x
          xcol <- prolongseq(rr$xcol, bb$xrange, rr$xstep)
          yrow <- prolongseq(rr$yrow, bb$yrange, rr$ystep)
          out <- list(xcol=xcol, yrow=yrow)
          do.resample <- TRUE
        }
        xy <- expand.grid(y=out$yrow,x=out$xcol)
        if(do.resample) {
          ## resample image on new pixel raster
          values <- lookup.im(x, xy$x, xy$y, naok=TRUE)
          out <- im(values, out$xcol, out$yrow, unitname=unitname(out))
        }
        inside <- inside.owin(xy$x, xy$y, i)
        if(!drop) {
          ## set other pixels to NA and return image
          out$v[!inside] <- NA
          if(!tight)
            return(out)
        } else if(!(rescue && i$type == "rectangle")) {
          ## return pixel values
          values <- out$v[inside]
          return(values)
        }
        ## return image in smaller rectangle
        if(disjoint(i$xrange, x$xrange) || disjoint(i$yrange, x$yrange))
          ## empty intersection
          return(numeric(0))
        xr <- clip(i$xrange, x$xrange)
        yr <- clip(i$yrange, x$yrange)
        colsub <- inrange(out$xcol, xr)
        rowsub <- inrange(out$yrow, yr)
        ncolsub <- sum(colsub)
        nrowsub <- sum(rowsub)
        if(ncolsub == 0 || nrowsub == 0)
          return(numeric(0))
        marg <- list(mat=out$v[rowsub, colsub, drop=FALSE],
                     unitname=unitname(x))
        xarg <-
          if(ncolsub > 1) list(xcol = out$xcol[colsub]) else list(xrange=xr)
        yarg <-
          if(nrowsub > 1) list(yrow = out$yrow[rowsub]) else list(yrange=yr)
        result <- do.call("im", c(marg, xarg, yarg))
        return(result)
      }
      if(verifyclass(i, "im", fatal=FALSE)) {
        if(jtype == "given")
          warning("Argument j ignored")
        ## logical images OK
        if(i$type == "logical") {
          ## convert to window
          w <- as.owin(eval.im(ifelse1NA(i)))
          return(x[w, drop=drop, ..., raster=raster])
        } else stop("Subset argument \'i\' is an image, but not of logical type")
      }

      if(is.ppp(i)) {
        ## 'i' is a point pattern 
        if(jtype == "given")
          warning("Argument j ignored")
        ## Look up the greyscale values for the points of the pattern
        values <- lookup.im(x, i$x, i$y, naok=TRUE)
        if(drop) 
          values <- values[!is.na(values)]
        if(length(values) == 0) 
          ## ensure the zero-length vector is of the right type
          values <- 
            switch(x$type,
                   factor={ factor(, levels=levels(x)) },
                   integer = { integer(0) },
                   logical = { logical(0) },
                   real = { numeric(0) },
                   complex = { complex(0) },
                   character = { character(0) },
                   { values }
                   )
        return(values)
      }
    }
    ## ............... not a spatial index .............................

    ## Try indexing as a matrix

    ## Construct a matrix index call for possible re-use
    M <- as.matrix(x)
    ycall <- switch(itype,
                    given = {
                      switch(jtype,
                             given   = quote(M[i, j, drop=FALSE]),
                             blank   = quote(M[i,  , drop=FALSE]),
                             missing = quote(M[i,    drop=FALSE]))
                    },
                    blank = {
                      switch(jtype,
                             given   = quote(M[ , j, drop=FALSE]),
                             blank   = quote(M[ ,  , drop=FALSE]),
                             missing = quote(M[ ,    drop=FALSE]))
                    },
                    missing = {
                      switch(jtype,
                             given   = quote(M[j=j,  drop=FALSE]),
                             blank   = quote(M[j= ,  drop=FALSE]),
                             missing = quote(M[      drop=FALSE]))
                    })
    ## try it
    y <- try(eval(as.call(ycall)), silent=TRUE)
    if(!inherits(y, "try-error")) {
      ## valid subset index for a matrix
      if(rescue) {
        ## check whether it's a rectangular block, in correct order
        RR <- row(x$v)
        CC <- col(x$v)
        rcall <- ycall
        rcall[[2]] <- quote(RR)
        ccall <- ycall
        ccall[[2]] <- quote(CC)
        rr <- eval(as.call(rcall))
        cc <- eval(as.call(ccall))
        rseq <- sort(unique(as.vector(rr)))
        cseq <- sort(unique(as.vector(cc)))
        if(all(diff(rseq) == 1) && all(diff(cseq) == 1) &&
           (length(rr) == length(rseq) * length(cseq)) &&
           all(rr == RR[rseq, cseq]) && all(cc == CC[rseq,cseq])) {
          ## yes - make image
          dim(y) <- c(length(rseq), length(cseq))
          Y <- x
          Y$v <- y
          Y$dim <- dim(y)
          Y$xcol <- x$xcol[cseq]
          Y$yrow <- x$yrow[rseq]
          Y$xrange <- range(Y$xcol) + c(-1,1) * x$xstep/2
          Y$yrange <- range(Y$yrow) + c(-1,1) * x$ystep/2
          return(Y)
        }
      }
      ## return pixel values (possibly as matrix)
      return(y)
    }

    ## Last chance!
    if(itype == "given" &&
       !is.matrix(i) &&
       !is.null(ip <- as.ppp(i, W=as.owin(x), fatal=FALSE, check=FALSE))) {
      ## 'i' is convertible to a point pattern 
      ## Look up the greyscale values for the points of the pattern
      values <- lookup.im(x, ip$x, ip$y, naok=TRUE)
      if(drop) 
        values <- values[!is.na(values)]
      if(length(values) == 0) 
        ## ensure the zero-length vector is of the right type
        values <- 
          switch(x$type,
                 factor={ factor(, levels=levels(x)) },
                 integer = { integer(0) },
                 logical = { logical(0) },
                 real = { numeric(0) },
                 complex = { complex(0) },
                 character = { character(0) },
                 { values }
                 )
      return(values)
    }
  
    stop("The subset operation is undefined for this type of index")
  }

  Extract.im
})

update.im <- function(object, ...) {
  ## update internal structure of image after manipulation
  X <- object
  mat <- X$v
  typ <- typeof(mat)
  if(typ == "double")
    typ <- "real"
  ## deal with factor case
  if(is.factor(mat)) {
    typ <- "factor"
  } else if(!is.null(lev <- levels(mat))) {
    typ <- "factor"
    X$v <- factor(mat, levels=lev)
  }
  X$type <- typ
  return(X)
}

"[<-.im" <- function(x, i, j, value) {
  # detect 'blank' arguments like second argument of x[i, ] 
  ngiven <- length(sys.call())
  nmatched <- length(match.call())
  nblank <- ngiven - nmatched
  itype <- if(missing(i)) "missing" else "given"
  jtype <- if(missing(j)) "missing" else "given"
  if(nblank == 1) {
    if(!missing(i)) jtype <- "blank"
    if(!missing(j)) itype <- "blank"
  } else if(nblank == 2) {
    itype <- jtype <- "blank"
  }

  X <- x
  W <- as.owin(X)
  if(is.im(value)) {
    value <- value$v
  }
  stopifnot(is.vector(value) || is.matrix(value) || is.factor(value))

  if(itype == "missing" && jtype == "missing") {
    # no index provided
    # set all pixels to 'value'
    v <- X$v
    if(!is.factor(value)) {
      v[!is.na(v)] <- value
    } else {
      vnew <- matrix(NA_integer_, ncol(v), nrow(v))
      vnew[!is.na(v)] <- as.integer(value)
      v <- factor(vnew, labels=levels(value))
    }
    X$v <- v
    return(update(X))
  }
  if(itype == "given") {
    # ..................... Try a spatial index ....................
    if(verifyclass(i, "owin", fatal=FALSE)) {
      if(jtype == "given") warning("Index j ignored")
      # 'i' is a window
      if(is.empty(i))
        return(X)
      rxy <- rasterxy.mask(W)
      xx <- rxy$x
      yy <- rxy$y
      ok <- inside.owin(xx, yy, i)
      X$v[ok] <- value
      X$type <- ifelse(is.factor(X$v), "factor", typeof(X$v))
      return(update(X))
    }
    if(verifyclass(i, "im", fatal=FALSE) && i$type == "logical") {
      if(jtype == "given") warning("Index j ignored")
      # convert logical vector to window where entries are TRUE
      i <- as.owin(eval.im(ifelse1NA(i)))
      # continue as above
      rxy <- rasterxy.mask(W)
      xx <- rxy$x
      yy <- rxy$y
      ok <- inside.owin(xx, yy, i)
      X$v[ok] <- value
      X$type <- ifelse(is.factor(X$v), "factor", typeof(X$v))
      return(update(X))
    }
    if(is.ppp(i)) {
      # 'i' is a point pattern
      if(jtype == "given") warning("Index j ignored")
      nv <- length(value)
      np <- npoints(i)
      if(nv != np && nv != 1)
        stop("Length of replacement value != number of point locations")
      # test whether all points are inside window FRAME
      ok <- inside.owin(i$x, i$y, as.rectangle(W))
      if(any(!ok)) {
        warning("Some points are outside the outer frame of the image")
        if(nv == np)
          value <- value[ok]
        i <- i[ok]
      }
      if(npoints(i) > 0) {
        # determine row & column positions for each point 
        loc <- nearest.pixel(i$x, i$y, X)
        # set values
        X$v[cbind(loc$row, loc$col)] <- value
      }
      X$type <- ifelse(is.factor(X$v), "factor", typeof(X$v))
      return(update(X))
    }
  }
  # .................. 'i' is not a spatial index ....................
  
  # Construct a matrix replacement call 
  ycall <- switch(itype,
                  given = {
                    switch(jtype,
                           given   = quote(X$v[i, j] <- value),
                           blank   = quote(X$v[i,  ] <- value),
                           missing = quote(X$v[i]    <- value))
                  },
                  blank = {
                    switch(jtype,
                           given   = quote(X$v[ , j] <- value),
                           blank   = quote(X$v[ ,  ] <- value),
                           missing = quote(X$v[ ] <- value))
                  },
                  missing = {
                    switch(jtype,
                           given   = quote(X$v[j=j] <- value),
                           blank   = quote(X$v[j= ] <- value),
                           missing = quote(X$v[] <- value))
                  })
  # try it
  litmus <- try(eval(as.call(ycall)), silent=TRUE)
  if(!inherits(litmus, "try-error")){
    X$type <- ifelse(is.factor(X$v), "factor", typeof(X$v))
    return(update(X))
  }
  #  Last chance!
  if(itype == "given" &&
     !is.matrix(i) &&
     !is.null(ip <- as.ppp(i, W=W, fatal=FALSE, check=TRUE))) {
    # 'i' is convertible to a point pattern
    if(jtype == "given") warning("Index j ignored")
    nv <- length(value)
    np <- npoints(ip)
    if(nv != np && nv != 1)
      stop("Length of replacement value != number of point locations")
    # test whether all points are inside window FRAME
    ok <- inside.owin(ip$x, ip$y, as.rectangle(W))
    if(any(!ok)) {
      warning("Some points are outside the outer frame of the image")
      if(nv == np)
        value <- value[ok]
      ip <- ip[ok]
    }
    if(npoints(ip) > 0) {
      # determine row & column positions for each point 
      loc <- nearest.pixel(ip$x, ip$y, X)
      # set values
      X$v[cbind(loc$row, loc$col)] <- value
    }
    X$type <- ifelse(is.factor(X$v), "factor", typeof(X$v))
    return(update(X))
  }

  stop("The subset operation is undefined for this type of index")
}

################################################################
########   other tools
################################################################

#
# This function is similar to nearest.raster.point except for
# the third argument 'im' and the different idiom for calculating
# row & column - which could be used in nearest.raster.point()

nearest.pixel <- function(x,y,im) {
  verifyclass(im, "im")
  if(length(x) > 0) {
    nr <- im$dim[1]
    nc <- im$dim[2]
    cc <- round(1 + (x - im$xcol[1])/im$xstep)
    rr <- round(1 + (y - im$yrow[1])/im$ystep)
    cc <- pmax.int(1,pmin.int(cc, nc))
    rr <- pmax.int(1,pmin.int(rr, nr))
  } else cc <- rr <- integer(0)
  return(list(row=rr, col=cc))
}

# Explores the 3 x 3 neighbourhood of nearest.pixel
# and finds the nearest pixel that is not NA

nearest.valid.pixel <- function(x,y,im) {
  rc <- nearest.pixel(x,y,im)
  rr <- rc$row
  cc <- rc$col
  # check whether any pixels are outside image domain
  outside <- is.na(im$v)
  miss <- outside[cbind(rr, cc)]
  if(!any(miss))
    return(rc)
  # for offending pixels, explore 3 x 3 neighbourhood
  nr <- im$dim[1]
  nc <- im$dim[2]
  xcol <- im$xcol
  yrow <- im$yrow
  for(i in which(miss)) {
    rows <- rr[i] + c(-1,0,1)
    cols <- cc[i] + c(-1,0,1)
    rows <- unique(pmax.int(1, pmin.int(rows, nr)))
    cols <- unique(pmax.int(1, pmin.int(cols, nc)))
    rcp <- expand.grid(row=rows, col=cols)
    ok <- !outside[as.matrix(rcp)]
    if(any(ok)) {
      # At least one of the neighbours is valid
      # Find the closest one
      rcp <- rcp[ok,]
      dsq <- with(rcp, (x[i] - xcol[col])^2 + (y[i] - yrow[row])^2)
      j <- which.min(dsq)
      rc$row[i] <- rcp$row[j]
      rc$col[i] <- rcp$col[j]
    }
  }
  return(rc)
}
  

# This function is a generalisation of inside.owin()
# to images other than binary-valued images.

lookup.im <- function(Z, x, y, naok=FALSE, strict=TRUE) {
  verifyclass(Z, "im")

  if(Z$type == "factor")
    Z <- repair.old.factor.image(Z)
  
  if(length(x) != length(y))
    stop("x and y must be numeric vectors of equal length")

  # initialise answer to NA 
  if(Z$type != "factor") {
    niets <- NA
    mode(niets) <- mode(Z$v)
  } else {
    niets <- factor(NA, levels=levels(Z))
  }
  value <- rep.int(niets, length(x))
               
  # test whether inside bounding rectangle
  xr <- Z$xrange
  yr <- Z$yrange
  eps <- sqrt(.Machine$double.eps)
  frameok <- (x >= xr[1] - eps) & (x <= xr[2] + eps) & 
             (y >= yr[1] - eps) & (y <= yr[2] + eps)
  
  if(!any(frameok)) {
    # all points OUTSIDE range - no further work needed
    if(!naok)
      warning("Internal error: all values NA")
    return(value)  # all NA
  }

  # consider only those points which are inside the frame
  xf <- x[frameok]
  yf <- y[frameok]
  # map locations to raster (row,col) coordinates
  if(strict)
    loc <- nearest.pixel(xf,yf,Z)
  else
    loc <- nearest.valid.pixel(xf,yf,Z)
  # look up image values
  vf <- Z$v[cbind(loc$row, loc$col)]
  
  # insert into answer
  value[frameok] <- vf

  if(!naok && any(is.na(value)))
    warning("Internal error: NA's generated")

  return(value)
}
  

## low level

rasterx.im <- function(x) {
  verifyclass(x, "im")
  xx <- x$xcol
  matrix(xx[col(x)], ncol=ncol(x), nrow=nrow(x))
}

rastery.im <- function(x) {
  verifyclass(x, "im")
  yy <- x$yrow
  matrix(yy[row(x)], ncol=ncol(x), nrow=nrow(x))
}

rasterxy.im <- function(x, drop=FALSE) {
  verifyclass(x, "im")
  xx <- x$xcol
  yy <- x$yrow
  ans <- cbind(x=as.vector(xx[col(x)]),
               y=as.vector(yy[row(x)]))
  if(drop) {
    ok <- as.vector(!is.na(x$v))
    ans <- ans[ok, , drop=FALSE]
  }
  return(ans)
}

## user interface 

raster.x <- function(w, drop=FALSE) {
  if(is.owin(w)) return(rasterx.mask(w, drop=drop))
  if(!is.im(w)) stop("w should be a window or an image")
  x <- w$xcol[col(w)]
  x <- if(drop) x[!is.na(w$v), drop=TRUE] else array(x, dim=w$dim)
  return(x)
}
  
raster.y <- function(w, drop=FALSE) {
  if(is.owin(w)) return(rastery.mask(w, drop=drop))
  if(!is.im(w)) stop("w should be a window or an image")
  y <- w$yrow[row(w)]
  y <- if(drop) y[!is.na(w$v), drop=TRUE] else array(y, dim=w$dim)
  return(y)
}

raster.xy <- function(w, drop=FALSE) {
  if(is.owin(w)) return(rasterxy.mask(w, drop=drop))
  if(!is.im(w)) stop("w should be a window or an image")
  y <- w$xcol[col(w)]
  y <- w$yrow[row(w)]
  if(drop) {
    ok <- !is.na(w$v)
    x <- x[ok, drop=TRUE]
    y <- y[ok, drop=TRUE]
  }
  return(list(x=as.numeric(x),
              y=as.numeric(y)))
}

##############

# methods for other functions

xtfrm.im <- function(x) { as.numeric(as.matrix.im(x)) }

as.matrix.im <- function(x, ...) {
  return(x$v)
}

as.array.im <- function(x, ...) {
  m <- as.matrix(x)
  a <- do.call(array, resolve.defaults(list(m),
                                       list(...),
                                       list(dim=c(dim(m), 1))))
  return(a)
}

as.data.frame.im <- function(x, ...) {
  verifyclass(x, "im")
  v <- x$v
  xx <- x$xcol[col(v)]
  yy <- x$yrow[row(v)]
  ok <- !is.na(v)
  xx <- as.vector(xx[ok])
  yy <- as.vector(yy[ok])
  # extract pixel values without losing factor info
  vv <- v[ok]
  dim(vv) <- NULL
  # 
  data.frame(x=xx, y=yy, value=vv, ...)
}
  
mean.im <- function(x, ...) {
  verifyclass(x, "im")
  xvalues <- x[drop=TRUE]
  return(mean(xvalues))
}

sum.im <- function(x, ...) {
  verifyclass(x, "im")
  xvalues <- x[drop=TRUE]
  return(sum(xvalues, ...))
}

median.im <- function(x, ...) {
  verifyclass(x, "im")
  xvalues <- x[drop=TRUE]
  return(median(xvalues, ...))
}

range.im <- function(x, ...) {
  verifyclass(x, "im")
  xvalues <- x[drop=TRUE]
  return(range(xvalues, ...))
}

max.im <- function(x, ...) {
  verifyclass(x, "im")
  xvalues <- x[drop=TRUE]
  return(max(xvalues, ...))
}

min.im <- function(x, ...) {
  verifyclass(x, "im")
  xvalues <- x[drop=TRUE]
  return(min(xvalues, ...))
}

## the following ensures that 'sd' works

as.double.im <- function(x, ...) { as.double(x[], ...) }

##

hist.im <- function(x, ..., probability=FALSE) {
  xname <- short.deparse(substitute(x))
  verifyclass(x, "im")
  main <- paste("Histogram of", xname)
  # default plot arguments
  # extract pixel values
  values <- as.matrix(x)
  dim(values) <- NULL
  # barplot or histogram
  if(x$type %in% c("logical", "factor")) {
    # barplot
    tab <- table(values)
    probs <- tab/sum(tab)
    if(probability) {
      heights <- probs
      ylab <- "Probability"
    } else {
      heights <- tab
      ylab <- "Number of pixels"
    }
    mids <- do.call("barplot",
                   resolve.defaults(list(heights),
                                    list(...),
                                    list(xlab=paste("Pixel value"),
                                         ylab=ylab,
                                         main=main)))
    out <- list(counts=tab, probs=probs, heights=heights,
                mids=mids, xname=xname)
    class(out) <- "barplotdata"
  } else {
    # histogram
    values <- values[!is.na(values)]
    plotit <- resolve.defaults(list(...), list(plot=TRUE))$plot
    if(plotit) {
      ylab <- if(probability) "Probability density" else "Number of pixels"
      out <- do.call("hist.default",
                     resolve.defaults(list(values),
                                      list(...),
                                      list(freq=!probability,
                                           xlab="Pixel value",
                                           ylab=ylab,
                                           main=main)))
      out$xname <- xname
    } else {
      # plot.default whinges if `probability' given when plot=FALSE
      out <- do.call("hist.default",
                   resolve.defaults(list(values),
                                    list(...)))
      # hack!
      out$xname <- xname
    }
  }
  return(invisible(out))
}

plot.barplotdata <- function(x, ...) {
  do.call("barplot",
          resolve.defaults(list(height=x$heights),
                           list(...),
                           list(main=paste("Histogram of ", x$xname))))
}

cut.im <- function(x, ...) {
  verifyclass(x, "im")
  typ <- x$type
  if(typ %in% c("factor", "logical", "character")) 
    stop(paste0("cut.im is not defined for ", typ, "-valued images"),
         call.=FALSE)
  vcut <- cut(as.numeric(as.matrix(x)), ...)
  return(im(vcut,
            xcol=x$xcol, yrow=x$yrow,
            xrange=x$xrange, yrange=x$yrange,
            unitname=unitname(x)))
}

quantile.im <- function(x, ...) {
  verifyclass(x, "im")
  q <- do.call("quantile",
               resolve.defaults(list(as.numeric(as.matrix(x))),
                                list(...),
                                list(na.rm=TRUE)))
  return(q)
}

integral <- function(f, domain=NULL, ...) {
  UseMethod("integral")
}

integral.im <- function(f, domain=NULL, ...) {
  verifyclass(f, "im")
  typ <- f$type
  if(!any(typ == c("integer", "real", "complex", "logical")))
    stop(paste("Don't know how to integrate an image of type", sQuote(typ)))
  if(!is.null(domain)) {
    if(is.tess(domain)) return(sapply(tiles(domain), integral.im, f=f))
    f <- f[domain, drop=FALSE, tight=TRUE]
  }
  a <- with(f, sum(v, na.rm=TRUE) * xstep * ystep)
  return(a)
}

conform.imagelist <- function(X, Zlist) {
  # determine points of X where all images in Zlist are defined
  ok <- rep.int(TRUE, length(X$x))
  for(i in seq_along(Zlist)) {
    Zi <- Zlist[[i]]
    ZiX <- Zi[X, drop=FALSE]
    ok <- ok & !is.na(ZiX)
  }
  return(ok)
}

split.im <- function(x, f, ..., drop=FALSE) {
  stopifnot(is.im(x))
  if(inherits(f, "tess")) 
    subsets <- tiles(f)
  else if(is.im(f)) {
    if(f$type != "factor")
      f <- eval.im(factor(f))
    subsets <- tiles(tess(image=f))
  } else stop("f should be a tessellation or a factor-valued image")
  if(!is.subset.owin(as.owin(x), as.owin(f)))
    stop("f does not cover the window of x")
  n <- length(subsets)
  out <- vector(mode="list", length=n)
  names(out) <- names(subsets)
  for(i in 1:n)
    out[[i]] <- x[subsets[[i]], drop=drop]
  if(drop)
    return(out)
  else 
    return(as.solist(out))
}

by.im <- function(data, INDICES, FUN, ...) {
  stopifnot(is.im(data))
  V <- split(data, INDICES)
  U <- lapply(V, FUN, ...)
  return(as.solist(U, demote=TRUE))
}

rebound.im <- function(x, rect) {
  stopifnot(is.im(x))
  stopifnot(is.owin(rect))
  rect <- as.rectangle(rect)
  stopifnot(is.subset.owin(as.rectangle(x), rect))
  # compute number of extra rows/columns
  dx <- x$xstep
  nleft  <- max(0, floor((x$xrange[1]-rect$xrange[1])/dx))
  nright <- max(0, floor((rect$xrange[2]-x$xrange[2])/dx))
  dy <- x$ystep
  nbot <- max(0, floor((x$yrange[1]-rect$yrange[1])/dy))
  ntop <- max(0, floor((rect$yrange[2]-x$yrange[2])/dy))
  # determine exact x and y ranges (to preserve original pixel locations)
  xrange.new <- x$xrange + c(-nleft, nright) * dx
  yrange.new <- x$yrange + c(-nbot,  ntop) * dy
  # expand pixel data matrix
  nr <- x$dim[1]
  nc <- x$dim[2]
  nrnew <- nbot  + nr + ntop
  ncnew <- nleft + nc + nright
  naval <- switch(x$type,
                  factor=,
                  integer=NA_integer_,
                  real=NA_real_,
                  character=NA_character_,
                  complex=NA_complex_,
                  NA)
  vnew <- matrix(naval, nrnew, ncnew)
  if(x$type != "factor") {
    vnew[nbot + (1:nr), nleft + (1:nc)] <- x$v
  } else {
    vnew[nbot + (1:nr), nleft + (1:nc)] <- as.integer(x$v)
    vnew <- factor(vnew, labels=levels(x))
    dim(vnew) <- c(nrnew, ncnew)
  }
  # build new image object
  xnew <- im(vnew,
             xrange = xrange.new,
             yrange = yrange.new,
             unitname = unitname(x))
  return(xnew)
}

sort.im <- function(x, ...) {
  verifyclass(x, "im")
  sort(as.vector(as.matrix(x)), ...)
}

dim.im <- function(x) { x$dim }

# colour images
rgbim <- function(R, G, B, maxColorValue=255, autoscale=FALSE) {
  if(autoscale) {
    R <- scaletointerval(R, 0, maxColorValue)
    G <- scaletointerval(G, 0, maxColorValue)
    B <- scaletointerval(B, 0, maxColorValue)
  }
  eval.im(factor(rgbNA(as.vector(R), as.vector(G), as.vector(B),
                     maxColorValue=maxColorValue)))
}

hsvim <- function(H, S, V, autoscale=FALSE) {
  if(autoscale) {
    H <- scaletointerval(H, 0, 1)
    S <- scaletointerval(S, 0, 1)
    V <- scaletointerval(V, 0, 1)
  }
  eval.im(factor(hsvNA(as.vector(H), as.vector(S), as.vector(V))))
}

scaletointerval <- function(x, from=0, to=1, xrange=range(x)) {
  UseMethod("scaletointerval")
}

scaletointerval.default <- function(x, from=0, to=1, xrange=range(x)) {
  x <- as.numeric(x)
  rr <- if(missing(xrange)) range(x, na.rm=TRUE) else as.numeric(xrange)
  b <- as.numeric(to - from)/diff(rr)
  if(is.finite(b)) {
    y <- from + b * (x - rr[1])
  } else {
    y <- (from+to)/2 + 0 * x
  }
  y[] <- pmin(pmax(y[], from), to)
  return(y)
}

scaletointerval.im <- function(x, from=0, to=1, xrange=range(x)) {
  v <- scaletointerval(x$v, from, to, xrange=xrange)
  y <- im(v, x$xcol, x$yrow, x$xrange, x$yrange, unitname(x))
  return(y)
}

zapsmall.im <- function(x, digits) {
  if(missing(digits))
    return(eval.im(zapsmall(x)))
  return(eval.im(zapsmall(x, digits=digits)))
}

domain.im <- Window.im <- function(X, ...) { as.owin(X) }

"Window<-.im" <- function(X, ..., value) {
  verifyclass(value, "owin")
  X[value, drop=FALSE]
}

padimage <- function(X, value, n=1) {
  stopifnot(is.im(X))
  if(missing(value) || n <= 0)
    return(X)
  stopifnot(length(value) == 1)
  if(isfac <- (X$type == "factor")) {
    ## handle factors
    levX <- levels(X)
    if(is.factor(value)) {
      stopifnot(identical(levels(X), levels(value)))
    } else {
      value <- factor(value, levels=levX)
    }
    X <- eval.im(as.integer(X))
    value <- as.integer(value)
  }
  value <- rep(value, n)
  sv <- 1:n
  mX <- as.matrix(X)
  dd <- dim(mX)
  mX <- cbind(matrix(rev(value), dd[1], n, byrow=TRUE),
              as.matrix(X),
              matrix(value, dd[1], n, byrow=TRUE))
  dd <- dim(mX)
  mX <- rbind(matrix(rev(value), n, dd[2]),
              mX,
              matrix(value, n, dd[2]))
  xcol <- with(X, c(xcol[1] - rev(sv) * xstep, xcol, xcol[dim[2]] + sv* xstep))
  yrow <- with(X, c(yrow[1] - rev(sv) * ystep, yrow, yrow[dim[1]] + sv* ystep))
  xr <- with(X, xrange + c(-1,1) * n * xstep)
  yr <- with(X, yrange + c(-1,1) * n * ystep)
  Y <- im(mX,
          xcol=xcol, yrow=yrow, xrange=xr, yrange=yr,
          unitname=unitname(X))
  if(isfac)
    Y <- eval.im(factor(Y, levels=seq_along(levX), labels=levX))
  return(Y)
}

as.function.im <- function(x, ...) {
  Z <- x
  f <- function(x,y) { Z[list(x=x, y=y)] }
  g <- funxy(f, Window(x))
  return(g)
}
back to top