#' A graph object returned by 'read.gatingML.cytobank' function.
#'
#' Each node corresponds to a population(or GateSet) defined in gatingML file. The actual gate object (both global and tailored gates) is
#' associated with each node as nodeData. Compensation and transformations are stored in graphData slot.
#'
#' The class simply extends the graphNEL class and exists for the purpose of method dispatching.
#'
#' @importClassesFrom graph graphNEL graphBase graph
#' @importClassesFrom Biobase AssayData
setClass("graphGML", contains = "graphNEL")
#' Parser for gatingML exported by Cytobank
#'
#' The Default parser (read.gatingML) does not parse the population tree as well as
#' the custom information from cytobank. (e.g. gate name, fcs filename).
#'
#' @param file Gating-ML XML file
#' @param ... additional arguments passed to the handlers of 'xmlTreeParse'
#' @importFrom flowCore parameters parameters<-
#' @importFrom methods is
#' @return a graphGML that represents the population tree.
#' The gate and population name are stored in nodeData of each node.
#' Compensation and transformations are stored in graphData.
#' @examples
#' \dontrun{
# acsfile <- system.file("extdata/cytobank_experiment.acs", package = "CytoML")
# ce <- open_cytobank_experiment(acsfile)
# xmlfile <- ce$gatingML
#' g <- read.gatingML.cytobank(xml) #parse the population tree
#' #plot(g) #visualize it
#' }
read.gatingML.cytobank <- function(file, ...){
#parse all the elements:gate, GateSets, comp, trans
flowEnv <- new.env()
suppressWarnings(read.gatingML(file, flowEnv))#suppress the deprecated warnings from XML
#parse gate info (id vs fcs and pop name)
gateInfo <- parse.gateInfo(file)
if(nrow(gateInfo) == 0)
stop("No gates defined in gatingML file!")
#restore the original gate parameter names
#because flowUtils add comp and tran names as prefix which is really not neccessary (even problematic) here.
for(objID in ls(flowEnv)){
obj <- flowEnv[[objID]]
if(methods::is(obj, "parameterFilter")){
# message(class(obj))
sb <- gateInfo[id == objID, ]
if(nrow(sb)>0){
orig_params <- sb[, params]
orig_params <- strsplit(split = ":", orig_params)[[1]]
params <- parameters(obj)
ind <- unlist(sapply(orig_params, function(orig_param)grep(paste0("\\Q", orig_param, "\\E$"), params)))
if(length(ind)==0)
stop("orig_param : '", paste(orig_params, collapse = ","), "'\n not matched to the prefixed params of the gate: \n'", paste(params, collapse = ","), "'")
orig_param <- orig_params[ind]
#cytobank add Comp_ prefix when the custom compensation is specified and referred by gates
#We always strip it to ensure the bare channel names are used for gates (so that flow data does not need changes)
orig_param <- sub("(^Comp_)(.*)", "\\2", orig_param)
parameters(obj) <- orig_param
flowEnv[[objID]] <- obj
}
}
}
#construct tree from GateSets
g <- constructTree(flowEnv, gateInfo)
#determine comps and trans to be used
comp_refs <- gateInfo[, comp_ref]
comp_refs <- unique(comp_refs[comp_refs!=""])
comp_refs <- comp_refs[comp_refs != "uncompensated"]
if(length(comp_refs) == 0)
comps <- "NONE"
else if(length(comp_refs) > 1)
stop("More than one compensation referred in gates!")
else{
if(comp_refs == "FCS")
comps <- "FCS"
else{
comps <- flowEnv[[comp_refs]]
#strip Comp_ prefix that was added by cytobank
# mat <- comps@spillover
# comp_param <- colnames(mat)
# comp_param <- sapply(comp_param, function(i)sub("(^Comp_)(.*)", "\\2", i), USE.NAMES = FALSE)
# colnames(mat) <- comp_param
# comps <- compensation(mat)
}
}
g@graphData[["compensation"]] <- comps
trans <- sapply(ls(flowEnv), function(i){
obj <- flowEnv[[i]]
#exclude the compensatedParameter since it also inherits transformation class
if(methods::is(obj, "transformation")&&!methods::is(obj, "compensatedParameter"))
obj
}, USE.NAMES = FALSE)
trans <- compact(trans)
chnl <- sapply(trans, function(tran)unname(parameters(tran@parameters)), USE.NAMES = FALSE)
ind <- chnl != "any"
chnl <- chnl[ind]
trans <- trans[ind]
names(trans) <- chnl
if(length(trans) > 0)
g@graphData[["transformations"]] <- trans
methods::as(g, "graphGML")
}
#' Parse the cytobank custom_info for each gate
#'
#' Fcs filename and gate name stored in 'custom_info' element are beyong the scope of
#' the gatingML standard and thus not covered by the default 'read.gatingML'.
#'
#' @param file xml file path
#' @param ... additional arguments passed to the handlers of 'xmlTreeParse'
#' @return a data.frame that contains three columns: id (gateId), name (gate name), fcs (fcs_file_filename).
#' @importFrom XML xmlRoot xmlName xmlGetAttr xmlValue xmlElementsByTagName xmlChildren
#' @import data.table
parse.gateInfo <- function(file, ...)
{
root <- xmlRoot(smartTreeParse(file,...))
rbindlist(
lapply(xmlChildren(root), function(node){
nodeName <- xmlName(node)
if(grepl("*Gate", nodeName)){
id <- xmlGetAttr(node, "id")
name <- getCustomNodeInfo(node, "name")
fcs_file_filename <- getCustomNodeInfo(node, "fcs_file_filename")
fcs_file_id <- getCustomNodeInfo(node, "fcs_file_id")
gate_id <- getCustomNodeInfo(node, "gate_id")#used to find tailored gate
if(nodeName %in% c("BooleanGate"))
{
gate_def <- comp_ref <- trans_ref <- params <- ""
}else
{
gate_def <- getCustomNodeInfo(node, "definition")
if(nodeName == "QuadrantGate"){
dimTag <- "divider"
#update id with quardant definition
quadrantList <- xmlElementsByTagName(node, "Quadrant")
id <- unname(sapply(quadrantList, function(i)xmlGetAttr(i, "id")))
}else
dimTag <- "dimension"
dimNode <- xmlElementsByTagName(node, dimTag)
comp_ref <- unique(sapply(dimNode, function(i)xmlGetAttr(i, "compensation-ref")))
comp_ref <- comp_ref[comp_ref != "uncompensated"]
if(length(comp_ref)==0)
comp_ref <- ""
else if(length(comp_ref) > 1)
stop(name, " gate is associated with multiple compensations: ", paste(comp_ref, collapse = ","))
trans_ref <- unique(unname(unlist(compact(sapply(dimNode, function(i)xmlGetAttr(i, "transformation-ref"))))))
trans_ref <- ifelse(is.null(trans_ref), "", trans_ref)
params <- paste(sapply(dimNode, function(i)xmlGetAttr(i[["fcs-dimension"]], "name")), collapse = ":")
}
# message(name)
# browser()
data.table(id = id, name = name, gate_id = gate_id, fcs = fcs_file_filename, fcs_file_id = fcs_file_id
, comp_ref = comp_ref, trans_ref = trans_ref, params = params, gate_def = gate_def
)
}
})
)
}
getCustomNodeInfo <- function(node, nodeName){
custom_node <- xmlElementsByTagName(node, nodeName, recursive = TRUE)
if(length(custom_node) >0 ){
value <- xmlValue(custom_node[[1]])
if(length(value)==0)
value <- ""
}
else
value <- ""
value
}
#' Given the leaf node, try to find out if a collection of nodes can be matched to a path in a graph(tree) by the bottom-up searching
#' @param g graphNEL
#' @param leaf the name of leaf(terminal) node
#' @param nodeSet a set of node names
#' @return TRUE if path is found, FALSE if not path is matched.
#' @importFrom graph inEdges
matchPath <- function(g, leaf, nodeSet){
if(leaf %in% nodeSet){
nodeSet <- nodeSet[-match(leaf, nodeSet)] #pop it out
leaf <- inEdges(leaf, g)[[1]]#update it with parent node
if(length(nodeSet) == 0){ #nodeSet emptied
if(length(leaf) == 0) #and path reach to the top
return (TRUE)
else
return(FALSE) #path has not reached to the top
}else
{
if(length(leaf) == 0) #reach the top before nodeSet is emptied
return(FALSE)
else
matchPath(g, leaf, nodeSet) #continue match the path against nodeSet
}
}else
return(FALSE)
}
#' Reconstruct the population tree from the GateSets
#' @param flowEnv the enivornment contains the elements parsed by read.gatingML function
#' @param gateInfo the data.frame contains the gate name, fcs filename parsed by parse.gateInfo function
#' @return a graphNEL represent the population tree. The gate and population name are stored as nodeData in each node.
#' @importFrom graph graphNEL nodeDataDefaults<- nodeData<- addEdge edges removeNode addNode
#' @importFrom jsonlite fromJSON
#' @importFrom methods slot
constructTree <- function(flowEnv, gateInfo){
#parse the references from boolean gates
#currently we use refs (which point to the gate id defined by the standard)
#we don't want to use gate_id from custom_info used by cytobank internally
#because quadrantGate seems do not have this id for each of its quadrant elements
gateSets <- sapply(ls(flowEnv), function(i){
obj <- flowEnv[[i]]
if(class(obj) == "intersectFilter"){
refs <- obj@filters
refs <- sapply(refs, methods::slot, "name")
unique(refs) #make root node depth of 1
}
})
gateSets <- compact(gateSets)
if(length(gateSets) == 0)
stop("No GateSets defined in gatingML file!")
popIds <- names(gateSets)
#sort by the depths of path
counts <- sapply(gateSets, function(i)length(i))
counts <- sort(counts)
#init the graph with all gates that have been referred
# gateIds <- unique(unlist(gateSets))
g <- graphNEL(nodes = popIds, edgemode = "directed")
nodeDataDefaults(g, "popName") <- ""
nodeDataDefaults(g, "gateInfo") <- list()
# add edges (from nDepths 2 to n)
for(popId in names(counts)){
thisGateSet <- gateSets[[popId]]
nDepth <- length(thisGateSet)
popName <- gateInfo[id == popId, name]
#add pop name
nodeData(g, popId, "popName") <- popName
message(popName)
if(nDepth == 1){#root node
gateID <- thisGateSet[1]
g <- addGate(gateInfo,flowEnv, g, popId, gateID)
}else{
#traverse all the potential parent GateSets(i.e. subset)
#must go all the way up to the root
#because the difference between parent and current node could be more than level 1
for(pDepth in (nDepth - 1):1){
parents_popIds <- names(counts[counts == pDepth])
matchRes <- findParent(gateSets, thisGateSet, parents_popIds)
if(!is.null(matchRes))
break
}
parentID <- matchRes[["parentID"]]
gateIDs <- matchRes[["gateIDs"]]
#when length(gateIDs) > 1, then we need to add each gate as a separate pop
#even though some of these gates do not have GateSet defined in gatingML
for(i in 1:length(gateIDs)){
gateID <- gateIDs[i]
if(i < length(gateIDs)){
#need to create the new pop that is not currently defined in gatingML GateSets
thisPopId <- gateID #use gateID as popID, which can not be found in gate_info
#add new pop
g <- addNode(thisPopId, g)
thisGateName <- gateInfo[id == gateID, name]
nodeData(g, thisPopId, "popName") <- paste0(thisGateName, "_0")
g <- addEdge(parentID, thisPopId, g)
g <- addGate(gateInfo,flowEnv, g, thisPopId, gateID)
parentID <- thisPopId #update the parent
}else{
thisPopId <- popId
g <- addEdge(parentID, thisPopId, g)
g <- addGate(gateInfo,flowEnv, g, thisPopId, gateID)
}
}
}
}
#prune the tree by removing the orphan nodes
# egs <- edges(g)
# leaves <- names(egs[sapply(egs, length) == 0])
# for(node in leaves){
# if(length(inEdges(node, g)[[1]]) == 0)
# g <- removeNode(node, g)
# }
g
}
findParent <- function(gateSets,thisGateSet, parents_popIds){
isFound <- FALSE
for(parentID in parents_popIds)
{
pGateSet <- gateSets[[parentID]]
#check the overlaps between two sets
if(setequal(intersect(pGateSet, thisGateSet), pGateSet))
{
isFound <- TRUE
#if it is indeed a subset of the current gateSet
gateIDs <- setdiff(thisGateSet, pGateSet) #parse out the gateID
# There might be multiple parents due to the duplicated GateSets allowed in gatingML
# it is not clear which one should be picked and
# we pick the first one for now
break
}
}
if(isFound)
return(list(parentID = parentID, gateIDs = gateIDs))
else
return(NULL)
}
addGate <- function(gateInfo,flowEnv, g, popId, gateID){
#add gate
sb <- gateInfo[id == gateID, ]
nGates <- nrow(sb)
if(nGates > 1)
stop("multiple gates found for ", gateID)
#try to find the tailored gate
tg_sb <- gateInfo[gate_id == sb[, gate_id] & fcs != "", ]
tg_gateid <- tg_sb[, id]
tg <- sapply(tg_gateid, function(gateID){
flowEnv[[gateID]]
}, simplify = FALSE)
#create index by both fcs name and fcs file id in case the input file is named by either
fcs_vs_gateid <- tg_gateid
names(fcs_vs_gateid) <- tg_sb[, fcs]
fileid_vs_gateid <- tg_gateid
names(fileid_vs_gateid) <- tg_sb[, fcs_file_id]
# message(popName)
gate <- flowEnv[[gateID]]
#parse bounding info
cytobank_gate_def <- fromJSON(sb[, gate_def])
# if(gateID == "Gate_2095590_UEQtMShIaXN0byk.")
# browser()
pname <- parameters(gate)
if(length(pname) == 1){
bound <- cytobank_gate_def[["scale"]][c("min", "max")]
bound <- t(as.matrix(bound))
rownames(bound) <- pname
}
else
bound <- t(sapply(c("x", "y"), function(axis){
cytobank_gate_def[["scale"]][[axis]][c("min", "max")]
}))
rownames(bound) <- as.vector(parameters(gate))
nodeData(g, popId, "gateInfo") <- list(list(gate = gate
, gateName = sb[, name]
, tailored_gate = list(gateid_vs_gate = tg
, file_vs_gateid = c(fcs_vs_gateid, fileid_vs_gateid)
)
, bound = bound
)
)
g
}
#' extend the gate to the minimum and maximum limit of both dimensions based on the bounding information.
#'
#' It is equivalent to the behavior of shifting the off-scale boundary events into the gate boundary that is describled in
#' bounding transformation section of gatingML standard.
#'
#' The advantage of extending gates instead of shifting data are two folds:
#' 1. Avoid the extra computation each time applying or plotting the gates
#' 2. Avoid changing the data distribution caused by adding the gates
#'
#' Normally this function is not used directly by user but invoked when parsing GatingML file exported from Cytobank.
#'
#' @export
#' @param gate a flowCore filter/gate
#' @param bound numeric matrix representing the bouding information parsed from gatingML. Each row corresponds to a channel.
#' rownames should be the channel names. colnames should be c("min", "max")
#' @param data.range numeric matrix specifying the data limits of each channel. It is used to set the extended value of vertices and must has the same structure as 'bound'.
#' when it is not supplied, c(-.Machine$integer.max, - .Machine$integer.max) is used.
#' @param limits character whether to plot in "extended" or "original" gate limits. Default is "original".
#' @param ... other arguments
#' @return a flowCore filter/gate
#' @examples
#' library(flowCore)
#' sqrcut <- matrix(c(300,300,600,600,50,300,300,50),ncol=2,nrow=4)
#' colnames(sqrcut) <- c("FSC-H","SSC-H")
#' pg <- polygonGate(filterId="nonDebris", sqrcut)
#' pg
#' bound <- matrix(c(100,3e3,100,3e3),
#' byrow = TRUE, nrow = 2,
#' dimnames = list(c("FSC-H", "SSC-H"),
#' c("min", "max")))
#' bound
#' pg.extened <- extend(pg, bound, plot = TRUE)
extend <- function(gate, bound, data.range = NULL, plot = FALSE, limits = c("original", "extended"))UseMethod("extend")
#' @export
#' @rdname extend
#' @param plot whether to plot the extended polygon.
#' @importFrom flowCore polygonGate
#' @importFrom graphics abline polygon text
#' @importFrom grDevices rgb
extend.polygonGate <- function(gate, bound, data.range = NULL, plot = FALSE, limits = c("original", "extended")){
limits <- match.arg(limits)
#linear functions
f.solve <- list(x = function(x, slope, x1, y1){
res <- slope * (x - x1) + y1
names(res) <- "y"
res
}
,y = function(y, slope, x1, y1){
res <- (y - y1) / slope + x1
names(res) <- "x"
res
}
)
#update chnnl names with generic axis names
chnls <- as.vector(rownames(bound))
axis.names <- c("x", "y")
rownames(bound) <- axis.names
verts.orig <- gate@boundaries
#fix Inf (from rectangleGate)
verts.orig[verts.orig==Inf] <- .Machine$integer.max
verts.orig[verts.orig==-Inf] <- -.Machine$integer.max
#this is a hack: We assume the polygon is convex
#if not we turn the convcave into convex
#to avoid the error
# verts.orig <- verts.orig[chull(verts.orig),]
verts <- data.table(verts.orig)
pname <- as.vector(parameters(gate))
setnames(verts, colnames(verts), pname)
ind <- match(pname, chnls)
new.name <- axis.names[ind]
setnames(verts, pname, new.name)
if(is.null(data.range)){
data.range <- data.frame(min = c(-.Machine$integer.max, - .Machine$integer.max)
, max = c(.Machine$integer.max, .Machine$integer.max)
, row.names = c("x", "y")
)
}else{
rname <- rownames(data.range)
ind <- match(rname, chnls)
rownames(data.range) <- axis.names[ind]
}
#accumulatively update verts by detecting and inserting the intersection points
#removing off-bound vertex points and inserting extended points
#for each boundary line
for(dim in axis.names){ #loop from x to y
for(bn in colnames(bound)){# loop from min to max
#extend data.range with gate range in case the later is larger
if(bn=="min")
data.range[dim, bn] <- min(data.range[dim, bn], min(verts[, ..dim]))
else
data.range[dim, bn] <- max(data.range[dim, bn], max(verts[, ..dim]))
intersect.coord <- bound[dim, bn][[1]]
names(intersect.coord) <- dim
nVerts <- nrow(verts)
# centroid <- colMeans(verts)
this.intersect <- lapply(1:nVerts, function(i){ #loop through each edge
#get two vertices
j <- ifelse(i == nVerts, 1, i + 1)
#get coordinates
y2 <- verts[j, y]
x2 <- verts[j, x]
y1 <- verts[i, y]
x1 <- verts[i, x]
#compute the slope
slope <- (y2 - y1) / (x2 - x1)
#get ranges
rg <- list(x = range(c(x1,x2)), y = range(c(y1,y2)))
this.rg <- rg[[dim]]
# points(x1, y1, col = "green")
# points(x2, y2, col = "blue")
#find inersect point
# message(i)
# if(i == 3)
if(intersect.coord <= this.rg[2]&&intersect.coord >= this.rg[1]){
#take care of horizontal/vertical edges
if(is.infinite(slope)){#vertical edge
if(dim == "x")
return(NULL) #parallel to vertical bound, no intersect
else
point <- c(x = x1, intersect.coord)
}else if(slope == 0){ #horizontal edge
if(dim == "y")
return(NULL)
else
point <- c(intersect.coord, y = y1)
}else{
#otherwise use linear function to compute the x or y coordinate
thisFun <- f.solve[[dim]]
# get the y axis of the intersected point
res <- thisFun(intersect.coord, slope, x1, y1)
# browser()
names(intersect.coord) <- dim
point <- c(intersect.coord, res)[axis.names]
}
c(point, id = i + 0.1)#jitter a positive ammount to break the tie with vert1
}
})
this.intersect.df <- do.call(rbind, this.intersect)
dim.flip <- ifelse(dim == "x", "y", "x")
# this.intersect <- this.intersect[order(this.intersect[[dim.flip]]),] #bottom to top/or left to right
nCount <- ifelse(is.null(this.intersect.df), 0, nrow(this.intersect.df))
if(nCount > 0){
if(nCount %% 2 != 0)
stop("uneven number of intersected points with ", bn, " boundary on ", dim, " axis: ", nCount)
verts[, id := 1:nVerts]#record the id before insertion
verts[, key := paste0(x,",", y)] # generate key for the comparsion to intersected points later on
#reset id for the duplicated intersects so that they won't cause chaos for the ordering later on
this.intersect.df <- as.data.table(this.intersect.df)
this.intersect.df[, key := paste0(x,",", y)]
this.intersect.df[, id := min(id), by = key]
#order by dim so that extension to pairs follows the order
setorderv(this.intersect.df, dim.flip)
inserted.ids <- NULL
#loop through each pair of extended points
for(i in seq_len(nCount/2)){
j <- 2 * i
# browser()
this.intersect <- this.intersect.df[(j -1):j, ]
# this.intersect <- as.data.table(this.intersect)
#remove the original edge points that overlap with intersect points
#but keep the intersected points inserted previously
#nchar(id) : length of significant digits
#== 1: original points
#== 2: intersected
#== 4: extended
# verts <- verts[!(key %in% this.intersect[, key] && nchar(id) == 1), ]
verts <- verts[!(key %in% this.intersect[, key]), ]
#keep track of id that has been inserted repeatly
#so that the duplicated intersects can be removed later
#otherwise, it could mess up the already removed off-bound points
dup.id <- this.intersect[id %in% inserted.ids, id]
inserted.ids <- unique(c(inserted.ids, this.intersect[, id]))
#insert the intersect points
verts <- rbindlist(list(verts, this.intersect))
verts <- verts[order(id),]
#remove off-bound points
#(no longer do it:between the two intersected points
#because sometime the points fall outside of the range of dim.flip of intersected points
#breaks the ordering assumption of two extended points)
vals.dim <- verts[, dim, with = FALSE]
vals.dim.flip <- verts[, dim.flip, with = FALSE]
rng.dim.flip <- range(this.intersect[[dim.flip]])
# ind.between <- vals.dim.flip <= rng.dim.flip[2] & vals.dim.flip >= rng.dim.flip[1]
if(bn == "min")
{
ind <- vals.dim < intersect.coord #& ind.between
}else
{
ind <- vals.dim > intersect.coord #& ind.between
}
ind <- as.vector(ind)
verts <- verts[!ind, ]
#add extended points
this.extend <- this.intersect
this.extend[, dim] <- data.range[dim, bn]
this.extend[, key := paste0(x,",", y)]#update key
#sort by the Id
this.extend[, is.smaller:= order(id) == 1]
#check if head-tail node situation
# by cal the distance between two points
this.extend[, order := match(id, verts[,id])]
dist <- abs(diff(this.extend[, order]))
if(dist == 1){#two consecutive points
this.extend[is.smaller == TRUE, id := id + 0.01]
this.extend[is.smaller == FALSE, id := this.extend[is.smaller == TRUE, id] + 0.01]
}else{
#deal with head-tail points
this.extend[is.smaller == TRUE, id := id - 0.01]
this.extend[is.smaller == FALSE, id := this.extend[is.smaller == TRUE, id] - 0.01]
}
# browser()
this.extend[, is.smaller := NULL]
this.extend[, order := NULL]
#remove dup id
verts <- verts[!id%in%dup.id, ]
verts <- rbindlist(list(verts, this.extend))
verts <- verts[order(id),]
}#end loop for each pair of intersects
#also make sure to remove the off-bound points outside of any intersected points range
vals.dim <- verts[, dim, with = FALSE]
vals.dim.flip <- verts[, dim.flip, with = FALSE]
rng.dim.flip <- range(this.intersect.df[[dim.flip]])
ind.between <- vals.dim.flip <= rng.dim.flip[2] & vals.dim.flip >= rng.dim.flip[1]
if(bn == "min")
ind <- vals.dim < intersect.coord & !ind.between
else
ind <- vals.dim > intersect.coord & !ind.between
ind <- as.vector(ind)
verts <- verts[!ind, ]
}#end if of ncount>0
}#end of inner for loop for boundary (from min to max)
}#end of outer for loop for dim (from x to y)
# if(isTRUE(parent.frame(1)[["popName"]] == "T-helpers"))
# browser()
if(plot){
if(limits == "extended")
plot(type = "n", x = verts[[1]], y = verts[[2]])
else
plot(type = "n", x = verts.orig[,1], y = verts.orig[,2])
polygon(verts.orig, lwd = 4, border = grDevices::rgb(0, 0, 0,0.5))
# points(verts.orig, col = "red")
# points(t(as.data.frame(colMeans(verts.orig))), col = "red")
# abline(v = bound[1,], lty = "dashed", col = "red")
# abline(h = bound[2,], lty = "dashed", col = "red")
text(verts, labels = verts[, id], col = "red")
# points(intersect.points[, c(axis.names)], col = "blue")
polygon(verts, lty = "dotted", border = "green", lwd = 3)
}
verts <- verts[, new.name, with = FALSE]
setnames(verts, new.name, chnls)
polygonGate(verts)
}
#' @export
#' @rdname extend
#' @importFrom flowCore rectangleGate
extend.rectangleGate <- function(gate, ...){
pnames <- parameters(gate)
nParam <- length(pnames)
if(nParam == 2){
gate <- fix.rectangleGate(gate)
extend(methods::as(gate, "polygonGate"), ...)
}else{
extend.rectangleGate1d(gate, ...)
}
}
extend.rectangleGate1d <- function(gate, bound, data.range = NULL)
{
if(is.null(data.range))
data.range <- c(min = -.Machine$integer.max, max= .Machine$integer.max)
if(gate@min <= bound[, "min"][[1]])
gate@min <- data.range["min"]
if(gate@max >= bound[, "max"][[1]])
gate@max <- data.range["max"]
gate
}
#' @export
#' @rdname extend
#' @importFrom flowCore ellipsoidGate
#' @importFrom methods as
extend.ellipsoidGate <- function(gate, ...){
#fix the ellipsoidGate parsed from gatingML
gate <- fix.ellipsoidGate(gate)
extend(methods::as(gate, "polygonGate"), ...)
}
fix.ellipsoidGate <- function(gate){
pnames <- as.vector(parameters(gate))
dimnames(gate@cov) <- list(pnames,pnames)
names(gate@mean) <- pnames
gate
}
fix.rectangleGate <- function(gate){
pnames <- parameters(gate)
names(gate@min) <- pnames
names(gate@max) <- pnames
gate
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.