Skip to contents

Our analysis starts with data import. While some flow cytometry acquisition software export data from different samples as separate .fcs files, others export a single, merged .fcs file containing data from multiple samples. The $NXTDATA keyword can be used to retrieve data from individual samples.

In flowFate, users can upload a merged file or individual files. If individual files are uploaded, they must be grouped inside a folder.

Note: Imported files must be FCS3.0.

A - Merged file upload

1) Fetch the number of datasets

We first fetch the number of datasets contained in the merged .fcs file (code adapted from RGLab).

n_datasets <- function(filename) {
  # Adapted code from https://github.com/RGLab/flowCore/blob/ba3b6ffed5310c1c0618487ab163c0142d8cab8f/R/IO.R

  # the keyword $NEXTDATA contains either a zero when there are no next data
  # or a positive integer for the next dataset
  counter <- 0
  nextdata <- 1
  while (nextdata != 0) {
    counter <- counter + 1
    nextdata <- read.FCSheader(filename, keyword = "$NEXTDATA", emptyValue = FALSE, dataset = counter)
    nextdata <- as.integer(nextdata[[1]]["$NEXTDATA"])
  }
  counter
}

2) Write individual .fcs files

We write the individual datasets inside the merged .fcs file as individual .fcs files to a temporary folder using the custom split_1_fcs( ) function. We append the $WELLID keyword to the filename.

split_1_fcs <- function(nb, input_file) {
  # create a folder named fcs_input in a temporary location
  if (!dir_exists(path(path_dir(input_file), "fcs_input"))) dir_create(path(path_dir(input_file), "fcs_input"))
  
  # walk along every dataset and read it as a flowFrame (data structure from flowCore)
  walk(seq_len(nb_ds), \(x) {
    fr <- read.FCS(filename, dataset = x,
                   transformation = FALSE,
                   truncate_max_range = FALSE,
                   alter.names = TRUE,
                   emptyValue = FALSE)
    message(paste("Write file #", x, "well", fr@description$`$WELLID`))
    
    # write flowframes to individual FCS files inside the fcs_input folder
    write.FCS(fr, fs::path("fcs_input", paste0("dataset_", fr@description$`$WELLID`, ".fcs")))
  })
  
  # return the complete path of the fcs_input folder
  path(path_dir(input_file), "fcs_input")
}

# create a reactive expression that, when called, executes the split_1_fcs() function and returns a directory
individual_fcs <- reactive(split_1_fcs(nb_ds(), input$filename$datapath))

3) Create a flowSet

We read the individual .fcs files (stored in the fcs_input temporary folder) into a flowSet, a data structure from the flowCore package). The reactive expression individual_fcs() returns the directory of fcs_input temporary folder.

fs <- reactive(read.flowSet(fs::dir_ls(individual_fcs(), glob = "*.fcs"), 
                            truncate_max_range = FALSE,
                            alter.names = TRUE,
                            transformation = FALSE))

B - Individual file upload

Note: Individual files (all FCS3.0) have to be grouped inside a folder. Steps 1) and 2) from above are now dispensable and we can start by creating a flowSet.

1) Create a flowSet

fs <- reactive({
  read.flowSet(path = filename(),
               truncate_max_range = FALSE,
               alter.names = TRUE,
               transformation = FALSE,
               emptyValue = FALSE)
})

Note: filename() is a reactive expression capturing the patch of the folder a user uploads.

C - Create a GatingSet

The flowFrame and flowSet data structures from flowCore are very useful for handling flow cytometry data in R. The GatingSet data structure from flowWorkspace is, however, better suited for storing and manipulating gated data.

1) Create a GatingSet from a flowSet

We use the GatingSet() function from flowWorkspace to create a GatingSet from the previous flowSet.

gs <- reactive({GatingSet(fs())})

D - Sharing reactive values across modules

The {golem} framework promotes the use of modules to split a Shiny app into separate, smaller parts. Different techniques are available in {golem} to allow communication between modules. We opted for the “stratégie du petit r”. Briefly, a list of reactive values - called r - is passed along modules.

1) Completing the reactive list r

We add the GatingSet, flowSet and nb_ds (number of datasets) reactive expression to the reactive list. These variables can now be used in other modules by calling r$gs, r$fs or r$nb_ds. To read reactive expressions in Shiny, they have to be wrapped in inside observe( ).

    observe({
      r$gs <- GatingSet(fs())
      r$fs <- fs()
      r$nb_ds <- nb_ds()
    })