Nothing
#' a function to find interesting features in density (most notably the minimum)
#' as precursor to creating a 1D gate
#'
#' @author Greg Finak, Phu T. Van
#' @param D a \code{density} containing the data to operate on
#' @param gate_range a \code{character} specifying the data range to operate on
#' @param adjust a \code{numeric} specifying the amount of smoothing to be used
#' @param plot a \code{boolean} specifying whether to output a plot
#' @noRd
.improvedMindensity <- function(D,adjust=2,gate_range=NA, plot = FALSE, ...){
# construct the density from data and adjust params we were given
dens <- density(D,adjust=adjust)
# restrict data to range
if (!is.null(gate_range)) {
if (length(gate_range) == 2 & gate_range[1] < gate_range[2]) {
filter <- dens$x > gate_range[1] & dens$x < gate_range[2]
dens$x <- dens$x[filter]
dens$y <- dens$y[filter]
}else{
#no range provided, do nothing
}
}
sp <- smooth.spline(dens$x,dens$y)
pred <- predict(sp)
d1 <- predict(sp,deriv = 1)
d2 <- predict(sp,deriv = 2)
d3 <- predict(sp,deriv = 3)
# find features
inf1 <- sign(d2$y[-length(d2$y)])>0&sign(d2$y[-1L])<0
inf2 <- sign(d2$y[-length(d2$y)])<0&sign(d2$y[-1L])>0
minima <- sign(d1$y[-1])>0&sign(d1$y[-length(d1$y)])<0
maxima <- sign(d1$y[-1])<0&sign(d1$y[-length(d1$y)])>0
shoulders <- sign(d3$y[-1])<0&sign(d3$y[-length(d3$y)])>0
minima_xcoords <- sp$x[which(minima)] # x-coords of minima
maxima_xcoords <- sp$x[which(maxima)] # x-coords of maxima
minima_ycoords <- sp$y[which(sp$x %in% minima_xcoords)] # y-coords of minima
maxima_ycoords <- sp$y[which(sp$x %in% maxima_xcoords)] # y-coords of maxima
#### TO-DO: determine a range in X-coord where the peaks are tiny (< some data-derived threshold)
# exclude features from this range when considering where to cut.
# this is motivated by cyTOF data, where there is a long tail of extreme values that
# cannot be eliminated by higher `adjust` values, and shouldn't be eliminated by gate_range()
#
if (length(which(minima == TRUE)) == 0){ # no minima found, look through shoulders
# if there is a peak, pick first shoulder to the right of peak
if (length(which(maxima == TRUE)) == 1 ){
pkidx <- which(maxima == TRUE)
pt <- sp$x[median(which(shoulders)[which(shoulders) > pkidx])]
if (is.na(pt)){
# there is only one peak, and there is no shoulder to the right of it
# pick an inflection point as a last resort
pt <- sp$x[median(which(inf2)[which(inf2) > pkidx])]
}
if( is.na(pt)){
# There also is no inflection point to the right of it. Resort
# to the overall median shoulder as the cutpoint like below
pt <- sp$x[median(which(shoulders))]
}
} else {
# no peak (or multiple peaks), select overall median shoulder as cutpoint (for now...)
pt <- sp$x[median(which(shoulders))]
}
# This is an absolute last resort to make sure this block never returns an NA
# due to a lack of shoulders and inflection points. In this case just return the
# simple min
if( is.na(pt)){
pt <- sp$x[which.min(sp$y)]
}
} else if (length(which(minima == TRUE)) > 1) { # multiple minima
m <- min(sp$y[which(sp$x %in% minima_xcoords)]) # pick the minima with lowest y
pt <- sp$x[which(sp$y == m)]
} else if (length(which(minima == TRUE)) == 1){ # only 1 minima, use it as cut point
pt <- sp$x[which(minima)]
}
.plots = function(){
abline(v = d2$x[which(inf1)],col="red") # red == inflection point
abline(v = d2$x[which(inf2)],col="green") # green == inflection point
abline(v = d2$x[which(maxima)],col="blue") # blue == maxima
abline(v = d2$x[which(minima)],col="orange") # orange == minima
abline(v = d2$x[which(shoulders)],col="pink") # pink == shoulders
}
if(plot){
par(mfrow=c(2,1))
plot(sp,type="l",main="features")
.plots()
plot(sp,type="l",main="final_cut")
abline(v = pt, col="black", lwd=2)
par(mfrow=c(1,1)) # be nice, restore plot settings
}
return(list(density = sp,
inf_rising = sp$x[which(inf1)],
inf_falling = sp$x[which(inf2)],
maxima = maxima_xcoords,
minima = minima_xcoords,
maxima_heights = maxima_ycoords,
minima_heights = minima_ycoords,
shoulders = sp$x[which(shoulders)],
final_cut = pt))
}
#' An improved version of mindensity used to determines a cutpoint as the minimum point
#' of a kernel density estimate between two peaks.
#'
#' Analogous to the original openCyto::mindensity(), mindensity2 operates on a standard flowFrame. Its behavior is closely modeled on
#' the original mindensity() whenever possible. However, the underlying peak-finding algorithm
#' (improvedMindensity) behaves significantly differently.
#'
#' @author Greg Finak, Phu T. Van
#'
#' @name gate_mindensity2
#' @aliases mindensity2
#' @param fr a \code{flowFrame} object
#' @param channel the channel to operate on
#' @param filterId a name to refer to this filter
#' @param pivot logical value. If \code{TRUE}, we choose as the two peaks the
#' largest peak and its neighboring peak. See details.
#' @param gate_range numeric vector of length 2. If given, this sets the bounds
#' on the gate applied.
#' @param min a numeric value that sets the lower boundary for data filtering
#' @param max a numeric value that sets the upper boundary for data filtering
#' @param peaks \code{numeric} vector. If not given , then perform peak detection first by .find_peaks
#' @param ... Additional arguments for peak detection.
#' @return a \code{rectangleGate} object based on the minimum density cutpoint
#' @examples
#' \dontrun{
#' gate <- gate_mindensity2(fr, channel = "APC-A") # fr is a flowFrame
#' }
#' @export
gate_mindensity2 <- function(fr, channel, filterId = "", pivot = FALSE,
gate_range = NULL, min = NULL, max = NULL, peaks = NULL,
...) {
if (missing(channel) || length(channel) != 1) {
stop("A single channel must be specified.")
}
# if there is
if (!(is.null(min) && is.null(max))) {
fr <- .truncate_flowframe(fr, channels = channel, min = min,
max = max)
}
x <- exprs(fr[,channel])
# this line does the actual searching of the cut point
g <- .improvedMindensity(D=x,gate_range=gate_range,...)
coords <- list(c(g$final_cut, Inf))
# name the gate and return it
names(coords) <- channel
return(rectangleGate(coords, filterId = filterId))
}
#' @export
mindensity2 <- function(fr, channel, filterId = "", pivot = FALSE,
gate_range = NULL, min = NULL, max = NULL, peaks = NULL,
...){
.Deprecated("gate_mindensity2")
gate_mindensity2(fr, channel, filterId, pivot, gate_range, min, max, peaks, ...)
}
#' wrapper for mindensity2
#'
#' It does some parameter preprocessing before calling the mindensity
#'
#' @param ... arguments to be passed to \link{mindensity}
#' @inheritParams .gate_flowclust_1d
#'
#' @return a \code{filter} object
#' @noRd
.gate_mindensity2 <- function(fr, pp_res, channels, ...) {
if(length(channels) != 1)
stop("invalid number of channels for mindensity!")
gate <- gate_mindensity2(fr, channel = channels, ...)
gate
}
.mindensity2 <- .gate_mindensity2
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.