#' Write a compensation matrix into a fcs file
#'
#' When a compensation matrix is made in flowjo it only exists in the workspace. It is applied like a mask on the fcs file.
#' In case the workspace is lost or gets corrupted, the compensation matrix is lost. To avoid that one may want to hard-code the matrix into
#' the fcs-file. This function takes a csv file of a compensation matrix as exported from flowjo and writes it into the SPILL-keyword
#' of a fcs file. Moreover, a full compensation matrix is generated: You may have noticed that a compensation matrix purely generated
#' in flowjo is not complete - spillover into channels can only be assigned for existing compensation controls. This is different to
#' a matrix generated by BDs DIVA software where spillover into all channels can be assigned, no matter if for respective channel a
#' compensation control was recorded. E.g. for a live/dead-channel we are generally only interested in spillover into the channel, not
#' which spillover comes out of it, as dead cells are not of interest in most cases.
#'
#' @param fcs_file_path, character, file path to the fcs file
#' @param compMat_file_path character, file path to the csv file of the compensation matrix
#' @param max_match_dist numeric, maximum string distance for matching channel names using utils::adist
#' @param skip_check logical, whether to ask for confirmation by the user if channel names were matched correctly
#'
#' @return no return but an fcs file with an updated compensation matrix (SPILL keyword) on disk
#' @export
#'
#' @examples
#'\dontrun{
#'compMat_to_fcs(fcs_file_path = "myfolder/my_file.fcs",
#'compMat_file_path = "myfolder/compmat_export_from_flowjo.csv")
#' }
compMat_to_fcs <- function(fcs_file_path, compMat_file_path, max_match_dist = 1, skip_check = F) {
if (!requireNamespace("BiocManager", quietly = T)){
utils::install.packages("BiocManager")
}
if (!requireNamespace("flowCore", quietly = T)){
BiocManager::install("flowCore")
}
if (!file.exists(compMat_file_path)) {
stop("compMat not found.")
}
if (!file.exists(fcs_file_path)) {
stop("fcs_file not found.")
}
if (!methods::is(compMat_file_path, "character") || rev(strsplit(basename(compMat_file_path), "\\.")[[1]])[1] != "csv") {
stop("compMat has to be character (path to a csv file).")
}
compMat <- utils::read.csv(compMat_file_path, header = T, row.names = 1, check.names = F)
if (!identical(rownames(compMat), colnames(compMat))) {
stop("colnames and rownames of compMat have to be equal.")
}
ff <- flowCore::read.FCS(fcs_file_path, truncate_max_range = F, emptyValue = F)
sp <- flowCore::keyword(ff)[["SPILL"]]
sp <- prep_spill(sp = sp, compMat = compMat, max_match_dist = max_match_dist, skip_check = skip_check, verbose = T)
flowCore::keyword(ff)[["SPILL"]] <- sp
flowCore::write.FCS(ff, fcs_file_path)
message(fcs_file_path)
}
prep_spill <- function(sp, compMat, max_match_dist = 1, skip_check = T, verbose = F) {
# sp is SPILL keyword from fcs file
# compMat is the matrix generated elsewhere (e.g. FlowJo)
# max_match_dist is the maximum allowed string distance between channel names for matching; channel names from FCCF always contain a "/" which is replaced by "_"
original_colnames_sp <- colnames(sp)
colnames(sp) <- gsub("/", "_", colnames(sp))
rownames(sp) <- colnames(sp)
if (!all(colnames(compMat) %in% colnames(sp))) {
if (verbose) {
message("Not all colnames of compMat found in those of the SPILLOVER keyword matrix from the FCS file.")
}
# match channel names
if (any(apply(utils::adist(colnames(compMat), colnames(sp)), 1, min) > max_match_dist)) {
print(colnames(compMat)[apply(utils::adist(colnames(compMat), colnames(sp)), 1, min) > max_match_dist])
print(colnames(sp))
stop("Too big string distances between channel names of compMat and FCS file. Please, check the column names or make sure you provide the correct compensation matrix.")
}
match_ind <- apply(utils::adist(colnames(compMat), colnames(sp)), 1, which.min)
if (any(duplicated(match_ind))) {
stop("Channel names from compMat not uniquely matched to channel names from FCS file.")
}
if (verbose) {
message("Matched channel names:")
print(data.frame(compMat = colnames(compMat), FCS = colnames(sp)[match_ind]))
}
colnames(compMat) <- colnames(sp)[match_ind]
rownames(compMat) <- colnames(sp)[match_ind]
if (!skip_check) {
if (interactive()) {
choice <- utils::menu(c("Yes", "No"), title = "Channel names matched correctly - Continue?")
if (choice == 2) {
return(NULL)
}
}
}
}
for (rr in rownames(compMat)) {
for (cc in colnames(compMat)) {
sp[which(rownames(sp) == rr), which(colnames(sp) == cc)] <- compMat[which(rownames(compMat) == rr), which(colnames(compMat) == cc)]
}
}
rownames(sp) <- NULL
colnames(sp) <- original_colnames_sp
return(sp)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.