R/multichannelPCA.R

Defines functions vectorToMultichannel multichannelToVector multichannelPCA

Documented in multichannelPCA multichannelToVector vectorToMultichannel

#' Shape analysis of multichannel images based on PCA
#'
#' Converts a set of multichannel images (e.g. deformation fields ) to a matrix
#' to enable various forms of PCA.  Returns the components of shape variability
#' and variance explained.  May employ different decomposition methods (WIP).
#'
#' @param x list containing multichannel images all of the same size
#' @param mask mask to apply to the multichannel images
#' @param k rank to use
#' @param pcaOption currently only PCA and randPCA, latter being much faster.
#' We also allow fastICA.
#' @param auxiliaryModality if you pass this matrix, then will do CCA.  This
#' will only work with one option.
#' @param center subtract the mean column vector.
#' @param sigma parameter for kernel PCA.
#' @param verbose produces more explanatory output.
#' @return list of the pca output and conversion to multichannel images
#' @author Avants BB
#' @examples
#'
#' img1 <- antsImageRead(getANTsRData("r16")) %>%
#'   resampleImage(c(4, 4))
#' img2 <- antsImageRead(getANTsRData("r64")) %>%
#'   resampleImage(c(4, 4))
#' img3 <- antsImageRead(getANTsRData("r27")) %>%
#'   resampleImage(c(4, 4))
#' img4 <- antsImageRead(getANTsRData("r30")) %>%
#'   resampleImage(c(4, 4))
#' reg1 <- antsRegistration(img1, img2, "SyN")
#' reg2 <- antsRegistration(img1, img3, "SyN")
#' reg3 <- antsRegistration(img1, img4, "SyN")
#' w1 <- antsImageRead(reg1$fwdtransforms[1])
#' w2 <- antsImageRead(reg2$fwdtransforms[1])
#' w3 <- antsImageRead(reg3$fwdtransforms[1])
#' mask <- getMask(img1)
#' x <- list(w1, w2, w3)
#' dpca <- multichannelPCA(x, mask)
#' warpTx <- antsrTransformFromDisplacementField(dpca$pcaWarps[[1]])
#' warped <- applyAntsrTransform(warpTx, data = img1, reference = img1)
#'
#' @export multichannelPCA
multichannelPCA <- function(
    x,
    mask,
    k = NA,
    pcaOption = "PCA",
    auxiliaryModality,
    center = TRUE,
    sigma = NA,
    verbose = FALSE) {
  n <- length(x)
  idim <- mask@dimension
  p <- sum(mask >= 1)
  # check the size of the inputs matches the mask
  for (i in 1:n) {
    if (!all(dim(x[[n]]) == dim(mask))) {
      stop(paste(
        "dimensionality of input number", i,
        "does not match the mask dimensionality."
      ))
    }
  }
  nchannels <- x[[1]]@components
  vecmat <- matrix(nrow = n, ncol = p * nchannels)
  for (i in 1:n)
  {
    vecmat[i, ] <- multichannelToVector(x[[i]], mask)
  }
  if (is.numeric(pcaOption)) {
    pcak <- pcaOption
    pcaOption <- "kPCA"
  }
  if (verbose) print(paste("begin decomposition option", pcaOption))
  if (is.na(k)) k <- nrow(vecmat) - 1
  if (center) cx <- sweep(vecmat, 2, colMeans(vecmat), "-") else cx <- vecmat
  if (pcaOption == "randPCA") {
    if (!usePkg("rsvd")) stop("please install rsvd")
    vpca <- rsvd::rsvd(cx, k)
  } else if (pcaOption == "rrPCAL") {
    vpca <- rsvd::rrpca(cx, k = k)
    vpca <- list(d = NA, u = cx %*% t(vpca$L), v = t(vpca$L))
  } else if (pcaOption == "rrPCAS") {
    vpca <- rsvd::rrpca(cx, k = k)
    vpca <- list(d = NA, u = cx %*% t(vpca$S), v = t(vpca$S))
  } else if (pcaOption == "kPCA") {
    kpcaopt <- "cov"
    if (!is.na(sigma)) kpcaopt <- "gaussian"
    if (!usePkg("irlba")) stop("please install irlba")
    if (missing("auxiliaryModality")) {
      tempdistmat <- sparseDistanceMatrix(cx, pcak, kmetric = kpcaopt, sigma = sigma)
    }
    if (!missing("auxiliaryModality")) {
      if (center) cy <- sweep(auxiliaryModality, 2, colMeans(auxiliaryModality), "-")
      if (!center) cy <- auxiliaryModality
      tempdistmat <- sparseDistanceMatrixXY(cy, cx, pcak, kmetric = kpcaopt, sigma = sigma)
    }
    vpca <- irlba::irlba(tempdistmat, nu = k, nv = k)
    vpca$u <- cx %*% vpca$v
  } else if (pcaOption == "fastICA") {
    if (!usePkg("fastICA")) stop("please install fastICA")
    tempica <- fastICA::fastICA(t(cx), k)
    vpca <- list(d = NA, u = cx %*% tempica$S, v = (tempica$S))
  } else if (pcaOption == "eanat") {
    # FIXME - implement mask for regularization
    # need to bind mask in proper order to make
    # regularization work
    arr <- abind::abind(as.array(mask), as.array(mask), along = idim)
    if (idim == 3) {
      arr <- abind::abind(arr, as.array(mask), along = idim)
    }
    maska <- as.antsImage(arr)
    a <- antsCopyImageInfo(mask, maska)
    #      eanat = sparseDecom( cx, inmask=maska, sparseness = 1.0/k, nvecs=k,
    #        cthresh=10, smooth=0.5, verbose=TRUE )
    #      vpca = list( d=eanat$varex,  u=eanat$umatrix,
    #       v=t(eanat$eigenanatomyimages ) )
    eanatD <- eanatDef(cx,
      mask = maska, smoother = mean(antsGetSpacing(mask)),
      positivity = TRUE, nvecs = k, cthresh = 0, verbose = TRUE
    )
    vpca <- list(d = NA, u = NA, v = t(eanatD))
    k <- ncol(vpca$v)
  } else {
    vpca <- svd(cx, nv = k, nu = k)
  }
  if (!verbose) {
    rm(vecmat)
    vecmat <- NA
  }
  if (verbose) {
    print(paste("convert back to multichannel"))
  }
  # now convert the vectors back to warps
  pcaWarps <- list()
  for (i in 1:k)
  {
    pcaWarps[[i]] <- vectorToMultichannel(vpca$v[, i], mask)
  }
  datatopcacorrs <- NA
  mylms <- NA
  if (verbose) print(paste("regression and correlation"))
  if (verbose) {
    datatopcacorrs <- cor(t(vecmat), vpca$v)
    # can also explore regressing the basis against the data
    mydf <- data.frame(vpca$v)
    mylms <- summary(lm(t(vecmat) ~ ., data = mydf))
  }
  gc()
  return(list(
    pca = vpca,
    pcaWarps = pcaWarps,
    vecmat = vecmat,
    datatopcacorrs = datatopcacorrs,
    mylms = mylms
  ))
}


#' Convert multichannel \code{antsImage} to a vector.
#'
#' Employs a mask to flatten each channel of a multi-channel image into a
#' contiguous piece of a vector.
#'
#' @param multichannelimage input multichannel image
#' @param mask mask of same dimensionality as multichannelimage
#' @return vector is output
#' @author Avants BB
#' @examples
#'
#' # see vectorToMultichannel
#'
#' @export multichannelToVector
multichannelToVector <- function(multichannelimage, mask) {
  dd <- mask@dimension
  p <- sum(mask >= 1)
  nchannels <- multichannelimage@components
  temp <- splitChannels(multichannelimage)
  maxn <- (nchannels * p)
  myinds <- seq(from = 1, to = maxn, by = (p))
  myinds[nchannels + 1] <- maxn
  v <- rep(0, maxn)
  for (k in 1:nchannels) {
    maxind <- (myinds[k + 1] - 1)
    if (k == nchannels) maxind <- maxn
    locinds <- myinds[k]:maxind
    v[locinds] <- temp[[k]][mask >= 1]
  }
  rm(temp)
  gc()
  return(v)
}


#' Convert vector to multichannel \code{antsImage}.
#'
#' Converts a vector of size n-entries in mask times number of channels to
#' a multi-channel image with the same dimensionality as the mask.
#'
#' @param v input multichannel vector
#' @param mask mask of same dimensionality as multichannelimage
#' @return multichannelimage is output
#' @author Avants BB
#' @examples
#'
#' fi <- antsImageRead(getANTsRData("r16")) %>%
#'   resampleImage(c(60, 60), 1, 0)
#' mi <- antsImageRead(getANTsRData("r64")) %>%
#'   resampleImage(c(60, 60), 1, 0)
#' mytx <- antsRegistration(fixed = fi, moving = mi, typeofTransform = c("SyN"))
#' mcimg <- antsImageRead(mytx$fwd[1])
#' msk <- getMask(fi)
#' vv <- multichannelToVector(mcimg, msk)
#' mcimg2 <- vectorToMultichannel(vv, msk)
#' vv2 <- multichannelToVector(mcimg2, msk)
#' cor.test(vv2, vv)
#' stopifnot(all(mcimg2[30, 30] == mcimg[30, 30]))
#'
#' @export vectorToMultichannel
vectorToMultichannel <- function(v, mask) {
  dd <- mask@dimension
  p <- sum(mask >= 1)
  nchannels <- round(length(v) / p)
  if (length(v) != (nchannels * p)) stop("dimensions do not match")
  maxn <- (nchannels * p)
  myinds <- seq(from = 1, to = maxn, by = (p))
  myinds[nchannels + 1] <- maxn
  mylist <- list()
  for (k in 1:nchannels)
  {
    maxind <- (myinds[k + 1] - 1)
    if (k == nchannels) maxind <- maxn
    locinds <- myinds[k]:maxind
    temp <- antsImageClone(mask)
    temp[mask >= 1] <- v[locinds]
    mylist[[k]] <- temp
  }
  vecimg <- mergeChannels(mylist)
  rm(temp, mylist)
  gc()
  return(vecimg)
}
ANTsX/ANTsR documentation built on March 29, 2025, 6:24 p.m.