R/compMat_to_FCS.R

Defines functions prep_spill compMat_to_fcs

Documented in compMat_to_fcs

#' 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)
}
Close-your-eyes/fcexpr documentation built on Sept. 29, 2023, 12:27 a.m.