# -------------------------------------------------------
# Utility functions for CytoImageList subsetting
# -------------------------------------------------------
#' @title General subsetting methods for CytoImageList objects
#' @name CytoImageList-subsetting
#'
#' @description
#' These getter and setter functions are used to extract, replace and merge
#' entries in a \code{\linkS4class{CytoImageList}} object.
#'
#' @param x,y \code{CytoImageList} objects
#' @param i integer, logical, character or vector of such indicating which
#' element(s) to replace or extract
#' @param value a \code{CytoImageList} or \code{\linkS4class{Image}}
#' object
#'
#' @section Setting and getting images:
#' Functions to extract and replace elements (= images) of a
#' \linkS4class{CytoImageList} object. In the following code, \code{x} is a
#' CytoImageList object. The parameter \code{i} indicates the element(s) of
#' \code{x} that should be returned or replaced. Replacement is done by
#' \code{value}, which takes a CytoImageList or Image object. If \code{length(i)
#' > 0}, \code{value} has to be a CytoImageList object of \code{length(i)},
#' otherwise \code{value} allows a CytoImageList object of length 1 or an Image
#' object. If an Image object is provided, only the image entry in the
#' CytoImageList object is replaced, not the corresponding elementMetadata
#' entry.
#'
#' \describe{
#' \item{\code{getImages(x, i)}}{Returns image(s) indicated by \code{i} of
#' the CytoImageList object \code{x}}
#' \item{\code{setImages(x, i) <- value}}{Replaces the image(s) indicated by
#' \code{i} of the CytoImageList object \code{x} with \code{value}. For this,
#' \code{value} needs to have the same length as \code{i}}
#' }
#'
#' These setter and getter functions are the recommended way of extracting and
#' replacing images in a CytoImageList object. Alternatively, the standard
#' operations via \code{`[`}, \code{`[[`}, \code{`[<-`} and \code{`[[<-`} can be
#' performed (see \code{?\link{List}} for S4Vectors subsetting functionality).
#' However, these operations do not change element names during replacment
#' calls. The \code{setImages()} function makes sure that element names are
#' replaced if \code{value} is named or if \code{i} is a character or vector of
#' characters.
#'
#' @section Getting and setting channels:
#' Functions to extract and replace channels of a \linkS4class{CytoImageList}
#' object. Here, \code{x} is a \linkS4class{CytoImageList} object. The parameter
#' \code{i} indicates the channels of \code{x} that should be returned or
#' replaced. Replacement is done by \code{value}, which takes a CytoImageList
#' object. The CytoImageList object \code{value} needs to have the same length
#' as \code{x}. Furthermore, the number of channels in \code{value} should be
#' identical to \code{length(i)}.
#'
#' \describe{
#' \item{\code{getChannels(x, i)}}{Returns channel(s) indicated by \code{i} of
#' the CytoImageList object \code{x}}
#' \item{\code{setChannels(x, i) <- value}}{Replaces the channel(s) indicated
#' by \code{i} of the CytoImageList object \code{x} with \code{value}. For this,
#' \code{value} needs to have the same length as \code{i} and the same
#' number of channels as \code{length(i)}.}
#' }
#'
#' The \code{setChannels()} setter function does not allow adding new channels
#' to the CytoImageList object. For this operation, the \code{mergeChannels}
#' function was implemented (see below).
#'
#' @section Merging images:
#' Merging images is possible by merging two or more CytoImageList objects via:
#'
#' \describe{
#' \item{\code{c(x, y)}}{Returns an composite CytoImageList object with
#' elements of both CytoImageList objects \code{x} and \code{y}.
#' More than two CytoImageList objects can be merged in that way.}
#' }
#'
#' @section Merging channels:
#' Merging channels is possible via:
#'
#' \describe{
#' \item{\code{mergeChannels(x, y, h5FilesPath = NULL)}:}{
#' Returns a CytoImageList in which the channels of the CytoImageList object
#' \code{y} have been appended to the channels of the CytoImageList object
#' \code{x}. Only channels of two CytoImageList objects can be merged in that
#' way. The \code{h5FilesPath} argument can be ignored unless images are stored
#' on disk. To avoid overriding the .h5 files, one needs to specify a new
#' location where the merged images are stored on disk.
#' }
#' }
#'
#' @return A CytoImageList object
#'
#' @examples
#' data("pancreasImages")
#'
#' # Get images
#' getImages(pancreasImages, 1)
#' getImages(pancreasImages, "E34_imc")
#' getImages(pancreasImages, 1:2)
#' getImages(pancreasImages, c("E34_imc", "G01_imc"))
#' getImages(pancreasImages, grepl("E34_imc", names(pancreasImages)))
#'
#' # Set images
#' setImages(pancreasImages, 1) <- pancreasImages[1]
#' setImages(pancreasImages, "J02_imc") <- pancreasImages[1]
#' setImages(pancreasImages, "J02_imc") <- NULL
#'
#' # Get channels
#' getChannels(pancreasImages, 1)
#' getChannels(pancreasImages, "CD99")
#' getChannels(pancreasImages, c("CD99", "PIN"))
#'
#' # Set channels
#' channel1 <- getChannels(pancreasImages, 1)
#' setChannels(pancreasImages, 1) <- channel1
#' channelPIN <- getChannels(pancreasImages, "PIN")
#' setChannels(pancreasImages, "CD8a") <- channelPIN
#' setChannels(pancreasImages, "CD8a") <- NULL
#'
#' # Merge images
#' data("pancreasImages")
#' c(pancreasImages[c(1,3)], pancreasImages[2])
#'
#' # Merge channels
#' channel12 <- getChannels(pancreasImages, c(1,2))
#' channel34 <- getChannels(pancreasImages, c(3,4))
#' mergeChannels(channel12, channel34)
#'
#' # Merge channels on disk
#' cur_images <- CytoImageList(pancreasImages,
#' on_disk = TRUE,
#' h5FilesPath = HDF5Array::getHDF5DumpDir())
#' channel12 <- getChannels(cur_images, c(1,2))
#' channel34 <- getChannels(cur_images, c(3,4))
#'
#' # This will overwrite the initial .h5 files
#' mergeChannels(channel12, channel34,
#' h5FilesPath = HDF5Array::getHDF5DumpDir())
#'
#' @aliases
#' getImages getChannels mergeChannels
#' setImages<- setChannels<-
#' getImages,CytoImageList-method
#' setImages<-,CytoImageList-method
#' getChannels,CytoImageList-method
#' setChannels<-,CytoImageList-method
#' [<-,CytoImageList,ANY,ANY,CytoImageList-method
#' [[<-,CytoImageList,ANY,ANY-method
#'
#' @docType methods
#'
#' @author
#' Nils Eling (\email{nils.eling@@dqbm.uzh.ch})
NULL
#' @export
setMethod("getImages",
signature = signature(x="CytoImageList"),
definition = function(x, i){
if(missing(i) || is.null(x)){
return(x)
}
# Initial checks
if(is.null(i) || (!is.numeric(i) &&
!is.character(i) &&
!is.logical(i))){
stop("Invalid subsetting. \n",
"Only logicals, characters and integers are supported")
}
return(x[i])
})
#' @export
setReplaceMethod("setImages",
signature = signature(x="CytoImageList"),
definition = function(x, i, value){
if(missing(i) || is.null(x)){
return(x)
}
if(is.null(i) || (!is.numeric(i) &&
!is.character(i) &&
!is.logical(i))){
stop("Invalid subsetting. \n",
"Only logicals, characters and integers are supported")
}
# Further checks
.valid.Image.setting(x, i, value)
# Set correct names
cor_names <- NULL
if(!is.character(i) && !is.null(value)){
cor_names <- names(x)
names(cor_names) <- cor_names
cor_names[i] <- names(value)
}
# If value is Image_OR_DelayedArray, only the image will be replaced
if(is(value, "Image_OR_DelayedArray")){
x[[i]] <- value
} else {
x[i] <- value
}
if(!is.null(cor_names)){
names(x) <- as.character(cor_names)
}
return(x)
})
#' @export
#' @importFrom S4Vectors endoapply
#' @importFrom methods validObject
setMethod("getChannels",
signature = signature(x="CytoImageList"),
definition = function(x, i){
# Initial checks
if(is.null(i) || (!is.numeric(i) &&
!is.character(i) &&
!is.logical(i))){
stop("Invalid subsetting. \n",
"Only logicals, characters and integers are supported")
}
if(is.character(i) &&
sum(!(i %in% channelNames(x))) > 0){
stop("'i' not in channelNames(x)")
}
if(length(dim(x[[1]])) >= 3){
if (is(x[[1]], "Image")) {
x <- S4Vectors::endoapply(x, function(y){
y[,,i,drop=FALSE]
})
} else {
x@listData <- lapply(x, function(y){
y[,,i,drop=FALSE]
})
}
} else {
if(i != 1L){
stop(paste("For single-channel images,",
"channels must be named or 'i' needs to be 1."))
}
}
validObject(x)
return(x)
})
#' @export
#' @importFrom S4Vectors mendoapply
#' @importFrom methods is validObject
setReplaceMethod("setChannels",
signature = signature(x="CytoImageList"),
definition = function(x, i, value){
if(missing(i) || is.null(x)){
return(x)
}
if(is.null(i) || (!is.numeric(i) &&
!is.character(i) &&
!is.logical(i))){
stop("Invalid subsetting. \n",
"Only logicals, characters and integers are supported")
}
# Further checks
.valid.Channel.setting(x, i, value)
# Use getChannels function if value is NULL
if(is.null(value) && is.numeric(i)){
cur_ind <- seq_len(length.out = dim(x[[1]])[3])
cur_ind <- cur_ind[-i]
x <- getChannels(x, cur_ind)
} else if(is.null(value) && is.character(i)){
x <- getChannels(x, !(channelNames(x) %in% i))
} else {
# Set correct names
cor_names <- NULL
if(!is.character(i)){
cor_names <- channelNames(x)
cor_names[i] <- channelNames(value)
}
x <- S4Vectors::mendoapply(function(k, u){
k[,,i] <- u
return(k)
}, x, value)
if(!is.null(cor_names)){
channelNames(x) <- as.character(cor_names)
}
}
validObject(x)
return(x)
})
# Expand bracket functions to check if valid object is returned
#' @export
#' @importFrom methods callNextMethod as validObject
setReplaceMethod("[",
signature = c("CytoImageList", "ANY", "ANY", "CytoImageList"),
definition = function(x, i, j, ..., value){
.Object <- callNextMethod()
.Object <- as(.Object, "CytoImageList")
validObject(.Object)
return(.Object)
})
#' @export
#' @importFrom methods callNextMethod as validObject
setReplaceMethod("[[",
signature = c("CytoImageList", "ANY", "ANY"),
definition = function(x, i, j, ..., value){
.Object <- callNextMethod()
.Object <- as(.Object, "CytoImageList")
validObject(.Object)
return(.Object)
})
#' @export
#' @importFrom S4Vectors mendoapply
#' @importFrom methods is validObject
#' @importFrom EBImage abind
mergeChannels <- function(x, y, h5FilesPath = NULL){
if(!is(x, "CytoImageList") || !is(y, "CytoImageList")){
stop("'x' and 'y' must be CytoImageList objects")
}
# Further checks
if(length(x) != length(y)){
stop("Invalid merge operation: \n",
"'y' needs to have same length as 'x'")
}
if(!all(as.character(lapply(x, class)) ==
as.character(lapply(y, class)))){
stop("Invalid merge operation: \n",
"'y' needs to contain the same class objects as 'x'")
}
if (is(x[[1]], "Image")) {
x <- S4Vectors::mendoapply(function(k, u){
k <- abind(k,u)
return(k)
}, x, y)
} else {
if (is.null(h5FilesPath)){
stop("Please specify the filepath \n",
"where the merged images should be stored.")
}
x@listData <- mapply(function(cur_name, k, u){
cur_array <- abind(as.array(k), as.array(u))
cur_file <- file.path(h5FilesPath, paste0(cur_name, ".h5"))
# Check if file already exists
# If so, delete them
if (file.exists(cur_file)) {
file.remove(cur_file)
}
writeHDF5Array(DelayedArray(cur_array),
filepath = cur_file,
name = cur_name,
with.dimnames = TRUE)
}, names(x), x, y, SIMPLIFY = FALSE)
}
validObject(x)
return(x)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.