#' 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)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.