# This file was copied from the flowUtils package on 5/6/2019. # This represents the work of Josef Spidlen and the other flowUtils # authors who are also accordingly credited in the authors list for CytoML. # # Details: # https://github.com/jspidlen/flowUtils/ # GitHub version: 1.35.8 # Bioconductor version: 1.49.0 ############################################# ## Code related to writing Gating-ML files ## ############################################# # NOTE: If somebody has the time, it may be nice to # rewrite this in an object-oriented fashion :-) # Write objects in the flowEnv environment to an Gating-ML 2.0 XML file. # If file is NULL then output is written to standard output. write.gatingML <- function(flowEnv, file = NULL) { if(!is.null(file) && !is(file, "character")) stop("A file has to be either NULL or a character string.", call. = FALSE) if(is.null(flowEnv) || !is.environment(flowEnv)) stop("A flowEnv environment with objects to be saved is requred.", call. = FALSE) if(!is.null(file) && substr(file, nchar(file) - 3, nchar(file)) != ".xml") file <- paste(file, "xml", sep=".") flowEnv[['.debugMessages']] = c() namespaces <- c( "gating" = "http://www.isac-net.org/std/Gating-ML/v2.0/gating", "xsi" = "http://www.w3.org/2001/XMLSchema-instance", "transforms" = "http://www.isac-net.org/std/Gating-ML/v2.0/transformations", "data-type" = "http://www.isac-net.org/std/Gating-ML/v2.0/datatypes") gatingMLNode = suppressWarnings(xmlTree("gating:Gating-ML", namespaces = namespaces, attrs = c("xsi:schemaLocation" = "http://www.isac-net.org/std/Gating-ML/v2.0/gating http://flowcyt.sourceforge.net/gating/2.0/xsd/Gating-ML.v2.0.xsd http://www.isac-net.org/std/Gating-ML/v2.0/transformations http://flowcyt.sourceforge.net/gating/2.0/xsd/Transformations.v2.0.xsd http://www.isac-net.org/std/Gating-ML/v2.0/datatypes http://flowcyt.sourceforge.net/gating/2.0/xsd/DataTypes.v2.0.xsd"))) gatingMLNode$addNode("data-type:custom_info", close = FALSE) gatingMLNode$addNode("info", "Gating-ML 2.0 export generated by R/flowCore/CytoML") gatingMLNode$addNode("R-version", sessionInfo()$R.version$version.string) gatingMLNode$addNode("flowCore-version", as.character(packageVersion("flowCore"))) gatingMLNode$addNode("CytoML-version", as.character(packageVersion("CytoML"))) gatingMLNode$addNode("XML-version", as.character(packageVersion("XML"))) gatingMLNode$closeTag() flowEnv[['.objectIDsWrittenToXMLOutput']] = list() # Use this list to collect XML Ids somethingUseful = FALSE for (x in ls(flowEnv)) { object = objectNameToObject(x, flowEnv) if(is(object, "parameterFilter") || is(object, "singleParameterTransform") || is(object, "setOperationFilter")) { somethingUseful = TRUE break } } if(!somethingUseful) warning("Nothing useful seems to be present in the environment; the output Gating-ML file may not be very useful.", call. = FALSE) # Go over everything and temporarily add transformations and argument gates to flowEnv # if they are not saved in flowEnv directly, but they are being used in other objects flowEnv[['.addedObjects']] = list() # List of object identifiers of objects that we have to temporarily add to flowEnv for (x in ls(flowEnv)) addReferencedObjectsToEnv(x, flowEnv) flowEnv[['.singleParTransforms']] = new.env() # Use this env to collect transformations for (x in ls(flowEnv)) if(is(flowEnv[[x]], "singleParameterTransform")) collectTransform(x, flowEnv) # Transforms go first unless they can be skipped all together for (x in ls(flowEnv)) if(is(flowEnv[[x]], "transform")) if(!shouldTransformationBeSkipped(x, flowEnv)) addObjectToGatingML(gatingMLNode, x, flowEnv) for (x in ls(flowEnv)) if(!is(flowEnv[[x]], "transform")) addObjectToGatingML(gatingMLNode, x, flowEnv) if(!is.null(file)) sink(file = file) cat(saveXML(gatingMLNode$value(), encoding = "UTF-8")) if(!is.null(file)) sink() rm(list = ls(flowEnv[['.singleParTransforms']], all.names = TRUE), envir = flowEnv[['.singleParTransforms']]) rm('.singleParTransforms', envir = flowEnv) rm(list = as.character(flowEnv[['.addedObjects']]), envir = flowEnv) rm('.addedObjects', envir = flowEnv) rm('.objectIDsWrittenToXMLOutput', envir = flowEnv) } # Add the object x to the Gating-ML node addObjectToGatingML <- function(gatingMLNode, x, flowEnv, addParent = NULL, forceGateId = NULL) { if(is(x, "character")) object = flowEnv[[x]] else object = x switch(class(object), "rectangleGate" = addRectangleGateNode(gatingMLNode, x, flowEnv, addParent, forceGateId), "polygonGate" = addPolygonGateNode(gatingMLNode, x, flowEnv, addParent, forceGateId), "ellipsoidGate" = addEllipsoidGateNode(gatingMLNode, x, flowEnv, addParent, forceGateId), "quadGate" = addQuadGateNode(gatingMLNode, x, flowEnv, addParent, forceGateId), "intersectFilter" = addBooleanAndGateNode(gatingMLNode, x, flowEnv, addParent, forceGateId), "unionFilter" = addBooleanOrGateNode(gatingMLNode, x, flowEnv, addParent, forceGateId), "complementFilter" = addBooleanNotGateNode(gatingMLNode, x, flowEnv, addParent, forceGateId), "subsetFilter" = addGateWithParent(gatingMLNode, x, flowEnv), "compensation" = addCompensation(gatingMLNode, x, flowEnv), "asinhtGml2" = addAsinhtGml2(gatingMLNode, x, flowEnv), "hyperlogtGml2" = addHyperlogtGml2(gatingMLNode, x, flowEnv), "lintGml2" = addLintGml2(gatingMLNode, x, flowEnv), "logtGml2" = addLogtGml2(gatingMLNode, x, flowEnv), "logicletGml2" = addLogicletGml2(gatingMLNode, x, flowEnv), "ratiotGml2" = addRatiotGml2(gatingMLNode, x, flowEnv), "ratio" = addRatioGml1.5(gatingMLNode, x, flowEnv), "asinht" = addAsinhtGml1.5(gatingMLNode, x, flowEnv), "compensatedParameter" = NA, "unitytransform" = NA, "numeric" = NA, { errMessage <- paste("Class \'", class(object), "\' is not supported in Gating-ML 2.0 output.", sep="") if(is(object, "singleParameterTransform")) errMessage <- paste(errMessage, " Only Gating-ML 2.0 compatible transformations are supported by Gating-ML 2.0 output. Transformation \'", object@transformationId, "\' is not among those and cannot be included. Therefore, any gate referencing this transformation would be referencing a non-existent transformation in the Gating-ML output. Please correct the gates and transformations in your environment and try again.", sep="") if(is(object, "filter")) errMessage <- paste(errMessage, " Only Gating-ML 2.0 compatible gates are supported by Gating-ML 2.0 output. Filter \'", object@filterId, "\' is not among those and cannot be included. Please remove this filter and any references to it from the environment and try again.", sep="") stop(errMessage, call. = FALSE) } ) } # Add rectangle gate x to the Gating-ML node addRectangleGateNode <- function(gatingMLNode, x, flowEnv, addParent, forceGateId) { gate = objectNameToObject(x, flowEnv) if(!is(gate, "rectangleGate")) stop(paste("Unexpected object insted of a rectangleGate - ", class(gate))) addDebugMessage(paste("Working on rectangleGate ", gate@filterId, sep=""), flowEnv) myID = getObjectId(gate, forceGateId, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("gating:id" = myID) if (!is.null(addParent)) attrs = c(attrs, "gating:parent_id" = filterIdtoXMLId(addParent, flowEnv)) gatingMLNode$addNode("gating:RectangleGate", attrs = attrs, close = FALSE) addDimensions(gatingMLNode, x, flowEnv) gatingMLNode$closeTag() # </gating:RectangleGate> } # Add polygon gate x to the Gating-ML node addPolygonGateNode <- function(gatingMLNode, x, flowEnv, addParent, forceGateId) { gate = objectNameToObject(x, flowEnv) if(!is(gate, "polygonGate")) stop(paste("Unexpected object insted of a polygonGate - ", class(gate))) addDebugMessage(paste("Working on polygonGate ", gate@filterId, sep=""), flowEnv) myID = getObjectId(gate, forceGateId, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("gating:id" = myID) if (!is.null(addParent)) attrs = c(attrs, "gating:parent_id" = filterIdtoXMLId(addParent, flowEnv)) gatingMLNode$addNode("gating:PolygonGate", attrs = attrs, close = FALSE) addDimensions(gatingMLNode, x, flowEnv) for (i in 1:length(gate@boundaries[,1])) { gatingMLNode$addNode("gating:vertex", close = FALSE) # attrs = c("data-type:value" = gate@boundaries[i,1]) attrs = c("data-type:value" = as.numeric(gate@boundaries[i,1])) gatingMLNode$addNode("gating:coordinate", attrs = attrs) # attrs = c("data-type:value" = gate@boundaries[i,2]) attrs = c("data-type:value" = as.numeric(gate@boundaries[i,2])) gatingMLNode$addNode("gating:coordinate", attrs = attrs) gatingMLNode$closeTag() # </gating:vertex> } gatingMLNode$closeTag() # </gating:PolygonGate> } # Add ellipse gate x to the Gating-ML node addEllipsoidGateNode <- function(gatingMLNode, x, flowEnv, addParent, forceGateId) { gate = objectNameToObject(x, flowEnv) if(!is(gate, "ellipsoidGate")) stop(paste("Unexpected object insted of an ellipsoidGate - ", class(gate))) addDebugMessage(paste("Working on ellipsoidGate ", gate@filterId, sep=""), flowEnv) myID = getObjectId(gate, forceGateId, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("gating:id" = myID) if (!is.null(addParent)) attrs = c(attrs, "gating:parent_id" = filterIdtoXMLId(addParent, flowEnv)) gatingMLNode$addNode("gating:EllipsoidGate", attrs = attrs, close = FALSE) addDimensions(gatingMLNode, x, flowEnv) gatingMLNode$addNode("gating:mean", close = FALSE) for (i in 1:length(gate@mean)) { attrs = c("data-type:value" = as.numeric(gate@mean[i])) gatingMLNode$addNode("gating:coordinate", attrs = attrs) } gatingMLNode$closeTag() # </gating:mean> gatingMLNode$addNode("gating:covarianceMatrix", close = FALSE) for (row in 1:length(gate@cov[,1])) { gatingMLNode$addNode("gating:row", close = FALSE) for (column in 1:length(gate@cov[1,])) { attrs = c("data-type:value" = gate@cov[row,column]) gatingMLNode$addNode("gating:entry", attrs = attrs) } gatingMLNode$closeTag() # </gating:row> } gatingMLNode$closeTag() # </gating:covarianceMatrix> attrs = c("data-type:value" = gate@distance ^ 2) gatingMLNode$addNode("gating:distanceSquare", attrs = attrs) gatingMLNode$closeTag() # </gating:EllipsoidGate> } # Add a Boolean AND gate x to the Gating-ML node addBooleanAndGateNode <- function(gatingMLNode, x, flowEnv, addParent, forceGateId) { gate = objectNameToObject(x, flowEnv) if(!is(gate, "intersectFilter")) stop(paste("Unexpected object insted of an intersectFilter - ", class(gate))) addDebugMessage(paste("Working on intersectFilter ", gate@filterId, sep=""), flowEnv) myID = getObjectId(gate, forceGateId, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("gating:id" = myID) if (!is.null(addParent)) attrs = c(attrs, "gating:parent_id" = filterIdtoXMLId(addParent, flowEnv)) gatingMLNode$addNode("gating:BooleanGate", attrs = attrs, close = FALSE) gatingMLNode$addNode("gating:and", close = FALSE) if(length(gate@filters) == 0) stop("Boolean AND gates (intersectFilter) have to reference some arguments.", call. = FALSE) for (i in 1:length(gate@filters)) { attrs = c("gating:ref" = filterIdtoXMLId(gate@filters[[i]]@filterId, flowEnv)) gatingMLNode$addNode("gating:gateReference", attrs = attrs) } if(length(gate@filters) == 1) { # If there was just one referenced filter than we add it twice # since and/or gates require at least two arguments in Gating-ML 2.0 gatingMLNode$addNode("gating:gateReference", attrs = attrs) } gatingMLNode$closeTag() # </gating:and> gatingMLNode$closeTag() # </gating:BooleanGate> } # Add a Boolean OR gate x to the Gating-ML node addBooleanOrGateNode <- function(gatingMLNode, x, flowEnv, addParent, forceGateId) { gate = objectNameToObject(x, flowEnv) if(!is(gate, "unionFilter")) stop(paste("Unexpected object insted of a unionFilter - ", class(gate))) addDebugMessage(paste("Working on unionFilter ", gate@filterId, sep=""), flowEnv) myID = getObjectId(gate, forceGateId, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("gating:id" = myID) if (!is.null(addParent)) attrs = c(attrs, "gating:parent_id" = filterIdtoXMLId(addParent, flowEnv)) gatingMLNode$addNode("gating:BooleanGate", attrs = attrs, close = FALSE) gatingMLNode$addNode("gating:or", close = FALSE) if(length(gate@filters) == 0) stop("Boolean OR gates (unionFilter) have to reference some arguments.", call. = FALSE) for (i in 1:length(gate@filters)) { attrs = c("gating:ref" = filterIdtoXMLId(gate@filters[[i]]@filterId, flowEnv)) gatingMLNode$addNode("gating:gateReference", attrs = attrs) } if(length(gate@filters) == 1) { # If there was just one referenced filter than we add it twice # since and/or gates require at least two arguments in Gating-ML 2.0 gatingMLNode$addNode("gating:gateReference", attrs = attrs) } gatingMLNode$closeTag() # </gating:or> gatingMLNode$closeTag() # </gating:BooleanGate> } # Add a Boolean NOT gate x to the Gating-ML node addBooleanNotGateNode <- function(gatingMLNode, x, flowEnv, addParent, forceGateId) { gate = objectNameToObject(x, flowEnv) if(!is(gate, "complementFilter")) stop(paste("Unexpected object insted of a complementFilter - ", class(gate))) addDebugMessage(paste("Working on complementFilter ", gate@filterId, sep=""), flowEnv) myID = getObjectId(gate, forceGateId, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("gating:id" = myID) if (!is.null(addParent)) attrs = c(attrs, "gating:parent_id" = filterIdtoXMLId(addParent, flowEnv)) gatingMLNode$addNode("gating:BooleanGate", attrs = attrs, close = FALSE) gatingMLNode$addNode("gating:not", close = FALSE) if(length(gate@filters) == 1) { attrs = c("gating:ref" = filterIdtoXMLId(gate@filters[[1]]@filterId, flowEnv)) gatingMLNode$addNode("gating:gateReference", attrs = attrs) } else stop("Boolean NOT gates (complementFilter) have to reference exactly one argument.", call. = FALSE) gatingMLNode$closeTag() # </gating:not> gatingMLNode$closeTag() # </gating:BooleanGate> } # Add a Quadrant gate x to the Gating-ML node addQuadGateNode <- function(gatingMLNode, x, flowEnv, addParent, forceGateId) { gate = objectNameToObject(x, flowEnv) if(!is(gate, "quadGate")) stop(paste("Unexpected object insted of a quadGate - ", class(gate))) addDebugMessage(paste("Working on quadGate ", gate@filterId, sep=""), flowEnv) myID = getObjectId(gate, forceGateId, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("gating:id" = myID) if (!is.null(addParent)) attrs = c(attrs, "gating:parent_id" = filterIdtoXMLId(addParent, flowEnv)) gatingMLNode$addNode("gating:QuadrantGate", attrs = attrs, close = FALSE) addDimensions(gatingMLNode, x, flowEnv, myID) attrs = c("gating:id" = paste(myID, ".PP", sep = "")) gatingMLNode$addNode("gating:Quadrant", attrs = attrs, close = FALSE) attrs = c("gating:divider_ref" = paste(myID, ".D1", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[1] + 1)) gatingMLNode$addNode("gating:position", attrs = attrs) attrs = c("gating:divider_ref" = paste(myID, ".D2", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[2] + 1)) gatingMLNode$addNode("gating:position", attrs = attrs) gatingMLNode$closeTag() # </gating:Quadrant> attrs = c("gating:id" = paste(myID, ".PN", sep = "")) gatingMLNode$addNode("gating:Quadrant", attrs = attrs, close = FALSE) attrs = c("gating:divider_ref" = paste(myID, ".D1", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[1] + 1)) gatingMLNode$addNode("gating:position", attrs = attrs) attrs = c("gating:divider_ref" = paste(myID, ".D2", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[2] - 1)) gatingMLNode$addNode("gating:position", attrs = attrs) gatingMLNode$closeTag() # </gating:Quadrant> attrs = c("gating:id" = paste(myID, ".NP", sep = "")) gatingMLNode$addNode("gating:Quadrant", attrs = attrs, close = FALSE) attrs = c("gating:divider_ref" = paste(myID, ".D1", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[1] - 1)) gatingMLNode$addNode("gating:position", attrs = attrs) attrs = c("gating:divider_ref" = paste(myID, ".D2", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[2] + 1)) gatingMLNode$addNode("gating:position", attrs = attrs) gatingMLNode$closeTag() # </gating:Quadrant> attrs = c("gating:id" = paste(myID, ".NN", sep = "")) gatingMLNode$addNode("gating:Quadrant", attrs = attrs, close = FALSE) attrs = c("gating:divider_ref" = paste(myID, ".D1", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[1] - 1)) gatingMLNode$addNode("gating:position", attrs = attrs) attrs = c("gating:divider_ref" = paste(myID, ".D2", sep = "")) attrs = c(attrs, "gating:location" = as.character(gate@boundary[2] - 1)) gatingMLNode$addNode("gating:position", attrs = attrs) gatingMLNode$closeTag() # </gating:Quadrant> gatingMLNode$closeTag() # </gating:QuadrantGate> } # Add a subsetFilter gate named x to the the Gating-ML node addGateWithParent <- function(gatingMLNode, x, flowEnv) { addDebugMessage(paste("Working on ", x, sep=""), flowEnv) gate = objectNameToObject(x, flowEnv) if (!is(gate, "subsetFilter")) stop(paste("Expected a subsetFilter to add a gate with a parent id, but found an object of class", class(gate))) if (length(gate@filters) == 2){ newX = gate@filters[[1]] parent = gate@filters[[2]] if (is(parent, 'filterReference')) parentName = parent@name else parentName = parent@filterId addObjectToGatingML(gatingMLNode, newX, flowEnv, parentName, gate@filterId) } else stop(paste("Unexpected length of filters for class", class(gate))) } # Add a compensation named x to the the Gating-ML node addCompensation <- function(gatingMLNode, x, flowEnv) { myComp = objectNameToObject(x, flowEnv) if(!is(myComp, "compensation")) stop(paste("Unexpected object insted of a compensation - ", class(myComp))) addDebugMessage(paste("Working on compensation ", myComp@compensationId, sep=""), flowEnv) myID = getObjectId(myComp, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) detectors <- colnames(myComp@spillover) if (is.null(detectors)) { stop(paste("Cannot export a spillover matrix without column names (", myComp@compensationId, ").", sep="")) return } fluorochromes <- rownames(myComp@spillover) if(is.null(fluorochromes)) { if(nrow(myComp@spillover) != ncol(myComp@spillover)) { stop(paste("Cannot export a non-sqaure spillover (spectrum) matrix without row names (", myComp@compensationId, ").", sep="")) return } else { fluorochromes <- detectors } } attrs = c("transforms:id" = myID) gatingMLNode$addNode("transforms:spectrumMatrix", attrs = attrs, close = FALSE) gatingMLNode$addNode("transforms:fluorochromes", close = FALSE) for (fname in fluorochromes) { attrs = c("data-type:name" = fname) gatingMLNode$addNode("data-type:fcs-dimension", attrs = attrs) } gatingMLNode$closeTag() # </transforms:fluorochromes> gatingMLNode$addNode("transforms:detectors", close = FALSE) for (dname in detectors) { attrs = c("data-type:name" = dname) gatingMLNode$addNode("data-type:fcs-dimension", attrs = attrs) } gatingMLNode$closeTag() # </transforms:detectors> for (rowNo in 1:nrow(myComp@spillover)) { gatingMLNode$addNode("transforms:spectrum", close = FALSE) for (colNo in 1:ncol(myComp@spillover)) { # attrs = c("transforms:value" = myComp@spillover[rowNo,colNo]) attrs = c("transforms:value" = as.vector(myComp@spillover[rowNo,colNo])) gatingMLNode$addNode("transforms:coefficient", attrs = attrs) } gatingMLNode$closeTag() # </transforms:spectrum> } gatingMLNode$closeTag() # </transforms:spectrumMatrix> } # Add an asinhtGml2 transformation named x to the the Gating-ML node addAsinhtGml2 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "asinhtGml2")) stop(paste("Unexpected object insted of asinhtGml2 - ", class(myTrans))) addDebugMessage(paste("Working on asinhtGml2 ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) if (is.finite(myTrans@boundMin)) attrs = append(attrs, c("transforms:boundMin" = myTrans@boundMin)) if (is.finite(myTrans@boundMax)) attrs = append(attrs, c("transforms:boundMax" = myTrans@boundMax)) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) attrs = c("transforms:T" = myTrans@T, "transforms:M" = myTrans@M, "transforms:A" = myTrans@A) gatingMLNode$addNode("transforms:fasinh", attrs = attrs) gatingMLNode$closeTag() # </transforms:transformation> } # Add an asinht transformation named x to the the Gating-ML node. # Encode asinht from Gating-ML 1.5 compatible parameterization using Gating-ML 2.0 # compatible parameterization as follows: # # asinht (ASinH from Gating-ML 1.5) is defined as # f(x) = asinh(a*x)*b # asinhtGml2 (fasinh from Gating-ML 2.0) is defined as: # f(x) = (asinh(x*sinh(M*log(10))/T) + A*log(10)) / ((M+A)*log(10)) # Therefore, we will encode asinht as asinhtGml2 by stating # A = 0 # M = 1 / (b * log(10)) # T = (sinh(1/b)) / a # which will give us exactly the right transformation in the Gating-ML 2.0 # compatible parameterization. Btw. log is natural logarithm, i.e., based e addAsinhtGml1.5 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "asinht")) stop(paste("Unexpected object insted of asinht - ", class(myTrans))) addDebugMessage(paste("Working on asinht ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) A = 0 M = 1 / (myTrans@b * log(10)) T = (sinh(1/myTrans@b)) / myTrans@a attrs = c("transforms:T" = T, "transforms:M" = M, "transforms:A" = A) gatingMLNode$addNode("transforms:fasinh", attrs = attrs) gatingMLNode$closeTag() # </transforms:transformation> } # Add a hyperlogtGml2 transformation named x to the the Gating-ML node addHyperlogtGml2 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "hyperlogtGml2")) stop(paste("Unexpected object insted of hyperlogtGml2 - ", class(myTrans))) addDebugMessage(paste("Working on hyperlogtGml2 ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) if (is.finite(myTrans@boundMin)) attrs = append(attrs, c("transforms:boundMin" = myTrans@boundMin)) if (is.finite(myTrans@boundMax)) attrs = append(attrs, c("transforms:boundMax" = myTrans@boundMax)) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) attrs = c("transforms:T" = myTrans@T, "transforms:M" = myTrans@M, "transforms:W" = myTrans@W, "transforms:A" = myTrans@A) gatingMLNode$addNode("transforms:hyperlog", attrs = attrs) gatingMLNode$closeTag() # </transforms:transformation> } # Add a logicletGml2 transformation named x to the the Gating-ML node addLogicletGml2 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "logicletGml2")) stop(paste("Unexpected object insted of logicletGml2 - ", class(myTrans))) addDebugMessage(paste("Working on logicletGml2 ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) if (is.finite(myTrans@boundMin)) attrs = append(attrs, c("transforms:boundMin" = myTrans@boundMin)) if (is.finite(myTrans@boundMax)) attrs = append(attrs, c("transforms:boundMax" = myTrans@boundMax)) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) attrs = c("transforms:T" = myTrans@T, "transforms:M" = myTrans@M, "transforms:W" = myTrans@W, "transforms:A" = myTrans@A) gatingMLNode$addNode("transforms:logicle", attrs = attrs) gatingMLNode$closeTag() # </transforms:transformation> } # Add a lintGml2 transformation named x to the the Gating-ML node addLintGml2 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "lintGml2")) stop(paste("Unexpected object insted of lintGml2 - ", class(myTrans))) addDebugMessage(paste("Working on lintGml2 ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) if (is.finite(myTrans@boundMin)) attrs = append(attrs, c("transforms:boundMin" = myTrans@boundMin)) if (is.finite(myTrans@boundMax)) attrs = append(attrs, c("transforms:boundMax" = myTrans@boundMax)) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) attrs = c("transforms:T" = myTrans@T, "transforms:A" = myTrans@A) gatingMLNode$addNode("transforms:flin", attrs = attrs) gatingMLNode$closeTag() # </transforms:transformation> } # Add a logtGml2 transformation named x to the the Gating-ML node addLogtGml2 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "logtGml2")) stop(paste("Unexpected object insted of logtGml2 - ", class(myTrans))) addDebugMessage(paste("Working on logtGml2 ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) if (is.finite(myTrans@boundMin)) attrs = append(attrs, c("transforms:boundMin" = myTrans@boundMin)) if (is.finite(myTrans@boundMax)) attrs = append(attrs, c("transforms:boundMax" = myTrans@boundMax)) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) attrs = c("transforms:T" = myTrans@T, "transforms:M" = myTrans@M) gatingMLNode$addNode("transforms:flog", attrs = attrs) gatingMLNode$closeTag() # </transforms:transformation> } # Add a ratiotGml2 transformation named x to the the Gating-ML node addRatiotGml2 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "ratiotGml2")) stop(paste("Unexpected object insted of ratiotGml2 - ", class(myTrans))) addDebugMessage(paste("Working on ratiotGml2 ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) if (is.finite(myTrans@boundMin)) attrs = append(attrs, c("transforms:boundMin" = myTrans@boundMin)) if (is.finite(myTrans@boundMax)) attrs = append(attrs, c("transforms:boundMax" = myTrans@boundMax)) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) attrs = c("transforms:A" = myTrans@pA, "transforms:B" = myTrans@pB, "transforms:C" = myTrans@pC) gatingMLNode$addNode("transforms:fratio", attrs = attrs, close = FALSE) addDimensionContents(gatingMLNode, myTrans@numerator, flowEnv) addDimensionContents(gatingMLNode, myTrans@denominator, flowEnv) gatingMLNode$closeTag() # </transforms:fratio> gatingMLNode$closeTag() # </transforms:transformation> } # Add a ratio transformation (from Gating-ML 1.5) named x # to the the Gating-ML node. This will be translated to how "fratio" of Gating-ML 2.0 # (When we set A = 1, B = 0, C = 0 then ratio of Gating-ML 1.5 == fratio of Gating-ML 2.0) addRatioGml1.5 <- function(gatingMLNode, x, flowEnv) { myTrans = objectNameToObject(x, flowEnv) if(!is(myTrans, "ratio")) stop(paste("Unexpected object insted of ratio - ", class(myTrans))) addDebugMessage(paste("Working on ratio ", myTrans@transformationId, sep=""), flowEnv) myID = getObjectId(myTrans, NULL, flowEnv) if(isIdWrittenToXMLAlready(myID, flowEnv)) return(FALSE) attrs = c("transforms:id" = myID) gatingMLNode$addNode("transforms:transformation", attrs = attrs, close = FALSE) attrs = c("transforms:A" = "1", "transforms:B" = "0", "transforms:C" = "0") gatingMLNode$addNode("transforms:fratio", attrs = attrs, close = FALSE) addDimensionContents(gatingMLNode, myTrans@numerator, flowEnv) addDimensionContents(gatingMLNode, myTrans@denominator, flowEnv) gatingMLNode$closeTag() # </transforms:fratio> gatingMLNode$closeTag() # </transforms:transformation> } # Add a Gating-ML dimension to a Gating-ML node addDimensions <- function(gatingMLNode, x, flowEnv, quadGateDividerIdBasedName = NULL) { gate = objectNameToObject(x, flowEnv) for (i in 1:length(gate@parameters)) { attrs = c() parameter = gate@parameters[[i]] if (is(gate, "rectangleGate")) { min = gate@min[[i]] max = gate@max[[i]] if(min != -Inf) attrs = c(attrs, "gating:min" = min) if(max != Inf) attrs = c(attrs, "gating:max" = max) } if(is(parameter, "transformReference")) parameter = resolveTransformationReference(parameter) if(is(parameter, "unitytransform")) attrs = c(attrs, "gating:compensation-ref" = "uncompensated") else if(is(parameter, "singleParameterTransform")) { attrs = c(attrs, "gating:transformation-ref" = filterIdtoXMLId(parameter@transformationId, flowEnv)) parameter = parameter@parameters if(is(parameter, "transformReference")) parameter = resolveTransformationReference(parameter) if(is(parameter, "unitytransform")) attrs = c(attrs, "gating:compensation-ref" = "uncompensated") else if(is(parameter, "compensatedParameter")) attrs = addCompensationRef(attrs, parameter, flowEnv) else if(is(parameter, "ratiotGml2") || is(parameter, "ratio")) attrs = addCompensationRef(attrs, parameter@numerator, flowEnv) else stop(paste("Unexpected parameter class ", class(parameter), ", compound transformations are not supported in Gating-ML 2.0.", sep="")) } else if(is(parameter, "compensatedParameter")) attrs = addCompensationRef(attrs, parameter, flowEnv) else if(is(parameter, "ratiotGml2") || is(parameter, "ratio")) attrs = addCompensationRef(attrs, parameter@numerator, flowEnv) else stop(paste("Unexpected parameter class", class(parameter), "- not supported in Gating-ML 2.0 output).")) if(is(gate, "quadGate")) { attrs = c(attrs, "gating:id" = paste(quadGateDividerIdBasedName, ".D", i, sep = "")) gatingMLNode$addNode("gating:divider", attrs = attrs, close = FALSE) } else gatingMLNode$addNode("gating:dimension", attrs = attrs, close = FALSE) addDimensionContents(gatingMLNode, parameter, flowEnv) if (is(gate, "quadGate")) gatingMLNode$addNode("gating:value", as.character(gate@boundary[i])) gatingMLNode$closeTag() # </gating:dimension> or </gating:divider> } } # Add the contents of a Gating-ML dimension to a Gating-ML node addDimensionContents <- function(gatingMLNode, parameter, flowEnv) { newDimension = FALSE if(is(parameter, "compensatedParameter")) { if (parameter@spillRefId == "SpillFromFCS") attrs = c("data-type:name" = parameter@parameters) else attrs = c("data-type:name" = parameter@transformationId) } else if(is(parameter, "unitytransform")) attrs = c("data-type:name" = parameter@parameters) else if(is(parameter, "character")) attrs = c("data-type:name" = parameter) else if(is(parameter, "ratiotGml2") || is(parameter, "ratio")) { attrs = c("data-type:transformation-ref" = parameter@transformationId) newDimension = TRUE } else stop(paste("Unrecognized parameter type, class ", class(parameter), ". Note that compound transformations are not supported in Gating-ML 2.0.", sep="")) if(newDimension) gatingMLNode$addNode("data-type:new-dimension", attrs = attrs) else gatingMLNode$addNode("data-type:fcs-dimension", attrs = attrs) } # This converts the identifier to an XML safe identifier and also, # if it is a singleParameterTransform and we have a different # 'representative' transform for those (saved in flowEnv[['.singleParTransforms']]) # then the identifier of the representative is used instead. filterIdtoXMLId <- function(x, flowEnv) { if(!(is.character(x))) stop(paste("Object of class", class(x), "cannot be converted to an XML identifier.")) if(length(x) <= 0) stop(paste("An empty string cannot be converted to an XML identifier.")) # First, if it is a singleParameterTransform then check for a representative and use it instead eventually trEnv = flowEnv[['.singleParTransforms']] trans = flowEnv[[x]] if(!is.null(trEnv) && !is.null(trans) && is(trans, "singleParameterTransform")) { key = createTransformIdentifier(trans) if (!is.null(trEnv[[key]])) x = trEnv[[key]] } # Now make it a safe XML identifier # 1) Put an underscore prefix if it starts with a number if(substr(x, 1, 1) >= "0" && substr(x, 1, 1) <= "9") x = paste("_", x, sep="") # 2) Replace 'strange characters with '.' for(i in 1:nchar(x)) { if(!isNCNameChar(substr(x, i, i))) x <- paste(substr(x, 0, i - 1), '.', substr(x, i + 1, nchar(x)), sep= "") } x } # Return true if you are sure that the character is safe to be placed in # an XML identifier. isNCNameChar <- function(char) { # Based on the ASCII table and XML NCName syntax asciiValue = as.numeric(charToRaw(char)) if(asciiValue < 45) return(FALSE) if(asciiValue == 47) return(FALSE) if(asciiValue >= 58 && asciiValue <= 64) return(FALSE) if(asciiValue >= 91 && asciiValue <= 94) return(FALSE) if(asciiValue == 96) return(FALSE) if(asciiValue >= 123) return(FALSE) TRUE } # Returns TRUE if and only if x is a singleParameterTransform # and there is another equivalent singleParameterTransform # in flowEnv that is the chosen representative among all # equivalent transforms. This is used to merge transforms # for Gating-ML 2.0 output since in Gating-ML 2.0, the same # transformation is applicable to many FCS parameters. For us, # the transformation with the shortest identifier is the chosen # representative. This function requires the flowEnv[['.singleParTransforms']] # to be set by calling the collectTransform function on all available # transforms before shouldTransformationBeSkipped can be used. shouldTransformationBeSkipped <- function(x, flowEnv) { trEnv = flowEnv[['.singleParTransforms']] trans = flowEnv[[x]] if(!is.null(trEnv) && !is.null(trans) && is(trans, "singleParameterTransform")) { key = createTransformIdentifier(trans) if (!is.null(trEnv[[key]])){ if (x == trEnv[[key]]) FALSE else TRUE } else FALSE } else FALSE } # Resolve transformation reference, return the transformation that the # reference is pointing to. resolveTransformationReference <- function(trRef) { if(!is(trRef, "transformReference")) stop(paste("Cannot call resolveTransformationReference on", class(trRef))) if(exists(trRef@transformationId, envir=trRef@searchEnv, inherits=FALSE)) trRef@searchEnv[[trRef@transformationId]] else stop(paste("Cannot find", trRef@transformationId, "in the environment.")) } # This will create an identifier of a singleParameterTransform that # is based on the class and slot values, such as T, M, W, A, etc. as applicable # for the various single parameter transformations. We will use this to # merge "the same transformations" applied to different FCS parameter into a single # transformation in the Gating-ML 2.0 output. createTransformIdentifier <- function(trans) { name <- class(trans) for (slotName in slotNames(trans)) { if(slotName != ".Data" && slotName != "parameters" && slotName != "transformationId") { slotValue = slot(trans, slotName) if(is(slotValue, "numeric") || is(slotValue, "character")) { name <- paste(name, slotName, slot(trans, slotName), sep = "_") } } } name } # The flowEnv[['.singleParTransforms']] environment will serve as a hashmap # with keys based on values returned by createTransformIdentifier and # values being the shortest transformationId value of all the transformations # matching that key. That way, we can merge all these transformations into # a single one in Gating-ML. collectTransform <- function(x, flowEnv) { trEnv = flowEnv[['.singleParTransforms']] trans = flowEnv[[x]] key = createTransformIdentifier(trans) if (is.null(trEnv[[key]]) || length(trEnv[[key]]) > trans@transformationId) trEnv[[key]] = trans@transformationId } # Add a debug message to out list of debug messages in flowEnv[['.debugMessages']] addDebugMessage <- function(msg, flowEnv) { flowEnv[['.debugMessages']] = c(flowEnv[['.debugMessages']], msg) } # Return TRUE of the provided id has been checked (and supposedly written) # before. Otherwise, add the id to the list in flowEnv[['.objectIDsWrittenToXMLOutput']] # and retusn FALSE. This function is used to prevent writing multiple objects # with the same ID to the Gating-ML output in case a gate or transformation # with the same ID is stored several times in the flowEnv. isIdWrittenToXMLAlready <- function(id, flowEnv) { idsList = flowEnv[['.objectIDsWrittenToXMLOutput']] if (is.null(idsList[[id]])) { idsList[[id]] = TRUE flowEnv[['.objectIDsWrittenToXMLOutput']] = idsList FALSE } else { addDebugMessage(paste("ID", id, "should be in the Gating-ML file already."), flowEnv) TRUE } } # Add an appropriate gating:compensation-ref attribute to the passed attrs addCompensationRef <- function(attrs, parameter, flowEnv) { if(is(parameter, "unitytransform")) attrs = c(attrs, "gating:compensation-ref" = "uncompensated") else if(is(parameter, "compensatedParameter")) { if (parameter@spillRefId != "SpillFromFCS") attrs = c(attrs, "gating:compensation-ref" = filterIdtoXMLId(parameter@spillRefId, flowEnv)) else attrs = c(attrs, "gating:compensation-ref" = "FCS") } else stop(paste("Unexpected parameter class", class(parameter))) attrs } # Add to attrs the gating:min and/or gating:max attributes # based on dimension number i of a rectangle gate gate. addRectGateMinMax <- function(attrs, gate, i) { if (is(gate, "rectangleGate")) { min = gate@min[[i]] max = gate@max[[i]] if(min != -Inf) attrs = c(attrs, "gating:min" = min) if(max != Inf) attrs = c(attrs, "gating:max" = max) } else stop(paste("Unexpected gate class", class(gate), "- expected a rectangleGate.")) attrs } # Get the XML compliant identifier of an object. This only works for object of type # "filter", "transform" or "compensation". The filterIdtoXMLId function is incorporated, # which includes the use of representative singleParameterTransforms instead of a different # transform whenever it is applied to a different FCS parameter. getObjectId <- function(object, forceGateId, flowEnv) { if (is(object, "filter")) { if (is.null(forceGateId)) myID = filterIdtoXMLId(object@filterId, flowEnv) else myID = filterIdtoXMLId(forceGateId, flowEnv) } else if (is(object, "transform")) { if (is.null(forceGateId)) myID = filterIdtoXMLId(object@transformationId, flowEnv) else myID = filterIdtoXMLId(forceGateId, flowEnv) } else if (is(object, "compensation")) { if (is.null(forceGateId)) myID = filterIdtoXMLId(object@compensationId, flowEnv) else myID = filterIdtoXMLId(forceGateId, flowEnv) } else stop(paste("Unexpected object to get id from, class", class(object))) myID } # If x is character then return flowEnv[[x]], otherwise return x objectNameToObject <- function(x, flowEnv) { if(is(x, "character")) flowEnv[[x]] else x } # Check object named x in flowEnv and make sure # flowEnv contains objects referenced from x, such as parameter # transformations used in x. If objects are missing then # add them to flowEnv and keep track of what has been # added in the flowEnv[['.addedObjects']] list so that it can be # removed at the end of the write.gatingML function. addReferencedObjectsToEnv <- function(x, flowEnv) { object = objectNameToObject(x, flowEnv) if(is(object, "parameterFilter")) for(par in object@parameters) doubleCheckExistanceOfParameter(par, flowEnv) else if (is(object, "singleParameterTransform")) doubleCheckExistanceOfParameter(object@parameters, flowEnv) else if (is(object, "setOperationFilter")) for(filt in object@filters) doubleCheckExistanceOfFilter(filt, flowEnv) } # If par is a transform then check whether it exists in the flowEnv environment, # and if it doesn't then add it there and make a note of it in flowEnv[['.addedObjects']] doubleCheckExistanceOfParameter <- function(par, flowEnv) { if(is(par, "transform")) { if(!is.null(par@transformationId) && par@transformationId != "" && !exists(par@transformationId, envir=flowEnv, inherits=FALSE)) { flowEnv[[par@transformationId]] <- par flowEnv[['.addedObjects']][[par@transformationId]] <- par@transformationId addReferencedObjectsToEnv(par@transformationId, flowEnv) } } } # If filt is a concreteFilter then check whether it exists in the flowEnv environment, # and if it doesn't then add it there and make a note of it in flowEnv[['.addedObjects']] doubleCheckExistanceOfFilter <- function(filt, flowEnv) { if(is(filt, "concreteFilter")) { if(!is.null(filt@filterId) && filt@filterId != "" && !exists(filt@filterId, envir=flowEnv, inherits=FALSE)) { flowEnv[[filt@filterId]] <- filt flowEnv[['.addedObjects']][[filt@filterId]] <- filt@filterId addReferencedObjectsToEnv(filt@filterId, flowEnv) } } }