Fluorescence Signatures
David Rach
University of Maryland, Baltimoredrach@som.umaryland.edu
30 October 2024
Source:vignettes/FluorescenceSignatures.Rmd
FluorescenceSignatures.Rmd
Introduction
In spectral flow cytometry (SFC), having good quality unmixing controls (both single color and unstained) is critical to the ability to take a full-stained sample and be able to decipher on an individual cell basis what fluorophore is present and the relative amount. Each single color unmixing control is itself a combination of the staining fluorophore and the underlying autofluorescence. Once autofluorescence is subtracted, the leftover signal should be distinct enough to be differentiated from other fluorophores in the reference matrix for unmixing using ordinary least squares to be effective.
When the above assumptions are broken, we end up with uncertainty in the calculation, which becomes visible in the form of unmixing errors. However, despite the importance foundational to the final result, we have limited tools by which we can address the quality of our unmixing controls beyond trial and errors.
This specific vignette addresses the Luciernaga functions that are focused on characterizing fluorescence signatures from individual .fcs files. They work on both unstained .fcs files where no subtraction takes place, as well as on single color .fcs files, where autofluorescent background (either internally or externally-derived) is first subtracted. The outputs can be either in a data.frame format that can be used for report generation, or as purified .fcs made up of populations of cells sharing the same normalized signature.
This vignette will cover the pre-processing and report generation components of the process. For the Unmixing steps using the resulting files, please refer to the unmixing vignette. We hope this allows a better understanding of how fluorophore signature and brightness impacts the typically ordinal least squares methods that are used to unmix full-stained samples.
Getting Started
To use the following Luciernaga functions, we will be working with raw .FCS files that retain their detector information (ex. UV7-A, V7-A, B3-A, etc.). We will bring these files into GatingSets comprising both unstained and single color unmixing controls, divided by whether the reference controls are cells or beads for ease of explanation.
Install Required Packages Libraries
Luciernaga relies on the infrastructure provided by other cytometry R packages like flowCore and flowWorkspace available through Bioconductor. It also relies on tidyverse packages available via CRAN. It is important to make sure that each of the following packages is installed on your computer before beginning. If you are new to R, an example of how to do this is provided below:
# To Install a CRAN Package:
install.packages("dplyr")
install.packages("BiocManager")
# To Install a Bioconductor Package:
BiocManager::install("flowCore")
Once you have the packages installed, you make sure that their contents are accessible by calling them via library:
Locating .fcs files
To get started, you will first need to provide the location on your computer where the .fcs files of interest are being stored. An example of how the author does this is provided below and can be modified for your own user and desired computer folder.
File_Location <- file.path("C:", "Users", "JohnDoe", "Desktop",
"TodaysExperiment")
FCS_Pattern <- ".fcs$"
FCS_Files <- list.files(path = File_Location, pattern = FCS_Pattern,
full.names = TRUE, recursive = FALSE)
For this vignette, we will using several small .fcs files that are stored in Luciernaga’s extdata folder for use in our examples.
File_Location <- system.file("extdata", package = "Luciernaga")
FCS_Pattern <- ".fcs$"
FCS_Files <- list.files(path = File_Location, pattern = FCS_Pattern,
full.names = TRUE, recursive = FALSE)
head(FCS_Files[10:30], 20)
#> [1] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/21_Before.fcs"
#> [2] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/22_After.fcs"
#> [3] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/22_Before.fcs"
#> [4] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/23_After.fcs"
#> [5] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/23_Before.fcs"
#> [6] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/4BeadsUnstained(Beads).fcs"
#> [7] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CCR4_BUV615(Beads).fcs"
#> [8] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CCR4_BUV615(Cells).fcs"
#> [9] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CCR6_BV786(Beads).fcs"
#> [10] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CCR6_BV786(Cells).fcs"
#> [11] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CCR7_BV650(Beads).fcs"
#> [12] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CCR7_BV650(Cells).fcs"
#> [13] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD107a_APC-R700(Beads).fcs"
#> [14] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD107a_APC-R700(Cells).fcs"
#> [15] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD127_BV421(Beads).fcs"
#> [16] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD127_BV421(Cells).fcs"
#> [17] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD16_APC(Beads).fcs"
#> [18] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD16_APC(Cells).fcs"
#> [19] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD161_BV480(Beads).fcs"
#> [20] "C:/Users/12692/AppData/Local/R/win-library/4.4/Luciernaga/extdata/CD161_BV480(Cells).fcs"
As you can see, there are a lot of different .fcs files that are used for different examples for the different vignettes, we will be selectively filtering from this list to get specific files that we need.
UnstainedFCSFiles <- FCS_Files[grep("Unstained", FCS_Files)]
UnstainedBeads <- UnstainedFCSFiles[grep("Beads", UnstainedFCSFiles)]
UnstainedCells <- UnstainedFCSFiles[-grep("Beads", UnstainedFCSFiles)]
BeadFCSFiles <- FCS_Files[grep("Beads", FCS_Files)]
BeadSingleColors <- BeadFCSFiles[-grep("Unstained", BeadFCSFiles)]
CellSingleColorFiles <- FCS_Files[grep("Cells", FCS_Files)]
CellSingleColors <- CellSingleColorFiles[!str_detect("Unstained", CellSingleColorFiles)]
Creating a GatingSet Bead Single Colors
The bead single color controls are part of a 29-color SFC panel. They additionally have cell single color equivalents that we will compare to in a separate GatingSet in the next section.
Now that we have filtered the .fcs files into smaller list, we can use the flowWorkspace to bring these individual .fcs files first into a CytoSet object, then into a GatingSet object that we can add gates to:
MyBeadsCytoSet <- load_cytoset_from_fcs(BeadSingleColors,
truncate_max_range = FALSE,
transform = FALSE)
MyBeadsCytoSet
#> A cytoset with 30 samples.
#>
#> column names:
#> Time, UV1-A, UV2-A, UV3-A, UV4-A, UV5-A, UV6-A, UV7-A, UV8-A, UV9-A, UV10-A, UV11-A, UV12-A, UV13-A, UV14-A, UV15-A, UV16-A, SSC-W, SSC-H, SSC-A, V1-A, V2-A, V3-A, V4-A, V5-A, V6-A, V7-A, V8-A, V9-A, V10-A, V11-A, V12-A, V13-A, V14-A, V15-A, V16-A, FSC-W, FSC-H, FSC-A, SSC-B-W, SSC-B-H, SSC-B-A, B1-A, B2-A, B3-A, B4-A, B5-A, B6-A, B7-A, B8-A, B9-A, B10-A, B11-A, B12-A, B13-A, B14-A, YG1-A, YG2-A, YG3-A, YG4-A, YG5-A, YG6-A, YG7-A, YG8-A, YG9-A, YG10-A, R1-A, R2-A, R3-A, R4-A, R5-A, R6-A, R7-A, R8-A
MyBeadsGatingSet <- GatingSet(MyBeadsCytoSet)
MyBeadsGatingSet
#> A GatingSet with 30 samples
For this example, we will use the openCyto package to automatically gate each of our .fcs files for the bead singlets. To do this, we first read in the example .csv file containing our desired gates (that can be found in Luciernaga’s extdata folder) using the data.table package
FileLocation <- system.file("extdata", package = "Luciernaga")
MyBeadsGates <- fread(file.path(path = FileLocation,
pattern = 'GatesBeads.csv'))
gt(MyBeadsGates)
alias | pop | parent | dims | gating_method | gating_args | collapseDataForGating | groupBy | preprocessing_method | preprocessing_args |
---|---|---|---|---|---|---|---|---|---|
nonDebris | - | root | FSC-A | gate_mindensity | gate_range=c(6e5, 7e5) | FALSE | NA | NA | NA |
beads | + | nonDebris | FSC-A, SSC-A | flowClust | K=2, target=c(5e4, 1e5) | FALSE | NA | NA | NA |
singlets | + | beads | FSC-A,FSC-H | singletGate | FALSE | NA | NA | NA |
For your own experiments, individual gates can be added, removed or modified to match the requirements of your own .fcs files, for additional details, please refer to the openCyto packages vignettes. Alternatively, GatingSet objects can be brought directly from several commercial software formats using the CytoML package.
Now that we have the gating information from the .csv file, we can convert them into a GatingTemplate, and append them to the .fcs files contained within the GatingSet
MyBeadsGatingTemplate <- gatingTemplate(MyBeadsGates)
gt_gating(MyBeadsGatingTemplate, MyBeadsGatingSet)
MyBeadsGatingSet[[1]]
#> Sample: CCR4_BUV615(Beads).fcs
#> GatingHierarchy with 4 gates
plot(MyBeadsGatingSet)
And finally check the gate placements using the
Utility_GatingPlots
function:
removestrings <- c("(Cells)", ".fcs", " ")
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
IndividualBeadPlot <- Utility_GatingPlots(x=MyBeadsGatingSet[[1]],
sample.name = "GUID",
removestrings = removestrings,
gtFile = MyBeadsGates,
DesiredGates = NULL,
outpath = StorageLocation,
export = FALSE)
IndividualBeadPlot
#> $`1`
IteratedBeadPlots <- map(.x = MyBeadsGatingSet[1:3], .f = Utility_GatingPlots,
sample.name = "GUID", removestrings = removestrings,
gtFile = MyBeadsGates, DesiredGates = NULL,
outpath = StorageLocation, export = FALSE)
IteratedBeadPlots
#> [[1]]
#> [[1]]$`1`
#>
#>
#> [[2]]
#> [[2]]$`1`
#>
#>
#> [[3]]
#> [[3]]$`1`
#gs_pop_get_count_fast(MyBeadsGatingSet)
Creating a GatingSet Cell Single Colors
These cell single color controls are part of 29-color SFC panel, they are the cell counterparts to the bead reference controls we worked with in the prior section. Having isolated the list of desired .fcs files at the start of the vignette, we can use the flowWorkspace to bring these individual .fcs files first into a CytoSet object, then into a GatingSet object that we can add gates to:
MyCytoSet <- load_cytoset_from_fcs(CellSingleColors,
truncate_max_range = FALSE,
transform = FALSE)
MyCytoSet
#> A cytoset with 30 samples.
#>
#> column names:
#> Time, UV1-A, UV2-A, UV3-A, UV4-A, UV5-A, UV6-A, UV7-A, UV8-A, UV9-A, UV10-A, UV11-A, UV12-A, UV13-A, UV14-A, UV15-A, UV16-A, SSC-W, SSC-H, SSC-A, V1-A, V2-A, V3-A, V4-A, V5-A, V6-A, V7-A, V8-A, V9-A, V10-A, V11-A, V12-A, V13-A, V14-A, V15-A, V16-A, FSC-W, FSC-H, FSC-A, SSC-B-W, SSC-B-H, SSC-B-A, B1-A, B2-A, B3-A, B4-A, B5-A, B6-A, B7-A, B8-A, B9-A, B10-A, B11-A, B12-A, B13-A, B14-A, YG1-A, YG2-A, YG3-A, YG4-A, YG5-A, YG6-A, YG7-A, YG8-A, YG9-A, YG10-A, R1-A, R2-A, R3-A, R4-A, R5-A, R6-A, R7-A, R8-A
MyGatingSet <- GatingSet(MyCytoSet)
MyGatingSet
#> A GatingSet with 30 samples
For this example, we will use the openCyto package to automatically gate each of our .fcs files for the lymphocyte population. To do this, we first read in the example .csv file containing our desired gates (that can be found in Luciernaga’s extdata folder) using the data.table package
FileLocation <- system.file("extdata", package = "Luciernaga")
MyGates <- fread(file.path(path = FileLocation, pattern = 'Gates.csv'))
gt(MyGates)
alias | pop | parent | dims | gating_method | gating_args | collapseDataForGating | groupBy | preprocessing_method | preprocessing_args |
---|---|---|---|---|---|---|---|---|---|
singletsFSC | + | root | FSC-A,FSC-H | singletGate | FALSE | NA | NA | NA | |
singletsSSC | + | singletsFSC | SSC-A,SSC-H | singletGate | FALSE | NA | NA | NA | |
singletsSSCB | + | singletsSSC | SSC-A,SSC-B-A | singletGate | FALSE | NA | NA | NA | |
nonDebris | + | singletsSSCB | FSC-A | gate_mindensity | FALSE | NA | NA | NA | |
lymphocytes | + | nonDebris | FSC-A, SSC-A | flowClust | K=2, target=c(1e5, 5e4) | FALSE | NA | NA | NA |
For your own experiments, individual gates can be added, removed or modified to match the requirements of your own .fcs files, for additional details, please refer to the openCyto packages vignettes. Alternatively, GatingSet objects can be brought directly from several commercial software formats using the CytoML package.
Now that we have the gating information from the .csv file, we can convert them into a GatingTemplate, and append them to the .fcs files contained within the GatingSet
MyGatingTemplate <- gatingTemplate(MyGates)
gt_gating(MyGatingTemplate, MyGatingSet)
MyGatingSet[[1]]
#> Sample: CCR4_BUV615(Cells).fcs
#> GatingHierarchy with 6 gates
And finally check the gate placements using:
removestrings <- c("(Cells)", ".fcs", " ")
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
IndividualPlot <- Utility_GatingPlots(x=MyGatingSet[[2]],
sample.name = "GUID",
removestrings = removestrings,
gtFile = MyGates,
DesiredGates = NULL,
outpath = StorageLocation,
export = FALSE)
#IndividualPlot
IteratedPlots <- map(.x = MyGatingSet[1:2], .f = Utility_GatingPlots,
sample.name = "GUID", removestrings = removestrings,
gtFile = MyGates, DesiredGates = NULL,
outpath = StorageLocation, export = FALSE)
IteratedPlots[[2]]
#> $`1`
#>
#> $`2`
Creating a GatingSet Bead Unstained
This unstained Bead cell control is by itself and could have been included in the Bead Single Color Gating Set, but we are including it in its own GatingSet for ease-of-locating reasons only. Now that we have filtered the .fcs file of interest, we can use the flowWorkspace to bring these individual .fcs files first into a CytoSet object, then into a GatingSet object that we can add gates to:
MyUnstainedBeadsCytoSet <- load_cytoset_from_fcs(UnstainedBeads,
truncate_max_range = FALSE,
transform = FALSE)
MyUnstainedBeadsCytoSet
#> A cytoset with 1 samples.
#>
#> column names:
#> Time, UV1-A, UV2-A, UV3-A, UV4-A, UV5-A, UV6-A, UV7-A, UV8-A, UV9-A, UV10-A, UV11-A, UV12-A, UV13-A, UV14-A, UV15-A, UV16-A, SSC-W, SSC-H, SSC-A, V1-A, V2-A, V3-A, V4-A, V5-A, V6-A, V7-A, V8-A, V9-A, V10-A, V11-A, V12-A, V13-A, V14-A, V15-A, V16-A, FSC-W, FSC-H, FSC-A, SSC-B-W, SSC-B-H, SSC-B-A, B1-A, B2-A, B3-A, B4-A, B5-A, B6-A, B7-A, B8-A, B9-A, B10-A, B11-A, B12-A, B13-A, B14-A, YG1-A, YG2-A, YG3-A, YG4-A, YG5-A, YG6-A, YG7-A, YG8-A, YG9-A, YG10-A, R1-A, R2-A, R3-A, R4-A, R5-A, R6-A, R7-A, R8-A
MyUnstainedBeadsGatingSet <- GatingSet(MyUnstainedBeadsCytoSet)
MyUnstainedBeadsGatingSet
#> A GatingSet with 1 samples
For this example, we will use the openCyto package to automatically gate each of our .fcs files for the bead singlets. To do this, we first read in the example .csv file containing our desired gates (that can be found in Luciernaga’s extdata folder) using the data.table package
# Failing here?
FileLocation <- system.file("extdata", package = "Luciernaga")
MyBeadsGates <- fread(file.path(path = FileLocation,
pattern = 'GatesBeads.csv'))
gt(MyBeadsGates)
alias | pop | parent | dims | gating_method | gating_args | collapseDataForGating | groupBy | preprocessing_method | preprocessing_args |
---|---|---|---|---|---|---|---|---|---|
nonDebris | - | root | FSC-A | gate_mindensity | gate_range=c(6e5, 7e5) | FALSE | NA | NA | NA |
beads | + | nonDebris | FSC-A, SSC-A | flowClust | K=2, target=c(5e4, 1e5) | FALSE | NA | NA | NA |
singlets | + | beads | FSC-A,FSC-H | singletGate | FALSE | NA | NA | NA |
For your own experiments, individual gates can be added, removed or modified to match the requirements of your own .fcs files, for additional details, please refer to the openCyto packages vignettes. Alternatively, GatingSet objects can be brought directly from several commercial software formats using the CytoML package.
Now that we have the gating information from the .csv file, we can convert them into a GatingTemplate, and append them to the .fcs files contained within the GatingSet
MyBeadsGatingTemplate <- gatingTemplate(MyBeadsGates)
gt_gating(MyBeadsGatingTemplate, MyUnstainedBeadsGatingSet)
MyUnstainedBeadsGatingSet[[1]]
#> Sample: 4BeadsUnstained(Beads).fcs
#> GatingHierarchy with 4 gates
plot(MyBeadsGatingSet)
And finally check the gate placements using the
Utility_GatingPlots
function:
removestrings <- c("(Cells)", ".fcs", " ")
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
IndividualBeadPlot <- Utility_GatingPlots(x=MyUnstainedBeadsGatingSet[[1]],
sample.name = "GUID",
removestrings = removestrings,
gtFile = MyBeadsGates,
DesiredGates = NULL,
outpath = StorageLocation,
export = FALSE)
IndividualBeadPlot
#> $`1`
Creating a GatingSet Cell Unstained Controls
These unstained cell controls were collected for CBMC and PBMC specimens, and stimulated with either PMA-ionomycin or control. They are useful to evaluate heterogeneity present in autofluorescence across donors and treatment conditions. Having isolated the list of desired .fcs files at the start of the vignette, we can use the flowWorkspace to bring these individual .fcs files first into a CytoSet object, then into a GatingSet object that we can add gates to:
MyUnstainedCytoSet <- load_cytoset_from_fcs(UnstainedCells,
truncate_max_range = FALSE,
transform = FALSE)
MyUnstainedCytoSet
#> A cytoset with 18 samples.
#>
#> column names:
#> Time, UV1-A, UV2-A, UV3-A, UV4-A, UV5-A, UV6-A, UV7-A, UV8-A, UV9-A, UV10-A, UV11-A, UV12-A, UV13-A, UV14-A, UV15-A, UV16-A, SSC-W, SSC-H, SSC-A, V1-A, V2-A, V3-A, V4-A, V5-A, V6-A, V7-A, V8-A, V9-A, V10-A, V11-A, V12-A, V13-A, V14-A, V15-A, V16-A, FSC-W, FSC-H, FSC-A, SSC-B-W, SSC-B-H, SSC-B-A, B1-A, B2-A, B3-A, B4-A, B5-A, B6-A, B7-A, B8-A, B9-A, B10-A, B11-A, B12-A, B13-A, B14-A, YG1-A, YG2-A, YG3-A, YG4-A, YG5-A, YG6-A, YG7-A, YG8-A, YG9-A, YG10-A, R1-A, R2-A, R3-A, R4-A, R5-A, R6-A, R7-A, R8-A
MyUnstainedGatingSet <- GatingSet(MyUnstainedCytoSet)
MyUnstainedGatingSet
#> A GatingSet with 18 samples
For this example, we will use the openCyto package to automatically gate each of our .fcs files for the lymphocyte population. To do this, we first read in the example .csv file containing our desired gates (that can be found in Luciernaga’s extdata folder) using the data.table package
FileLocation <- system.file("extdata", package = "Luciernaga")
MyGates <- fread(file.path(path = FileLocation, pattern = 'Gates.csv'))
gt(MyGates)
alias | pop | parent | dims | gating_method | gating_args | collapseDataForGating | groupBy | preprocessing_method | preprocessing_args |
---|---|---|---|---|---|---|---|---|---|
singletsFSC | + | root | FSC-A,FSC-H | singletGate | FALSE | NA | NA | NA | |
singletsSSC | + | singletsFSC | SSC-A,SSC-H | singletGate | FALSE | NA | NA | NA | |
singletsSSCB | + | singletsSSC | SSC-A,SSC-B-A | singletGate | FALSE | NA | NA | NA | |
nonDebris | + | singletsSSCB | FSC-A | gate_mindensity | FALSE | NA | NA | NA | |
lymphocytes | + | nonDebris | FSC-A, SSC-A | flowClust | K=2, target=c(1e5, 5e4) | FALSE | NA | NA | NA |
For your own experiments, individual gates can be added, removed or modified to match the requirements of your own .fcs files, for additional details, please refer to the openCyto packages vignettes. Alternatively, GatingSet objects can be brought directly from several commercial software formats using the CytoML package.
Now that we have the gating information from the .csv file, we can convert them into a GatingTemplate, and append them to the .fcs files contained within the GatingSet
MyGatingTemplate <- gatingTemplate(MyGates)
gt_gating(MyGatingTemplate, MyUnstainedGatingSet)
MyUnstainedGatingSet[[1]]
#> Sample: INF071_Ctrl_Unstained.fcs
#> GatingHierarchy with 6 gates
And finally check the gate placements using:
removestrings <- c("(Cells)", ".fcs", " ")
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
IndividualPlot <- Utility_GatingPlots(x=MyUnstainedGatingSet[[2]],
sample.name = "GUID",
removestrings = removestrings,
gtFile = MyGates,
DesiredGates = NULL,
outpath = StorageLocation,
export = FALSE)
#IndividualPlot
IteratedPlots <- map(.x = MyUnstainedGatingSet[1:2], .f = Utility_GatingPlots,
sample.name = "GUID", removestrings = removestrings,
gtFile = MyGates, DesiredGates = NULL,
outpath = StorageLocation, export = FALSE)
IteratedPlots[[1]]
#> $`1`
#>
#> $`2`
AveragedSignature and QC_ViewSignature
Before moving on to the more-automated functions, let’s demonstrate the underlying building blocks that we use to evaluate fluorescent signatures within .fcs files. Within each .fcs file, the information related to individual cells measurements is stored within exprs. Let’s demonstrate an example of how this is retrieved.
PopulationInterest <- gs_pop_get_data(MyUnstainedGatingSet[1], subset="lymphocytes")
TheDataValues <- exprs(PopulationInterest[[1]])
TheDataValues <- data.frame(TheDataValues, check.names=FALSE)
head(TheDataValues, 5)
#> Time UV1-A UV2-A UV3-A UV4-A UV5-A UV6-A UV7-A
#> 1 100991 287.0875 865.6507 330.3738 894.5082 1029.7963 1117.410 2144.380
#> 2 518430 797.0026 21.7175 310.1438 774.5413 620.4363 1101.345 2400.379
#> 3 363287 366.2225 816.4144 561.6057 1122.9882 514.0800 1031.061 1086.768
#> 4 128030 866.3944 1672.2476 1169.3982 1018.8632 1989.2339 2996.271 5853.759
#> 5 201752 377.4532 640.0713 -152.3200 759.2944 688.4894 1166.572 2252.224
#> UV8-A UV9-A UV10-A UV11-A UV12-A UV13-A UV14-A UV15-A
#> 1 1890.613 1469.427 712.1407 395.3031 512.89001 79.20938 658.73938 470.57065
#> 2 2252.744 1558.603 365.1069 478.6031 -36.74125 360.12378 974.90753 682.24188
#> 3 1645.993 1502.375 692.5800 460.8275 827.19879 376.56064 26.10563 561.23376
#> 4 4622.258 4908.527 2213.4001 2142.3721 558.18439 690.42316 779.30127 832.62817
#> 5 1932.486 2221.953 731.1807 556.4738 207.06001 95.79501 341.67877 70.13563
#> UV16-A SSC-W SSC-H SSC-A V1-A V2-A V3-A V4-A
#> 1 340.3400 658951.4 1100375 1208489.5 22.9625 815.7875 1472.144 1973.675
#> 2 -172.0294 663280.7 1027170 1135503.4 345.4000 1093.1250 1360.219 1895.506
#> 3 253.7675 637444.7 867507 921646.2 730.1938 1326.6688 1440.244 1610.675
#> 4 447.5888 834948.1 1027346 1429634.4 926.5438 3792.5938 5305.712 6203.244
#> 5 -226.6206 629706.4 641798 673573.9 270.3250 986.9750 2229.494 2461.250
#> V5-A V6-A V7-A V8-A V9-A V10-A V11-A
#> 1 3333.137 2550.419 3789.913 3033.387 2055.969 2121.969 1218.3876
#> 2 3019.500 2855.188 3932.294 2555.438 1841.056 2300.306 755.0125
#> 3 3323.856 2341.625 3614.394 3397.625 1903.825 2800.738 1258.3313
#> 4 10569.969 12557.257 19422.701 16951.000 11412.913 13348.706 7723.7876
#> 5 4007.644 4480.644 7203.006 5535.200 4504.844 4350.294 2635.3938
#> V12-A V13-A V14-A V15-A V16-A FSC-W FSC-H FSC-A
#> 1 781.6188 915.5438 763.6063 387.2688 428.3125 650326.3 1277333 1384472.1
#> 2 737.2750 746.4188 478.1562 81.4000 478.9813 672487.3 1353167 1516646.0
#> 3 601.3563 514.2500 706.2000 -155.9250 739.6125 681057.6 1142994 1297407.9
#> 4 3604.2876 3363.9375 3308.4563 2351.1125 825.0000 855744.7 488498 696715.9
#> 5 1387.0312 1101.8563 926.2000 860.2688 612.7000 664640.4 322764 357536.7
#> SSC-B-W SSC-B-H SSC-B-A B1-A B2-A B3-A B4-A B5-A
#> 1 665813.2 653211 724860.8 1035.5363 1193.3193 1382.389 868.2256 559.290
#> 2 643891.6 771916 828383.8 665.0581 900.7994 1516.739 871.5087 1002.641
#> 3 636927.8 485970 515879.7 1696.0237 1306.6837 1446.635 727.8881 1036.244
#> 4 814962.2 564298 766469.2 3459.2549 4499.4263 6746.500 6100.3037 5927.199
#> 5 616127.5 434878 446567.2 1359.9219 1967.3644 2749.649 1878.0118 1274.496
#> B6-A B7-A B8-A B9-A B10-A B11-A B12-A
#> 1 429.4456 639.1150 -131.9044 237.9944 29.3550 324.6431 325.6731
#> 2 591.1556 343.9556 271.9200 328.4413 130.2306 557.4875 737.8019
#> 3 818.8500 940.7119 597.6575 435.0462 440.6469 282.4131 538.9475
#> 4 4646.1367 4256.9253 2672.2705 2430.0273 1379.2344 1110.1469 1117.3568
#> 5 1657.9780 1462.0206 1209.3488 1007.1469 758.9169 263.2294 262.6500
#> B13-A B14-A YG1-A YG2-A YG3-A YG4-A YG5-A
#> 1 159.3925 278.3575 481.6706 -197.5106 357.2812 835.41376 406.6069
#> 2 541.0718 201.4937 191.1975 377.8163 569.2219 54.73688 -166.6387
#> 3 26.5225 172.3319 207.7781 378.2325 899.3775 493.46439 761.1825
#> 4 269.8600 1497.0405 689.5875 1649.0438 1963.6594 1809.23059 1411.7119
#> 5 275.0100 663.3200 645.1181 855.1856 361.4438 1050.26819 824.9381
#> YG6-A YG7-A YG8-A YG9-A YG10-A R1-A R2-A
#> 1 544.94061 415.97250 372.19687 -444.4856 -415.00125 -139.5550 414.2863
#> 2 257.10376 46.20375 -64.51875 33.0225 -194.38875 106.4319 -182.4950
#> 3 38.78062 776.16748 -123.69563 183.7050 91.08938 271.9769 312.4450
#> 4 1519.86755 2322.88306 723.02625 1515.4969 675.92065 640.2863 672.3500
#> 5 557.91376 1073.99438 96.77813 367.8956 384.12939 294.2944 594.5919
#> R3-A R4-A R5-A R6-A R7-A R8-A
#> 1 324.66312 418.5944 16.03188 65.11625 248.52937 316.1881
#> 2 188.92188 370.5694 154.66875 -104.24250 -227.27126 313.2925
#> 3 374.59500 -208.1319 258.48749 421.84311 -28.32063 37.0075
#> 4 932.10876 574.1106 1004.28748 792.20062 594.52124 258.0638
#> 5 16.45563 217.3837 -164.62688 813.59998 255.66251 360.3287
TotalLymphocytes <- nrow(TheDataValues)
TotalLymphocytes
#> [1] 10000
AveragedSignature()
is the Luciernaga
function that takes these data outputs and for each detector summarizes
them for a given statistic given in the stats argument. The main ones
used are “median” and “mean”.
TheSummary <- AveragedSignature(x=TheDataValues, stats="median")
gt(TheSummary)
Time | UV1-A | UV2-A | UV3-A | UV4-A | UV5-A | UV6-A | UV7-A | UV8-A | UV9-A | UV10-A | UV11-A | UV12-A | UV13-A | UV14-A | UV15-A | UV16-A | SSC-W | SSC-H | SSC-A | V1-A | V2-A | V3-A | V4-A | V5-A | V6-A | V7-A | V8-A | V9-A | V10-A | V11-A | V12-A | V13-A | V14-A | V15-A | V16-A | FSC-W | FSC-H | FSC-A | SSC-B-W | SSC-B-H | SSC-B-A | B1-A | B2-A | B3-A | B4-A | B5-A | B6-A | B7-A | B8-A | B9-A | B10-A | B11-A | B12-A | B13-A | B14-A | YG1-A | YG2-A | YG3-A | YG4-A | YG5-A | YG6-A | YG7-A | YG8-A | YG9-A | YG10-A | R1-A | R2-A | R3-A | R4-A | R5-A | R6-A | R7-A | R8-A |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
280707 | 395.4891 | 664.1316 | 492.0278 | 605.7844 | 829.021 | 1304.872 | 2519.788 | 1888.047 | 1819.882 | 721.921 | 494.631 | 307.1688 | 248.4125 | 303.1525 | 225.1703 | 201.8166 | 695970.8 | 1169318 | 1359648 | 444.8813 | 1294.803 | 1981.547 | 2249.5 | 3483.838 | 3387.141 | 4656.06 | 3545.472 | 2429.041 | 2698.369 | 1515.284 | 874.6031 | 799.7344 | 715.0344 | 632.7062 | 360.1812 | 680531.5 | 1289974 | 1467994 | 680777 | 790005.5 | 901305.7 | 1338.549 | 1547.865 | 2142.496 | 1523.724 | 1314.183 | 1063.25 | 783.7012 | 595.4687 | 609.9531 | 399.8331 | 311.4462 | 286.5009 | 210.3453 | 250.1612 | 608.8003 | 507.2006 | 529.2966 | 543.4838 | 422.7019 | 421.8 | 515.6297 | 257.7628 | 246.4547 | 166.3266 | 267.6687 | 327.4528 | 333.7384 | 355.95 | 219.8556 | 185.885 | 174.5497 | 110.3516 |
As you can see, there is still some non-signature information present
in the form of the Time, FSC and SSC columns. After we remove these, we
can pass the values corresponding to the signature to
QC_ViewSignature()
UV1-A | UV2-A | UV3-A | UV4-A | UV5-A | UV6-A | UV7-A | UV8-A | UV9-A | UV10-A | UV11-A | UV12-A | UV13-A | UV14-A | UV15-A | UV16-A | V1-A | V2-A | V3-A | V4-A | V5-A | V6-A | V7-A | V8-A | V9-A | V10-A | V11-A | V12-A | V13-A | V14-A | V15-A | V16-A | B1-A | B2-A | B3-A | B4-A | B5-A | B6-A | B7-A | B8-A | B9-A | B10-A | B11-A | B12-A | B13-A | B14-A | YG1-A | YG2-A | YG3-A | YG4-A | YG5-A | YG6-A | YG7-A | YG8-A | YG9-A | YG10-A | R1-A | R2-A | R3-A | R4-A | R5-A | R6-A | R7-A | R8-A |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
395.4891 | 664.1316 | 492.0278 | 605.7844 | 829.021 | 1304.872 | 2519.788 | 1888.047 | 1819.882 | 721.921 | 494.631 | 307.1688 | 248.4125 | 303.1525 | 225.1703 | 201.8166 | 444.8813 | 1294.803 | 1981.547 | 2249.5 | 3483.838 | 3387.141 | 4656.06 | 3545.472 | 2429.041 | 2698.369 | 1515.284 | 874.6031 | 799.7344 | 715.0344 | 632.7062 | 360.1812 | 1338.549 | 1547.865 | 2142.496 | 1523.724 | 1314.183 | 1063.25 | 783.7012 | 595.4687 | 609.9531 | 399.8331 | 311.4462 | 286.5009 | 210.3453 | 250.1612 | 608.8003 | 507.2006 | 529.2966 | 543.4838 | 422.7019 | 421.8 | 515.6297 | 257.7628 | 246.4547 | 166.3266 | 267.6687 | 327.4528 | 333.7384 | 355.95 | 219.8556 | 185.885 | 174.5497 | 110.3516 |
For Raw MFI:
TheData <- TheData %>% mutate(Sample="TestSignature") %>% relocate(Sample, .before=1)
Plot <- Luciernaga::QC_ViewSignature(x="TestSignature", data=TheData, Normalize=FALSE)
Plot
And after-normalizing by the peak detector.
Plot <- Luciernaga::QC_ViewSignature(x="TestSignature", data=TheData, Normalize=TRUE)
#> Normalizing Data for Signature Comparison
Plot
A lot of the more complicated Luciernaga
functions build on
these elements in combination with filtering decisions to characterize
all the signatures present within an .fcs file. However, if you have
questions that are not addressed directly by one of the more complicated
functions, build on the above until you answer your own question. It’s
how we all initially get started.
Luciernaga_QC
Luciernaga_QC()
is the Luciernaga
package
workhorse function. It’s primary purpose is to take individual .fcs
files, identify normalized signatures at the level of individual cells,
group cells with similar signatures together and then export the data as
purified signature .fcs file or as a dataframe object for use in
reports. Given the various types of unmixing controls it can encounter
(beads or cells, single color or unstained), it coordinates various
functions behind the scenes, while retaining similar processing and
arguments to allow for user-directed inputs.
As with other Luciernaga
functions,
Luciernaga_QC()
starts from GatingSet objects. For this
workflow, our overarching goal is to characterize signatures present
within our single color cell reference controls. To do this, we will
need to lay down some groundwork that will vary depending on your
individual panel and experiment context.
Autofluorescent Overlaps
For our SFC panels, a few fluorophores (BUV496, BV510) peak signature
detector overlaps directly with that of the main autofluorescent
detector peaks (UV7-A, V7-A). Luciernaga_QC
works on the
basis of filtering single stained cells from unstained cells by their
peak fluorescence detectors. To handle situations when these are
identical, Luciernaga_QC()
refers to an autofluorescent
overlap list to appropriately handle fluorophores where this is the
case.
Since these will vary by panel and cell type in user-specific ways, to appropriately extract the background autofluorescence from these particular single colors, we need to provide information about where to expect to overlapping autofluorescent with fluorophore signatures in a .csv file.
FileLocation <- system.file("extdata", package = "Luciernaga")
pattern = "AutofluorescentOverlaps.csv"
AFOverlap <- list.files(path=FileLocation, pattern=pattern,
full.names = TRUE)
AFOverlap_CSV <- read.csv(AFOverlap, check.names = FALSE)
AFOverlap_CSV
#> Fluorophore MainDetector
#> 1 Unstained UV7-A,V7-A,V3-A,V5-A
#> 2 BUV496 UV7-A
#> 3 BV480 V5-A
#> 4 BV510 V7-A
#> 5 BV570 V8-A
As you can see from the above example, we provide for Fluorophore “Unstained” the location of the peak detectors that autofluorescence (both the main autofluorescence (UV7-A, V7-A) and rarer variants (V5-A, V7-A, V8-A)) can be found on. If the rarer variants are unknown, list the main Autofluorescent detector in your system and add the exceptions to the .csv file as you encounter them in actual practice.
The second component are individual fluorophores that overlap with the mentioned Unstained detectors. You can add/remove to this list, the crucial part is to retain the Unstained row.
Now that an initial overlap .csv has been provided, the initial steps in our workflow focus on refining what autofluorescence backgrounds we are working with. This is important not only for isolating signatures of the single-color controls, but also for handling overlap exceptions (mentioned above) as well as determining any autofluorescence(s) that might be present within individual samples that will need to be treated as individual fluorophores.
To do this without running all Luciernaga_QC()
extraction protocols, we first set the arguments SignatureReturnNow to
TRUE. We can additionally set Verbose = TRUE to get processing
information that can be useful in troubleshooting.
FileLocation <- system.file("extdata", package = "Luciernaga")
pattern = "AutofluorescentOverlaps.csv"
AFOverlap <- list.files(path=FileLocation, pattern=pattern,
full.names = TRUE)
removestrings <- ".fcs"
UnstainedSignature <- Luciernaga_QC(x=MyUnstainedGatingSet[2],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = TRUE,
ratiopopcutoff = 0.001,
Verbose = TRUE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data.frame",
SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.09 of all events were negative and will be rounded to 0
#> Fluors Counts
#> 1 V7 3920
#> 2 V8 22
#> 3 V5 17
#> 4 V6 11
#> [1] "V7" "V8" "V5" "V6"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V7 3920
#> 2 V8 22
#> 3 V5 17
#> 4 V6 11
From the Verbose = TRUE readouts, we can see that the main peak detector of autofluorescence in unstained lymphocytes cells in our sample is “V7” detector, with a few cells having their respective peaks for V8, V5, V6.
For the samples provided in this R package, we down-sampled unstained files to 10,000 event, so depending on total number of cells would inform whether these minor autofluorescent peaks are negligible or important enough to add to the above AutofluorescentOverlap.csv.
What we see is also influence by the ratiopopcutoff argument (set to 0.001 above). It is used to determine minimal number of cells a peak detector would need to have in order to be returned as a significant autofluorescence detector. We can be see this in action by making the argument more stringent with this argument (increase it to 0.1 ~ 10% total unstained cells share that peak detector):
UnstainedSignature <- Luciernaga_QC(x=MyUnstainedGatingSet[2],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = TRUE,
ratiopopcutoff = 0.1,
Verbose = TRUE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data.frame",
SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.09 of all events were negative and will be rounded to 0
#> Fluors Counts
#> 1 V7 3920
#> [1] "V7"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V7 3920
With SignatureReturnNow = TRUE, for the main peak detector present within the sample, we get an averaged signature that is returned. The average is determined by the stats argument, which is typically either “median” or “mean”.
UnstainedSignature
#> UV1-A UV2-A UV3-A UV4-A UV5-A UV6-A UV7-A UV8-A
#> 1 401.216 682.8741 544.5366 668.5197 922.0641 1473.294 2798.136 2067.067
#> UV9-A UV10-A UV11-A UV12-A UV13-A UV14-A UV15-A UV16-A
#> 1 1938.659 729.0238 473.1738 282.4763 231.1575 289.2444 215.3528 190.4744
#> V1-A V2-A V3-A V4-A V5-A V6-A V7-A V8-A
#> 1 465.5063 1445.744 2224.922 2513.191 3835.941 3698.578 5102.797 3820.334
#> V9-A V10-A V11-A V12-A V13-A V14-A V15-A V16-A
#> 1 2592.081 2877.359 1541.306 875.6344 799.4938 704.0344 614.1437 344.5406
#> B1-A B2-A B3-A B4-A B5-A B6-A B7-A B8-A
#> 1 1403.117 1631.713 2303.724 1569.205 1344.987 1071.586 778.7122 590.5762
#> B9-A B10-A B11-A B12-A B13-A B14-A YG1-A YG2-A
#> 1 621.5084 400.5734 307.1331 287.9816 206.3541 265.9009 592.3237 513.6872
#> YG3-A YG4-A YG5-A YG6-A YG7-A YG8-A YG9-A YG10-A
#> 1 507.6169 493.4644 389.5753 391.2056 497.0025 242.535 231.8166 174.7209
#> R1-A R2-A R3-A R4-A R5-A R6-A R7-A R8-A
#> 1 199.2684 268.94 274.3781 305.5944 170.1709 156.3638 149.5838 105.09
This returned signature is what we provide as an external autofluorescence argument for either the bead or single-color controls.
In cases where we want to return the averaged signature for a specific autofluorescene or one of the minor autofluorescent peak detectors (like V8, V5 or V6) we can add the additional argument desiredAF and set it to the corresponding detector (“V8-A”):
V7 <- Luciernaga_QC(x=MyUnstainedGatingSet[2], desiredAF = "V7-A",
subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells",
Unstained = TRUE, ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> Normalizing Data for Signature Comparison
V8 <- Luciernaga_QC(x=MyUnstainedGatingSet[2], desiredAF = "V8-A",
subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells",
Unstained = TRUE, ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> Normalizing Data for Signature Comparison
V5 <- Luciernaga_QC(x=MyUnstainedGatingSet[2], desiredAF = "V5-A",
subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells",
Unstained = TRUE, ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> Normalizing Data for Signature Comparison
Comparison <- rbind(V7, V8, V5)
gt(Comparison)
UV1-A | UV2-A | UV3-A | UV4-A | UV5-A | UV6-A | UV7-A | UV8-A | UV9-A | UV10-A | UV11-A | UV12-A | UV13-A | UV14-A | UV15-A | UV16-A | V1-A | V2-A | V3-A | V4-A | V5-A | V6-A | V7-A | V8-A | V9-A | V10-A | V11-A | V12-A | V13-A | V14-A | V15-A | V16-A | B1-A | B2-A | B3-A | B4-A | B5-A | B6-A | B7-A | B8-A | B9-A | B10-A | B11-A | B12-A | B13-A | B14-A | YG1-A | YG2-A | YG3-A | YG4-A | YG5-A | YG6-A | YG7-A | YG8-A | YG9-A | YG10-A | R1-A | R2-A | R3-A | R4-A | R5-A | R6-A | R7-A | R8-A |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
401.2160 | 682.8741 | 544.5366 | 668.5197 | 922.0641 | 1473.2944 | 2798.136 | 2067.067 | 1938.659 | 729.0238 | 473.1738 | 282.4763 | 231.1575 | 289.2444 | 215.35282 | 190.4744 | 465.5063 | 1445.744 | 2224.922 | 2513.191 | 3835.941 | 3698.578 | 5102.797 | 3820.334 | 2592.081 | 2877.359 | 1541.306 | 875.6344 | 799.4938 | 704.0344 | 614.1437 | 344.5406 | 1403.1174 | 1631.713 | 2303.724 | 1569.205 | 1344.9868 | 1071.5862 | 778.7122 | 590.5762 | 621.5084 | 400.5734 | 307.1331 | 287.9816 | 206.3541 | 265.9009 | 592.3237 | 513.6872 | 507.6169 | 493.4644 | 389.5753 | 391.2056 | 497.0025 | 242.5350 | 231.8166 | 174.7209 | 199.26844 | 268.9400 | 274.3781 | 305.5944 | 170.17094 | 156.363754 | 149.5838 | 105.09000 |
242.0535 | 543.6069 | 377.8994 | 408.4675 | 579.3069 | 966.2057 | 1982.800 | 1380.735 | 1152.738 | 456.2163 | 304.2310 | 233.3144 | 172.4013 | 239.4503 | 87.42782 | 113.0128 | 273.7969 | 1019.356 | 1537.594 | 1757.628 | 2781.488 | 2676.644 | 3281.334 | 3409.106 | 1912.144 | 2068.241 | 1127.053 | 658.4188 | 661.9250 | 497.4750 | 479.5656 | 314.3250 | 941.9994 | 1233.715 | 1644.942 | 1132.871 | 897.0978 | 873.5044 | 555.0091 | 400.4447 | 405.3372 | 254.0881 | 255.5044 | 142.1722 | 180.6684 | 448.0178 | 355.9285 | 359.6747 | 486.3534 | 284.9231 | 245.9344 | 436.8544 | 299.3185 | 187.1391 | 252.7678 | 134.4141 | 71.79031 | 295.7422 | 228.1541 | 261.2772 | 81.18344 | 57.947811 | 153.5034 | 84.64406 |
382.5107 | 521.2944 | 473.1738 | 551.7138 | 672.7219 | 1426.7357 | 2137.612 | 1689.354 | 1508.325 | 662.2350 | 248.5613 | 279.7244 | 101.4475 | 363.5450 | 215.46439 | 290.7319 | 272.6625 | 1080.613 | 1693.244 | 2038.438 | 3486.106 | 2627.694 | 3357.062 | 2769.456 | 2090.963 | 1995.125 | 1288.925 | 737.1375 | 337.9750 | 461.6562 | 496.3063 | 286.6875 | 1002.3831 | 1372.539 | 1683.728 | 1122.958 | 884.8987 | 787.3062 | 610.4037 | 353.4831 | 298.8931 | 402.0862 | 171.2375 | 339.5137 | 40.1700 | 169.3706 | 429.4312 | 482.5725 | 416.6663 | 201.0488 | 350.3438 | 276.4594 | 354.7144 | 85.4700 | 287.4206 | 223.8038 | 159.47125 | 304.8175 | 151.4200 | 202.4819 | 50.85000 | 1.906875 | 0.0000 | 196.19624 |
These averaged signature readouts can also be combined with the functions covered in the QC vignette to visualize the normalized signature:
Fluorophore <- data.frame(Sample=c("V7", "V8", "V5"))
Data <- cbind(Fluorophore, Comparison)
SignatureOnly <- QC_WhatsThis(x="V7", data=Data, NumberHits = 0, returnPlots = TRUE)
#> Normalizing Data for Signature Comparison
SignatureOnly[[2]]
We can also compare it to reference fluorophore spectras to see what single colors it might overlap with and significantly impact:
Comparison <- QC_WhatsThis(x="V7", data=Data, NumberHits = 5, returnPlots = TRUE)
#> Normalizing Data for Signature Comparison
Comparison[[1]]
#> Fluorophore ID_V7
#> 1 BV510 0.89
#> 2 LIVE DEAD Aqua 0.86
#> 3 OC515 0.84
#> 4 VioGreen 0.84
#> 5 Zombie Aqua 0.84
plotly::ggplotly(Comparison[[2]])
We will typically repeat the above workflow on various unstained specimens within our GatingSet, identify if any additional rare AF may be present, and then update our AFOverlap.csv to account for these Unstained peak detectors, and add any fluorophores that share their peak detector. Once this is done, we are ready to proceed.
The SignatureReturnNow = TRUE argument will also work for single-color unmixing controls for both bead and cell reference controls, set Verbose = TRUE to see what peak detectors are returned, set desiredAF to the detector of interest to further explore different signatures. In the case below, we use the BV786 single-color bead unstained control.
pData(MyBeadsGatingSet)
#> name
#> CCR4_BUV615(Beads).fcs CCR4_BUV615(Beads).fcs
#> CCR6_BV786(Beads).fcs CCR6_BV786(Beads).fcs
#> CCR7_BV650(Beads).fcs CCR7_BV650(Beads).fcs
#> CD107a_APC-R700(Beads).fcs CD107a_APC-R700(Beads).fcs
#> CD127_BV421(Beads).fcs CD127_BV421(Beads).fcs
#> CD16_APC(Beads).fcs CD16_APC(Beads).fcs
#> CD161_BV480(Beads).fcs CD161_BV480(Beads).fcs
#> CD25_PE-Cy5(Beads).fcs CD25_PE-Cy5(Beads).fcs
#> CD26_PerCP-Cy5.5(Beads).fcs CD26_PerCP-Cy5.5(Beads).fcs
#> CD27_APC-Fire750(Beads).fcs CD27_APC-Fire750(Beads).fcs
#> CD3_AlexaFluor488(Beads).fcs CD3_AlexaFluor488(Beads).fcs
#> CD3_AlexaFluor647(Beads).fcs CD3_AlexaFluor647(Beads).fcs
#> CD3_SparkBlue550(Beads).fcs CD3_SparkBlue550(Beads).fcs
#> CD38_APC-Fire810(Beads).fcs CD38_APC-Fire810(Beads).fcs
#> CD4_BUV805(Beads).fcs CD4_BUV805(Beads).fcs
#> CD45RA_BV510(Beads).fcs CD45RA_BV510(Beads).fcs
#> CD56_BV605(Beads).fcs CD56_BV605(Beads).fcs
#> CD62L_BUV395(Beads).fcs CD62L_BUV395(Beads).fcs
#> CD69_BUV563(Beads).fcs CD69_BUV563(Beads).fcs
#> CD7_BV711(Beads).fcs CD7_BV711(Beads).fcs
#> CD8_BUV496(Beads).fcs CD8_BUV496(Beads).fcs
#> CXCR3_BUV737(Beads).fcs CXCR3_BUV737(Beads).fcs
#> Dump_CD14_PacificBlue(Beads).fcs Dump_CD14_PacificBlue(Beads).fcs
#> Dump_CD19_PacificBlue(Beads).fcs Dump_CD19_PacificBlue(Beads).fcs
#> IFNg_BV750(Beads).fcs IFNg_BV750(Beads).fcs
#> NKG2D_PE(Beads).fcs NKG2D_PE(Beads).fcs
#> PD1_PE-Vio770(Beads).fcs PD1_PE-Vio770(Beads).fcs
#> TNFa_PE-Dazzle594(Beads).fcs TNFa_PE-Dazzle594(Beads).fcs
#> Va7.2_AlexaFluor647(Beads).fcs Va7.2_AlexaFluor647(Beads).fcs
#> VD2_BUV661(Beads).fcs VD2_BUV661(Beads).fcs
BV786 <- Luciernaga_QC(x=MyBeadsGatingSet[2], desiredAF = NULL,
subsets="singlets", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "beads",
Unstained = FALSE, ratiopopcutoff = 0.001, Verbose = TRUE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.19 of all events were negative and will be rounded to 0
#> Returning Peak Bead Detector Medians
#> TheDetector TheMedian
#> 1 V15 394556.2500
#> 2 YG1 768.0050
#> 3 V7 713.0750
#> 4 UV9 764.5007
#> 5 V5 765.1188
#> 6 V3 735.7625
#> 7 V4 719.6750
#> Fluors Counts
#> 1 V15 1146
#> [1] "V15"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V15 1146
For the staining beads we use, the only bright signal is the fluorophore stained beads as seen above. Beyond that, the next occupied peak detector is dictated often by noise and the normalize signature reflects this uncertainty, as we see when we set the desiredAF to retrieve the “YG1-A” instead.
pData(MyBeadsGatingSet)
#> name
#> CCR4_BUV615(Beads).fcs CCR4_BUV615(Beads).fcs
#> CCR6_BV786(Beads).fcs CCR6_BV786(Beads).fcs
#> CCR7_BV650(Beads).fcs CCR7_BV650(Beads).fcs
#> CD107a_APC-R700(Beads).fcs CD107a_APC-R700(Beads).fcs
#> CD127_BV421(Beads).fcs CD127_BV421(Beads).fcs
#> CD16_APC(Beads).fcs CD16_APC(Beads).fcs
#> CD161_BV480(Beads).fcs CD161_BV480(Beads).fcs
#> CD25_PE-Cy5(Beads).fcs CD25_PE-Cy5(Beads).fcs
#> CD26_PerCP-Cy5.5(Beads).fcs CD26_PerCP-Cy5.5(Beads).fcs
#> CD27_APC-Fire750(Beads).fcs CD27_APC-Fire750(Beads).fcs
#> CD3_AlexaFluor488(Beads).fcs CD3_AlexaFluor488(Beads).fcs
#> CD3_AlexaFluor647(Beads).fcs CD3_AlexaFluor647(Beads).fcs
#> CD3_SparkBlue550(Beads).fcs CD3_SparkBlue550(Beads).fcs
#> CD38_APC-Fire810(Beads).fcs CD38_APC-Fire810(Beads).fcs
#> CD4_BUV805(Beads).fcs CD4_BUV805(Beads).fcs
#> CD45RA_BV510(Beads).fcs CD45RA_BV510(Beads).fcs
#> CD56_BV605(Beads).fcs CD56_BV605(Beads).fcs
#> CD62L_BUV395(Beads).fcs CD62L_BUV395(Beads).fcs
#> CD69_BUV563(Beads).fcs CD69_BUV563(Beads).fcs
#> CD7_BV711(Beads).fcs CD7_BV711(Beads).fcs
#> CD8_BUV496(Beads).fcs CD8_BUV496(Beads).fcs
#> CXCR3_BUV737(Beads).fcs CXCR3_BUV737(Beads).fcs
#> Dump_CD14_PacificBlue(Beads).fcs Dump_CD14_PacificBlue(Beads).fcs
#> Dump_CD19_PacificBlue(Beads).fcs Dump_CD19_PacificBlue(Beads).fcs
#> IFNg_BV750(Beads).fcs IFNg_BV750(Beads).fcs
#> NKG2D_PE(Beads).fcs NKG2D_PE(Beads).fcs
#> PD1_PE-Vio770(Beads).fcs PD1_PE-Vio770(Beads).fcs
#> TNFa_PE-Dazzle594(Beads).fcs TNFa_PE-Dazzle594(Beads).fcs
#> Va7.2_AlexaFluor647(Beads).fcs Va7.2_AlexaFluor647(Beads).fcs
#> VD2_BUV661(Beads).fcs VD2_BUV661(Beads).fcs
NotBV786 <- Luciernaga_QC(x=MyBeadsGatingSet[2], desiredAF = "YG1-A",
subsets="singlets", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "beads",
Unstained = FALSE, ratiopopcutoff = 0.001, Verbose = TRUE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.19 of all events were negative and will be rounded to 0
#> Returning Peak Bead Detector Medians
#> TheDetector TheMedian
#> 1 V15 394556.2500
#> 2 YG1 768.0050
#> 3 V7 713.0750
#> 4 UV9 764.5007
#> 5 V5 765.1188
#> 6 V3 735.7625
#> 7 V4 719.6750
#> Fluors Counts
#> 1 V15 1146
#> [1] "V15"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V15 1146
Unstained Signatures
Having established autofluorescence background for the cells we are
working with (and updated the AFOverlaps csv), we can now switch
SignatureReturnNow = FALSE, and allow Luciernaga_QC()
to
continue down the processing pipeline. Let’s continue with Unstained
cell gating set, as their signatures do not require subtraction of
background like single color controls, and are easier to demonstrate
Luciernaga_QC()
building blocks.
From the above example, we used Luciernaga_QC()
to
retrieve for individual detectors that passed the ratiopopcutoff limit
the averaged signature of cells that shared their respective peak
detectors. We can quickly visualize both the raw fluorescence and the
normalized signatures using the QC_ViewSignature()
function:
SignatureOnly <- QC_ViewSignature(x=c("V7", "V8", "V5"), data=Data, Normalize = FALSE)
plotly::ggplotly(SignatureOnly)
SignatureOnly <- QC_ViewSignature(x=c("V7", "V8", "V5"), data=Data, Normalize = TRUE)
#> Normalizing Data for Signature Comparison
plotly::ggplotly(SignatureOnly)
While this approach is good to quickly characterize average signature
of cells at the population level for each detector, it can miss
variation present in cells, especially for non-peak detector regions.
For example, if we passed the same original unstained file to
Luciernaga_LinearSlices()
, we could see that when we cut
the population for the “V7” detector in 10% increments on the basis of
MFI, that we end up some variation in the normalized signatures for the
secondary peaks.
RawSlices <- Luciernaga_LinearSlices(x=MyUnstainedGatingSet[2], subset="lymphocytes",
sample.name="GUID", removestrings=removestrings,
stats="median", returntype="raw",
probsratio=0.1, output="plot", desiredAF="V7-A")
plotly::ggplotly(RawSlices)
NormalizedSlices <- Luciernaga_LinearSlices(x=MyUnstainedGatingSet[2], subset="lymphocytes",
sample.name="GUID", removestrings=removestrings,
stats="median", returntype="normalized",
probsratio=0.1, output="plot", desiredAF="V7-A")
plotly::ggplotly(NormalizedSlices)
This is more particularly pronounced in context of the single-color unmixing controls, for example, here is what CD16 APC looks like when filtered solely on the basis of peak-detector:
#pData(MyGatingSet[6])
APC_Example <- subset(MyGatingSet, str_detect(name, "CD16_"))
RawSlices <- Luciernaga_LinearSlices(x=APC_Example[1], subset="lymphocytes",
sample.name="GUID", removestrings=removestrings,
stats="median", returntype="raw",
probsratio=0.1, output="plot", desiredAF="R1-A")
plotly::ggplotly(RawSlices)
#pData(MyGatingSet[6])
NormalizedSlices <- Luciernaga_LinearSlices(x=APC_Example[1], subset="lymphocytes",
sample.name="GUID", removestrings=removestrings,
stats="median", returntype="normalized",
probsratio=0.1, output="plot", desiredAF="R1-A")
plotly::ggplotly(NormalizedSlices)
For the dimmest cells in terms of brightness (raw MFI), the resulting
normalized signatures retain significant portion of autofluorescence
signature that contaminates the final normalized signature. We can
compare how different these are by setting the returntype to “data” and
passing the rows of comparison interest to
QC_WhatsThis()
NormalizedSliceData <- Luciernaga_LinearSlices(x=APC_Example[1], subset="lymphocytes",
sample.name="GUID", removestrings=removestrings,
stats="median", returntype="normalized",
probsratio=0.1, output="data", desiredAF="R1-A")
APC_90to100 <- NormalizedSliceData %>% rename(Sample = Percentiles) %>% filter(Sample %in% "90")
APC_10to20 <- NormalizedSliceData %>% rename(Sample = Percentiles) %>% filter(Sample %in% "10")
MatchingSignature <- QC_WhatsThis(x="90", data=APC_90to100, NumberHits = 10, returnPlots = TRUE)
MatchingSignature[1]
#> [[1]]
#> Fluorophore ID_90
#> 1 APC 1.00
#> 2 cFluor R659 1.00
#> 3 CF633 0.96
#> 4 CellTrace Far Red 0.94
#> 5 CF640R 0.94
#> 6 Live-or-Dye 640-662 0.94
#> 7 ViaKrome 638 0.94
#> 8 Vio Bright R667 0.94
#> 9 Vio R667 0.94
#> 10 NovaFluor Red 660 0.93
plotly::ggplotly(MatchingSignature[[2]])
ContaminationSignature <- QC_WhatsThis(x="10", data=APC_10to20, NumberHits = 10, returnPlots = TRUE)
ContaminationSignature[1]
#> [[1]]
#> Fluorophore ID_10
#> 1 APC 0.74
#> 2 cFluor R659 0.73
#> 3 StarBright R670 0.72
#> 4 CF633 0.70
#> 5 CellTrace Far Red 0.69
#> 6 CF640R 0.67
#> 7 Live-or-Dye 640-662 0.67
#> 8 NovaFluor Red 660 0.67
#> 9 ViaKrome 638 0.67
#> 10 Vio Bright R667 0.67
plotly::ggplotly(ContaminationSignature[[2]])
In this case, the cosine value of the signature from our APC
single-color went from being a near match to the reference signature, to
0.76. It’s because of these reasons, that in Luciernaga_QC
after the initial peak detectors are determined, we filter cells that
share this peak detector, and then determine if any additional signature
peaks are present. This occurs using a rolling local maxima approach to
identify these additional peaks. If the fluorophore doesn’t have any
additional peaks, Luciernaga_QC()
will enumerate the first
peak and move on. If they are present, individual cells are evaluated by
the height of the retrieved detectors corresponding to these additional
peaks vs the main peak. Ie, is the additional peak 0.5 (50%) the height,
or 0.1 (10%) the height of the peak detector.
Once this process has been repeated for each individual cell, the enumerations are appended to create a subcluster designation. For autofluorescence cells in our system, this would often resemble a name similar to V7_10-UV8_05-B3_04. With the primary peak coming first, with it’s relative height equaling 10 (since R doesn’t like periods to allow for a 1.0) [V7_10]. Then the second highest peak and it’s relative height compared to the peak detector [-UV7_05-], and finally the final peak in the fluorescent signature and it’s height relative to the peak detector [-B3_04]. Once these subcluster names are formed, they are used to group cells with similar autofluorescence together to be exported/reported as purified signatures.
Let’s see what this looks like in the context of our original unstained sample.
FileLocation <- system.file("extdata", package = "Luciernaga")
pattern = "AutofluorescentOverlaps.csv"
AFOverlap <- list.files(path=FileLocation, pattern=pattern,
full.names = TRUE)
removestrings <- c(".fcs")
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
Unstained_Signatures <- Luciernaga_QC(x=MyUnstainedGatingSet[2],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = TRUE,
ratiopopcutoff = 0.001,
Verbose = FALSE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data",
SignatureReturnNow = FALSE,
outpath = NULL,
Increments=0.1,
experiment = "UnstainedSignature",
condition = "Test")
gt(head(Unstained_Signatures, 5))
Sample | Experiment | Condition | Cluster | Count | UV1 | UV2 | UV3 | UV4 | UV5 | UV6 | UV7 | UV8 | UV9 | UV10 | UV11 | UV12 | UV13 | UV14 | UV15 | UV16 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 | V13 | V14 | V15 | V16 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | B13 | B14 | YG1 | YG2 | YG3 | YG4 | YG5 | YG6 | YG7 | YG8 | YG9 | YG10 | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_04-B3_05 | 746 | 0.1 | 0.2 | 0.1 | 0.2 | 0.2 | 0.3 | 0.6 | 0.4 | 0.4 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.3 | 0.5 | 0.5 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.1 | 0.3 | 0.4 | 0.5 | 0.4 | 0.3 | 0.3 | 0.2 | 0.2 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.2 | 0.1 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_05-B3_05 | 733 | 0.1 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.5 | 0.4 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.3 | 0.5 | 0.5 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.1 | 0.3 | 0.4 | 0.5 | 0.4 | 0.3 | 0.3 | 0.2 | 0.2 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_04-B3_04 | 373 | 0.1 | 0.2 | 0.1 | 0.2 | 0.2 | 0.3 | 0.6 | 0.4 | 0.4 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.3 | 0.5 | 0.5 | 0.8 | 0.7 | 1 | 0.8 | 0.5 | 0.6 | 0.3 | 0.2 | 0.2 | 0.2 | 0.2 | 0.1 | 0.3 | 0.3 | 0.4 | 0.3 | 0.3 | 0.2 | 0.2 | 0.2 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_05-B3_04 | 335 | 0.1 | 0.2 | 0.2 | 0.2 | 0.3 | 0.4 | 0.6 | 0.5 | 0.4 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.3 | 0.5 | 0.5 | 0.8 | 0.8 | 1 | 0.8 | 0.5 | 0.6 | 0.3 | 0.2 | 0.2 | 0.2 | 0.2 | 0.1 | 0.3 | 0.3 | 0.4 | 0.3 | 0.3 | 0.2 | 0.2 | 0.2 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_05-B3_06 | 318 | 0.1 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.5 | 0.4 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.3 | 0.5 | 0.5 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.1 | 0.3 | 0.4 | 0.6 | 0.4 | 0.3 | 0.3 | 0.2 | 0.2 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.2 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 | 0.1 |
As you can see, we have a Cluster column following the peak naming format mentioned above, as well as the count of the number of cells that shared this signature. We also get back within the data.frame the normalized signature values for respective detectors that can be used to plot the signatures.
TheData <- Unstained_Signatures %>% select(-any_of(c("Sample", "Experiment", "Condition", "Count"))) %>% rename(Sample=Cluster)
TheFluors <- TheData %>% pull(Sample)
TheFluors <- TheFluors[1:10]
Luciernaga_Subpeaks <- QC_ViewSignature(x=TheFluors, data=TheData, Normalize = TRUE)
plotly::ggplotly(Luciernaga_Subpeaks)
As can be seen, this approach improves on the ability to capture the variation present within the UV8 detector.
Two important arguments to mention are Increments and SecondaryPeaks.
Increments are by default set to 0.1, so 10% increments. So if a
secondary peak is 0.18 of the primary peak value, it would get rounded
to the nearest 0.1 increment interval (0.2). In
Luciernaga
’s current implementation, 0.1 is the lowest
value verified to work, but it can be increased more broadly to 0.2.
Unstained_Signatures <- Luciernaga_QC(x=MyUnstainedGatingSet[2],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = TRUE,
ratiopopcutoff = 0.001,
Verbose = FALSE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data",
SignatureReturnNow = FALSE,
outpath = NULL,
Increments=0.2,
experiment = "UnstainedSignature",
condition = "Test")
gt(head(Unstained_Signatures, 5))
Sample | Experiment | Condition | Cluster | Count | UV1 | UV2 | UV3 | UV4 | UV5 | UV6 | UV7 | UV8 | UV9 | UV10 | UV11 | UV12 | UV13 | UV14 | UV15 | UV16 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 | V13 | V14 | V15 | V16 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | B13 | B14 | YG1 | YG2 | YG3 | YG4 | YG5 | YG6 | YG7 | YG8 | YG9 | YG10 | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_04-B3_06 | 1410 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_06-B3_06 | 1307 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_04-B3_04 | 549 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_06-B3_04 | 433 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_06-B3_08 | 93 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.6 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 1.0 | 0.6 | 0.8 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.8 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
TheData <- Unstained_Signatures %>% select(-any_of(c("Sample", "Experiment", "Condition", "Count"))) %>% rename(Sample=Cluster)
TheFluors <- TheData %>% pull(Sample)
TheFluors <- TheFluors[1:10]
Luciernaga_Subpeaks <- QC_ViewSignature(x=TheFluors, data=TheData, Normalize = TRUE)
plotly::ggplotly(Luciernaga_Subpeaks)
Similarly, SecondaryPeaks default is set to 2, so allowing for a fluorescent with 3 separate peaks to be characterized. This works for most single-color unmixing controls, but can also be currently increased up to 8 peaks (which is used to characterize the QC bead and raw full-stained signatures, as described in vignettes elsewhere). Let’s decrease it to 1 for this example:
Unstained_Signatures <- Luciernaga_QC(x=MyUnstainedGatingSet[2],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = TRUE,
ratiopopcutoff = 0.001,
Verbose = FALSE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data",
SignatureReturnNow = FALSE,
outpath = NULL,
Increments=0.2,
SecondaryPeaks=1,
experiment = "UnstainedSignature",
condition = "Test")
gt(head(Unstained_Signatures, 5))
Sample | Experiment | Condition | Cluster | Count | UV1 | UV2 | UV3 | UV4 | UV5 | UV6 | UV7 | UV8 | UV9 | UV10 | UV11 | UV12 | UV13 | UV14 | UV15 | UV16 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 | V13 | V14 | V15 | V16 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | B13 | B14 | YG1 | YG2 | YG3 | YG4 | YG5 | YG6 | YG7 | YG8 | YG9 | YG10 | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_04 | 2013 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_06 | 1836 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 0.8 | 0.6 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_08 | 41 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.8 | 0.8 | 0.6 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 1.0 | 0.8 | 1 | 0.8 | 0.6 | 0.8 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V7_10-UV8_02 | 30 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.2 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 0.8 | 0.8 | 1 | 1.0 | 0.6 | 0.8 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.4 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
INF071_PMA_Unstained | UnstainedSignature | Test | V8_10-V7_10 | 22 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.7 | 0.6 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.6 | 0.6 | 1.0 | 0.8 | 1 | 1.0 | 0.6 | 0.8 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.4 | 0.4 | 0.6 | 0.4 | 0.4 | 0.4 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 | 0.2 |
One challenge is that for every additional peak, at 0.1 increments, the number of potential combinations a subcluster name can take increases, the end number of subclusters increase, and the fewer cells per subcluster result. At the same time, we get into the “Poisson” noise discussion territory where only people named David typically thread. At a certain point of increasing points we are evaluating at, every single autofluorescent cell eventually clusters only with itself at the typical number of cells collected for our experiments. So my advice for now is to set the Increments and SecondaryPeak arguments for what you need to achieve stable resolution/characterization/unmixing in your particular context.
Data Export Arguments
With all the above, we have covered all the important arguments
related to Unstained cell unmixing controls. For the examples above, we
specified returntype == “data”, which returned the summaries that could
be used for the reporting functions in Luciernaga
.
Alternatively, we could export the purified signatures as .fcs files, by
setting returntype = “fcs”, and providing a file path to our desired
storage location via the outpath argument.
These files are compatible with other flow cytometry software, and can be used to assist in unmixing. This can be useful when a single-color is experiencing quality issues, or you want to create a tag specific to a certain subtype of autofluorescence. In the context of unstained unmixing controls, this would resemble the following code (sending to a temporary folder for this example):
#StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
TemporaryStorage <- tempdir()
Unstained_Signatures <- Luciernaga_QC(x=MyUnstainedGatingSet[2],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = TRUE,
ratiopopcutoff = 0.001,
Verbose = FALSE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "fcs",
SignatureReturnNow = FALSE,
outpath = TemporaryStorage,
Increments=0.1,
SecondaryPeaks=2,
experiment = "UnstainedSignature",
condition = "Test")
TheFCSFileOutputs <- list.files(TemporaryStorage, pattern="fcs")
TheFCSFileOutputs
#> [1] "INF071_PMA_Unstained_V710UV803B305.fcs"
#> [2] "INF071_PMA_Unstained_V710UV804B304.fcs"
#> [3] "INF071_PMA_Unstained_V710UV804B305.fcs"
#> [4] "INF071_PMA_Unstained_V710UV804B306.fcs"
#> [5] "INF071_PMA_Unstained_V710UV805B304.fcs"
#> [6] "INF071_PMA_Unstained_V710UV805B305.fcs"
#> [7] "INF071_PMA_Unstained_V710UV805B306.fcs"
To visualize what these may look like on their return:
TheFCSFileOutputs <- list.files(TemporaryStorage, pattern="fcs", full.names=TRUE)
TheIsolatedFCS <- load_cytoset_from_fcs(TheFCSFileOutputs[1], truncate_max_range = FALSE, transformation = FALSE)
TheIsolatedGS <- GatingSet(TheIsolatedFCS)
FCSView <- Utility_IterativeGating(x=TheIsolatedGS[1], subset="root", gate = NULL, xValue="UV8-A", yValue="B3-A",
sample.name="GUID", removestrings=".fcs", bins=100)
FCSView
#> [[1]]
Additionally, providing argument Brightness = TRUE will return a .csv
file with the report data. This output is used by
Luciernaga_Tree()
and a couple other of the report
functions we will cover later.
#StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
TemporaryStorage <- tempdir()
TheUnstaineds <- map(.x=MyUnstainedGatingSet[2], .f=Luciernaga_QC,
subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells",
Unstained = TRUE, ratiopopcutoff = 0.001,
Verbose = FALSE, AFOverlap = AFOverlap,
stats = "median", ExportType = "fcs",
SignatureReturnNow = FALSE, outpath = TemporaryStorage,
Increments=0.1, SecondaryPeaks=2,
experiment = "UnstainedSignature",
condition = "Test", Brightness = TRUE)
BrightnessCSV <- list.files(TemporaryStorage, pattern="csv")
BrightnessCSV
#> [1] "RelativeBrightnessINF071_PMA_Unstained.csv"
BrightnessCSV <- list.files(TemporaryStorage, pattern="csv", full.names=TRUE)
TheBrightnessInformation <- read.csv(BrightnessCSV[1], check.names = FALSE)
head(TheBrightnessInformation, 5)
#> Cluster Brightness Detector1 Detector1Value Detector2
#> 1 V7_10-UV8_04-B3_05 19 V7 10 UV8
#> 2 V7_10-UV8_05-B3_05 20 V7 10 UV8
#> 3 V7_10-UV8_04-B3_04 18 V7 10 UV8
#> 4 V7_10-UV8_05-B3_04 19 V7 10 UV8
#> 5 V7_10-UV8_05-B3_06 21 V7 10 UV8
#> Detector2Value Detector3 Detector3Value Detector1Raw Detector2Raw
#> 1 4 B3 5 5572.359 1988.379
#> 2 5 B3 5 5049.207 2240.696
#> 3 4 B3 4 5381.337 1924.007
#> 4 5 B3 4 5008.231 2190.865
#> 5 5 B3 6 4722.747 2105.891
#> Detector3Raw Count Time UV1 UV2 UV3 UV4 UV5
#> 1 2517.288 746 375075.5 425.2763 701.2447 526.6494 672.3500 926.2291
#> 2 2271.021 733 359993.0 450.5638 785.2513 614.5607 722.7763 1001.7569
#> 3 1968.330 373 370222.0 395.0056 728.1313 537.3594 682.3907 959.2888
#> 4 1820.461 335 380210.0 458.8194 791.7219 642.3026 779.1525 1005.4013
#> 5 2528.843 318 316452.0 414.3060 683.8781 547.5116 655.8016 911.5400
#> UV6 UV7 UV8 UV9 UV10 UV11 UV12 UV13
#> 1 1487.798 2846.778 1988.379 1969.524 769.5953 511.3653 295.6035 237.9256
#> 2 1580.841 2923.979 2240.696 2021.736 748.2869 468.9344 281.6581 242.0906
#> 3 1536.439 2870.354 1924.007 1992.655 762.1950 468.7113 302.2600 269.3863
#> 4 1613.343 2879.280 2190.865 1928.693 710.6531 465.0669 256.7425 208.6219
#> 5 1450.015 2783.819 2105.891 1893.588 681.2379 422.1525 242.7228 174.8928
#> UV14 UV15 UV16 SSC-W SSC-H SSC V1 V2
#> 1 288.0172 227.9966 211.2250 683829.4 1107675 1261012 498.7125 1553.991
#> 2 278.0881 188.7638 175.4506 677780.6 1184736 1341172 470.5250 1465.956
#> 3 278.6831 202.4488 194.2675 679364.5 1091629 1250144 480.7000 1487.956
#> 4 289.9138 258.8250 169.9469 676423.9 1118851 1267046 433.8125 1438.731
#> 5 250.4206 198.6184 142.7628 674595.1 1153897 1301204 423.3969 1337.153
#> V3 V4 V5 V6 V7 V8 V9 V10
#> 1 2389.991 2714.181 4178.934 4014.313 5572.359 4092.859 2830.163 3114.341
#> 2 2207.975 2498.512 3834.531 3721.919 5049.207 3793.763 2586.238 2864.744
#> 3 2245.031 2556.331 3892.694 3766.263 5381.337 3829.238 2562.519 2879.869
#> 4 2189.206 2489.575 3741.788 3533.888 5008.231 3657.500 2464.550 2757.219
#> 5 2084.809 2350.425 3551.556 3501.300 4722.747 3607.175 2461.009 2723.016
#> V11 V12 V13 V14 V15 V16 FSC-W FSC-H
#> 1 1682.966 982.3000 864.8063 757.2812 662.9906 366.1969 675626.4 1318871
#> 2 1516.006 844.7313 740.5750 643.2250 566.6375 332.7500 676409.3 1406765
#> 3 1546.394 878.4875 816.6813 714.4500 661.9938 361.4875 673448.2 1348645
#> 4 1437.081 818.7438 704.1375 657.7313 543.6063 313.2250 674743.6 1397406
#> 5 1475.994 805.3031 713.9344 644.1531 563.3031 323.7438 675404.4 1393753
#> FSC SSC-B-W SSC-B-H SSC-B B1 B2 B3 B4
#> 1 1491917 669505.2 729827 821310.0 1572.070 1809.549 2517.288 1703.298
#> 2 1586855 665302.0 809745 886955.3 1349.236 1600.684 2271.021 1543.906
#> 3 1517825 668050.1 732468 819600.6 1363.656 1610.147 1968.330 1530.709
#> 4 1568460 663978.6 764488 842286.2 1246.429 1475.861 1820.461 1445.154
#> 5 1568841 662644.3 780609 861347.5 1404.952 1595.148 2528.843 1517.093
#> B5 B6 B7 B8 B9 B10 B11 B12
#> 1 1449.693 1177.2256 858.7625 644.5869 671.5278 444.7990 324.1925 312.4119
#> 2 1330.696 1018.9275 756.2775 544.6769 600.4256 359.0194 292.7775 255.3756
#> 3 1349.944 1083.3668 789.4950 612.4637 613.1719 416.6994 302.3694 294.1938
#> 4 1166.926 966.7194 671.5600 522.9825 543.5825 351.1012 232.0719 255.9550
#> 5 1272.887 1025.2040 720.0987 535.5356 558.9037 386.8937 298.1206 246.3631
#> B13 B14 YG1 YG2 YG3 YG4 YG5 YG6
#> 1 214.4653 304.5259 635.7178 572.4478 593.8847 539.6681 440.1844 462.8353
#> 2 201.4294 215.9781 601.3425 471.3338 471.9581 454.8919 345.9731 353.3269
#> 3 206.5794 269.2806 596.9719 499.1531 491.9381 512.5425 386.9738 433.6631
#> 4 173.8769 234.1962 545.8425 440.9475 443.7919 422.6325 312.8119 306.2906
#> 5 173.7803 253.5087 559.3359 454.3716 447.9197 446.1506 352.1128 327.8316
#> YG7 YG8 YG9 YG10 R1 R2 R3 R4
#> 1 522.7753 281.9747 241.6678 174.5475 218.5491 275.8259 300.2622 311.2797
#> 2 448.9950 185.5781 191.8913 181.0688 170.0650 256.5806 248.8825 300.1562
#> 3 531.4125 265.2206 214.7850 172.3275 183.4131 286.9494 257.8519 306.5831
#> 4 433.8019 166.9856 198.6206 137.9869 181.5769 256.0156 235.8875 252.1313
#> 5 435.0853 181.0341 223.8037 115.7522 174.5144 218.6197 244.5038 264.4906
#> R5 R6 R7 R8
#> 1 167.3459 169.2528 188.53343 102.6888
#> 2 156.7169 117.0963 137.93062 108.9744
#> 3 159.6125 180.9413 126.91312 105.8669
#> 4 177.5513 154.5981 136.16499 49.1550
#> 5 166.7809 110.4928 81.39531 109.6453
Similarly, in combination with purrr map function, we can iterate over our GatingSet to generate subcluster .fcs files for all our Unstained samples in one go.
#StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
TemporaryStorage <- tempdir()
TheUnstaineds <- map(.x=MyUnstainedGatingSet[1:3], .f=Luciernaga_QC,
subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells",
Unstained = TRUE, ratiopopcutoff = 0.001,
Verbose = FALSE, AFOverlap = AFOverlap,
stats = "median", ExportType = "fcs",
SignatureReturnNow = FALSE, outpath = StorageLocation,
Increments=0.1, SecondaryPeaks=2,
experiment = "UnstainedSignature",
condition = "Test", Brightness = TRUE)
Single Color Unmixing Controls
Now that we have covered unstained unmixing controls, let’s look at
how Luciernaga_QC()
works with single-color unmixing
controls. Let’s set the returnsignaturenow = TRUE as we had done
previously when starting with the unstained samples.
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
pData(MyGatingSet[2])
#> name
#> CCR6_BV786(Cells).fcs CCR6_BV786(Cells).fcs
SingleStain_Signatures <- Luciernaga_QC(x=MyGatingSet[2],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = FALSE,
ratiopopcutoff = 0.01,
Verbose = TRUE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data",
SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.09 of all events were negative and will be rounded to 0
#> Fluors Counts
#> 1 V7 3671
#> 2 V15 1356
#> 3 UV7 645
#> 4 V5 61
#> [1] "V15"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V7 3671
#> 2 V15 1356
#> 3 UV7 645
#> 4 V5 61
V7 <- Luciernaga_QC(x=MyGatingSet[2], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="V7-A")
#> Normalizing Data for Signature Comparison
V15 <- Luciernaga_QC(x=MyGatingSet[2], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="V15-A")
#> Normalizing Data for Signature Comparison
UV7 <- Luciernaga_QC(x=MyGatingSet[2], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="UV7-A")
#> Normalizing Data for Signature Comparison
V5 <- Luciernaga_QC(x=MyGatingSet[2], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="V5-A")
#> Normalizing Data for Signature Comparison
data <- rbind(V7, V15, UV7, V5)
Sample <- data.frame(Sample=c("V7", "V15", "UV7", "V5"))
Data <- cbind(Sample, data)
Fluors <- Data %>% pull(Sample)
Raw_Results <- QC_ViewSignature(x=Fluors, data=Data, Normalize = FALSE)
plotly::ggplotly(Raw_Results)
Normalized_Results <- QC_ViewSignature(x=Fluors, data=Data)
#> Normalizing Data for Signature Comparison
plotly::ggplotly(Normalized_Results)
As we can see from the returned peak detectors, for this BV786 single-stained unmixing control, there were 4 detectors which cells from the sample had primary peaks on. Looking at the signatures (and referencing to the results from our Unstained samples looked at previously) three are unstained peak detectors (V7, UV7, V5). Consequently, the remaining detector (V15) is likely to be of antibody-staining origin. This is particularly obvious when we look at the normalized signature. In the case of Raw MFI plot, the antibody-stained cells are way brighter than the underlying autofluorescence of cells that didn’t get stained by the antibody (due to not having the antigen).
Please note that at this stage, autofluorescence background has not yet been subtracted from the single-color unmixing control, which is why we can see for ID_V15 residual autofluorescence present in both the raw and normalized signature. In the normalized signature, it materializes as minor peaks below 0.2 at both the UV7-A and V7-A. It’s this bit of background that we need to properly extract in order to provide a good signature for unmixing.
Luciernaga_QC()
carries this process in a similar manner
for all single-color unmixing controls, enumerating peak detectors,
removing those listed by the AFOverlap .csv for unstained from
consideration, and then focusing on the likely antibody-stain origin
detectors for further processing.
The advantage of isolating peak detectors in this manner is that it
allows Luciernaga
to profile unusual fluorescence signals
that may be present within a single-color unmixing control. These could
be rarer autofluorescences, but they could also be indicators for the
overall quality of the antibody-fluorophore conjugation.
It’s because of this that we can also see why the AFOverlap is needed when a fluorophore shares it’s peak detector with the main autofluorescence peaks. Let’s take a look at what happens with BV510 for this specific case:
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
#pData(MyGatingSet)
SingleStain_Signatures <- Luciernaga_QC(x=MyGatingSet[16],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = FALSE,
ratiopopcutoff = 0.01,
Verbose = TRUE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data",
SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.08 of all events were negative and will be rounded to 0
#> Fluors Counts
#> 1 V7 2122
#> [1] "V7"
#> Only a single detector present. If this was not an autofluorescence overlap
#> fluourophore, it would suggest there was no antibody staining, or everything
#> was overstained. Please investigate further.
#> Error in Luciernaga_QC(x = MyGatingSet[16], subsets = "lymphocytes", removestrings = removestrings, : Only one detector present and no external cell autofluorescence signature was provided
#> for subtraction. Please provision the CellAF argument.
In this case, the function errors out and calls for an external autofluorescence signature to be provided to the ExternalAF argument. When we look at the Verbose = TRUE message, we can see that for this particular sample, only V7 is present. We all address how to deal this shortly.
While just working off the peak detector works quite nicely for bright fluorophores, this is not always the case. Brightness is impacted not only by the fluorophore itself, but also by the antigen density on the cell surfact that the fluorophore-conjugated antibodies latch on to.
A good highlighting example of this more-complicated side is our PerCP-Cy5.5 CD26 unmixing control. In this panel, it was added after fixation, and had some issues with degradatation and non-specific/background staining:
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
#pData(MyGatingSet)
SingleStain_Signatures <- Luciernaga_QC(x=MyGatingSet[9],
subsets="lymphocytes",
removestrings=removestrings,
sample.name="GUID",
unmixingcontroltype = "cells",
Unstained = FALSE,
ratiopopcutoff = 0.01,
Verbose = TRUE,
AFOverlap = AFOverlap,
stats = "median",
ExportType = "data",
SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.02 of all events were negative and will be rounded to 0
#> Fluors Counts
#> 1 V7 2675
#> 2 B9 2383
#> 3 UV7 307
#> 4 B14 285
#> 5 R3 83
#> [1] "B9" "B14" "R3"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V7 2675
#> 2 B9 2383
#> 3 UV7 307
#> 4 B14 285
#> 5 R3 83
V7 <- Luciernaga_QC(x=MyGatingSet[9], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="V7-A")
#> Normalizing Data for Signature Comparison
B9 <- Luciernaga_QC(x=MyGatingSet[9], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="B9-A")
#> Normalizing Data for Signature Comparison
UV7 <- Luciernaga_QC(x=MyGatingSet[9], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="UV7-A")
#> Normalizing Data for Signature Comparison
B14 <- Luciernaga_QC(x=MyGatingSet[9], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="B14-A")
#> Normalizing Data for Signature Comparison
R3 <- Luciernaga_QC(x=MyGatingSet[9], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = TRUE, outpath = NULL,
desiredAF="R3-A")
#> Normalizing Data for Signature Comparison
data <- rbind(V7, B9, UV7, B14, R3)
Sample <- data.frame(Sample=c("V7", "B9", "UV7", "B14", "R3"))
Data <- cbind(Sample, data)
Fluors <- Data %>% pull(Sample)
Results <- QC_ViewSignature(x=Fluors, data=Data, Normalize = FALSE)
plotly::ggplotly(Results)
Results <- QC_ViewSignature(x=Fluors, data=Data)
#> Normalizing Data for Signature Comparison
plotly::ggplotly(Results)
As we can see from the signatures, there are only a few detectors that reach peak detector status. However, unlike was the case with BV786, the peak-detectors corresponding to antibody-staining are not that much brighter than the underlying autofluorescence in terms on raw MFI. Consequently, without subtracting out the background signal, the normalized signatures end up introducing autofluorescence artefacts to what should be fluorophore specific peaks, and fluorophore contributions to what should be purely autofluorescence detectors. Consequently, an additional step to process single-stained cells is needed.
One way that we can do this is through the provisioning of an external autofluorescence signature. While this is required for the autofluorescence overlap fluorophores, it is an option that can be implemented for all fluorophores if desired.
# pData(MyUnstainedGatingSet[1])
V7_Signature <- Luciernaga_QC(x=MyUnstainedGatingSet[1], desiredAF = "V7-A",
subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells",
Unstained = TRUE, ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> Normalizing Data for Signature Comparison
PerCPCy55_Signatures <- Luciernaga_QC(x=MyGatingSet[9], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = FALSE, outpath = NULL,
experiment = "PerCP", condition = "Test", Increments=0.1, Subtraction = "External",
CellAF=V7_Signature)
Data <- PerCPCy55_Signatures %>% filter(Count > 60) %>% select(-Sample, -Experiment, -Condition, -Count) %>%
rename(Sample = Cluster)
Fluorophores <- Data %>% pull(Sample)
AfterSubtraction <- QC_ViewSignature(x=Fluorophores, data=Data)
plotly::ggplotly(AfterSubtraction)
As we can see, we are left with signatures more closely resembling that of PerCP-Cy5.5.
PerCPCy5.5_Sample <- Data[1,]
x <- PerCPCy5.5_Sample %>% pull(Sample)
ThePerCPCy5.5 <- QC_WhatsThis(x=x, data=PerCPCy5.5_Sample, NumberHits = 5, returnPlots = TRUE)
ThePerCPCy5.5[1]
#> [[1]]
#> Fluorophore ID_B9_10-V13_07-YG6_05
#> 1 cFluor B690 0.94
#> 2 PerCP-Cy5.5 0.94
#> 3 PerCP-Vio 700 0.93
#> 4 StarBright B700 0.92
#> 5 BB700 0.89
plotly::ggplotly(ThePerCPCy5.5[[2]])
As we can see, this did work well and improved the returned signature. An alternative way, is to rely on an internal autofluorescence. As was the case in BV786, for many single-color unmixing controls, there are quite a few cells that remain unstained within the sample that could potentially serve as an internal autofluorescence negative. This is particularly helpful in cases where a mismatch ends up occurring vs an external unstained sample during the processing and we end up with mismatching autofluorescence. However, if the antibody has a lot of non-specific background, it can contaminate the unstained peak detectors as we saw in the case above with PerCP-Cy5.5.
Our way of handling this issue when using
Luciernaga_QC()
internal negatives for subtraction is as
follows. We set Subtraction = “Internal”, and leave desiredAF = NULL
(the R equivalent to empty). The main autofluorescent overlap peak is
worked out internally via the AFOverlap csv and the count of peak
detectors. After the initial peaks are characterized, the cells in a
sample are initially grouped on basis of shared peak detectors. Once
this is done, for each “likely” antibody detector, they are subclustered
on the basis of the normalized values of both that detector and the Main
Autofluorescent detector.
What we end up with, for the case above, would be a continuum of clusters ranging from those where the antibody stain is way brighter than underlying autofluorescence (B9_10-V7_00) to cells with equal brightness for both antibody and autofluorescence (B9_10-V7_10), all the way through cells that only have autofluorescence (V7_10-B9_00). From this continuum, the bin with the least amount of “antibody” detector is selected as the “uncontaminated” autofluorescence, and these cells are filtered out, and the average autofluorescence signature is extracted from them. This in turn is subtracted from all initial subclustering cells removing the background. At this point, cells with the subtracted values are re-enumerated on basis of their peak detector, and passed onward in the function to the rolling local maxima step that was described in the unstained section of the vignette to figure out the secondary peak detectors needed to continue with the pipeline.
Does this more complicated way work, and is it worth the hassle? Let’s compare the external AF from the above to the internal AF method below:
InternalPerCPCy55_Signatures <- Luciernaga_QC(x=MyGatingSet[9], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = FALSE, outpath = NULL,
experiment = "PerCP", condition = "Test", Increments=0.1, Subtraction = "Internal")
Data <- InternalPerCPCy55_Signatures %>% filter(Count > 60) %>% select(-Sample, -Experiment, -Condition, -Count) %>%
rename(Sample = Cluster)
Fluorophores <- Data %>% pull(Sample)
AfterSubtraction <- QC_ViewSignature(x=Fluorophores, data=Data)
plotly::ggplotly(AfterSubtraction)
InternalPerCPCy5.5_Sample <- Data[1,]
x <- InternalPerCPCy5.5_Sample %>% pull(Sample)
TheInternalPerCPCy5.5 <- QC_WhatsThis(x=x, data=InternalPerCPCy5.5_Sample, NumberHits = 5, returnPlots = TRUE)
TheInternalPerCPCy5.5[1]
#> [[1]]
#> Fluorophore ID_B9_10-YG6_06-V13_06
#> 1 cFluor B690 0.98
#> 2 PerCP-Cy5.5 0.98
#> 3 StarBright B700 0.93
#> 4 PerCP-Vio 700 0.92
#> 5 BB700 0.89
plotly::ggplotly(TheInternalPerCPCy5.5[[2]])
As you can see, that the main peaks are similar for both methods (which is good considering they are the same .fcs file), but better matching signature of the secondary peaks. In our testing, either approach provides good results, with internal AF providing some additional usefulness in cases where the experiment required troubleshooting. Being aware of possible scenarios in which picking the least contaminated bin could cause issues will be discussed in the unmixing vignette along with the broader discussion on brightness role in the process.
And finally for good measure, let’s generate the signature for BV510 using the externalAF to demonstrate the AFOverlap cases as well. For these, we provision the CellAF argument:
pData(MyGatingSet[16])
#> name
#> CD45RA_BV510(Cells).fcs CD45RA_BV510(Cells).fcs
V7_Signature <- Luciernaga_QC(x=MyUnstainedGatingSet[1], desiredAF = "V7-A",
subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells",
Unstained = TRUE, ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> Normalizing Data for Signature Comparison
BV510 <- Luciernaga_QC(x=MyGatingSet[16], subsets="lymphocytes", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE, AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = FALSE, outpath = NULL,
experiment = "PerCP", condition = "Test", Increments=0.1, Subtraction="Internal",
CellAF=V7_Signature)
#> Only a single detector present. If this was not an autofluorescence overlap
#> fluourophore, it would suggest there was no antibody staining, or everything
#> was overstained. Please investigate further.
Data <- BV510 %>% filter(Count > 60) %>% select(-Sample, -Experiment, -Condition, -Count) %>%
rename(Sample = Cluster)
Fluorophores <- Data %>% pull(Sample)
AfterSubtraction <- QC_WhatsThis(x=Fluorophores, data=Data, NumberHits=5, returnPlots = TRUE)
AfterSubtraction[1]
#> [[1]]
#> Fluorophore ID_V7_10-V6_08-UV8_04
#> 1 BV510 0.97
#> 2 LIVE DEAD Aqua 0.96
#> 3 Zombie Aqua 0.94
#> 4 VioGreen 0.93
#> 5 Krome Orange 0.91
plotly::ggplotly(AfterSubtraction[[2]])
Small rounding errors contribute to the the plateau appearance at 0.1 compared to the reference control and remain on my fix-list.
Data Export
For the above examples, we had ExportType set to “data”. As was the case with the Unstained unmixing controls, we can also export these as “.fcs” files for storage and use in other flow cytometry software. Unlike Unstained controls where nothing was subtracted, single color unmixing controls had values subtracted to derive the signatures that were used for the clustering, so they require a few additional arguments.
SCData can be set to either “subtracted” or “raw”. It dictates whether the values within the new “.fcs” file are the ones after background subtraction, or the original raw MFI values that would need to be subtracted from again. Please note, at this time, “raw” only works when Subtracted = “Internal_General” or “External”. We are still in process of designing a way for “Internal” to return raw values rather than the after subtraction ones.
What choice we select for SCData partly depends on what you plan to do with the .fcs files afterwards. If we are bringing in to other software (let’s use Cytek’s SpectroFlo for our current example) we would want to avoid having the software subtract out background autofluorescence from a Luciernaga .fcs output that has already had the background subtracted and was returned with SCData = “subtracted”. We would need to either export with SCData set to “raw”, or provision the file with a workaround to get around the subtraction step.
One way we have done this during development was to provide the NegativeType argument, which comes with the arguments “default”, “artificial” and “samples”. Default doesn’t add anything to the .fcs file. Artificial adds a certain number of events with MFI values for all detectors set to 0. Samples returns a certain number of events with the same MFI values corresponding to the autofluorescence signature used to subtract out background (currently restricted to Subtraction = “Internal_Generalized” and “External”).
If we are using a single-color unmixed .fcs file from Luciernaga within SpectroFlo, we would set the single color controls we are adding to use internal background subtraction within the software, and if 1) SCData was set to “subtracted” use NegativeType “artifical” to provision it with an internal negative consisting entirely of 0 MFI values which we can set the internal negative gate on. Consequently, no MFI values are further altered, and the single color unmixing control retains it’s signature for unmixing.
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
SingleStain_Signatures <- Luciernaga_QC(x=MyGatingSet[2], subsets="lymphocytes",
removestrings=removestrings, sample.name="GUID",
unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "fcs", SignatureReturnNow = FALSE,
outpath = StorageLocation, Increments=0.1,
SecondaryPeaks=2, experiment = "UnstainedSignature",
condition = "Test", SCData="subtracted",NegativeType="artificial")
Alternatively, 2) if SCData was set to return “raw” MFI values (original values), we could provision NegativeType with “samples” to provide the background AF signature as events we could set the internal negative gate on to extract the same final signature within the software.
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
SingleStain_Signatures <- Luciernaga_QC(x=MyGatingSet[2], subsets="lymphocytes",
removestrings=removestrings, sample.name="GUID",
unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "fcs", SignatureReturnNow = FALSE,
outpath = StorageLocation, Increments=0.1,
SecondaryPeaks=2, experiment = "UnstainedSignature",
condition = "Test", SCData="subtracted",NegativeType="samples")
And finally, if we are not bringing the files into a software that
will try to extract additional background, we can leave SCData =
“subtractd” and NegativeType = “default” to retain the data in the
format it is returned by Luciernaga_QC()
TemporaryFolder <- tempdir()
SingleStain_Signatures <- Luciernaga_QC(x=MyGatingSet[2], subsets="lymphocytes",
removestrings=removestrings, sample.name="GUID",
unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "fcs", SignatureReturnNow = FALSE,
outpath = TemporaryFolder, Increments=0.1,
SecondaryPeaks=2, experiment = "UnstainedSignature",
condition = "Test", SCData="subtracted", NegativeType="default")
ExportedFCSFiles <- list.files(TemporaryFolder, pattern="fcs")
ExportedFCSFiles
As you can tell, there are some coding decisions/bugs/consideration for this portion of the workflow that remain to be automated when I have time and the paper I need to graduate is submitted and I can justify yet again optimizing code with multiple moving parts. So until then, hang on, and reach out if with any questions.
And as always, in combination with the purrr map function, we can iterate over the entire GatingSet, returning isolated signatures for all single-color unmixing controls.
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
SingleStain_Signatures <- map(.x=MyGatingSet, .f=Luciernaga_QC, subsets="lymphocytes",
removestrings=removestrings, sample.name="GUID",
unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "fcs", SignatureReturnNow = FALSE,
outpath = TemporaryFolder, Increments=0.1,
SecondaryPeaks=2, experiment = "UnstainedSignature",
condition = "Test", SCData="subtracted",
NegativeType="default"))
Bead Single-Color Signatures
We have covered most of the basic functionality to
Luciernaga_QC()
in the above examples, but we will briefly
cover the adjustments when running unmixing controls derived with beads.
While recovering the main fluorescence peak from stained beads is rather
simple due to it’s substantially brighter MFI, in our experience with
Invitrogen’s UltraComp eBeads Plus(TM) it was difficult to differentiate
the unstained bead peaks from degrading antibody peaks. This is because
unlike unstained cells, there is no one single bead autofluorescence
peak, but rather due to the low levels of MFI background, what detector
ends up being characterized as a peak detector resembles noisy
distribution across all detectors.
pData(MyBeadsGatingSet)
#> name
#> CCR4_BUV615(Beads).fcs CCR4_BUV615(Beads).fcs
#> CCR6_BV786(Beads).fcs CCR6_BV786(Beads).fcs
#> CCR7_BV650(Beads).fcs CCR7_BV650(Beads).fcs
#> CD107a_APC-R700(Beads).fcs CD107a_APC-R700(Beads).fcs
#> CD127_BV421(Beads).fcs CD127_BV421(Beads).fcs
#> CD16_APC(Beads).fcs CD16_APC(Beads).fcs
#> CD161_BV480(Beads).fcs CD161_BV480(Beads).fcs
#> CD25_PE-Cy5(Beads).fcs CD25_PE-Cy5(Beads).fcs
#> CD26_PerCP-Cy5.5(Beads).fcs CD26_PerCP-Cy5.5(Beads).fcs
#> CD27_APC-Fire750(Beads).fcs CD27_APC-Fire750(Beads).fcs
#> CD3_AlexaFluor488(Beads).fcs CD3_AlexaFluor488(Beads).fcs
#> CD3_AlexaFluor647(Beads).fcs CD3_AlexaFluor647(Beads).fcs
#> CD3_SparkBlue550(Beads).fcs CD3_SparkBlue550(Beads).fcs
#> CD38_APC-Fire810(Beads).fcs CD38_APC-Fire810(Beads).fcs
#> CD4_BUV805(Beads).fcs CD4_BUV805(Beads).fcs
#> CD45RA_BV510(Beads).fcs CD45RA_BV510(Beads).fcs
#> CD56_BV605(Beads).fcs CD56_BV605(Beads).fcs
#> CD62L_BUV395(Beads).fcs CD62L_BUV395(Beads).fcs
#> CD69_BUV563(Beads).fcs CD69_BUV563(Beads).fcs
#> CD7_BV711(Beads).fcs CD7_BV711(Beads).fcs
#> CD8_BUV496(Beads).fcs CD8_BUV496(Beads).fcs
#> CXCR3_BUV737(Beads).fcs CXCR3_BUV737(Beads).fcs
#> Dump_CD14_PacificBlue(Beads).fcs Dump_CD14_PacificBlue(Beads).fcs
#> Dump_CD19_PacificBlue(Beads).fcs Dump_CD19_PacificBlue(Beads).fcs
#> IFNg_BV750(Beads).fcs IFNg_BV750(Beads).fcs
#> NKG2D_PE(Beads).fcs NKG2D_PE(Beads).fcs
#> PD1_PE-Vio770(Beads).fcs PD1_PE-Vio770(Beads).fcs
#> TNFa_PE-Dazzle594(Beads).fcs TNFa_PE-Dazzle594(Beads).fcs
#> Va7.2_AlexaFluor647(Beads).fcs Va7.2_AlexaFluor647(Beads).fcs
#> VD2_BUV661(Beads).fcs VD2_BUV661(Beads).fcs
BV786 <- Luciernaga_QC(x=MyBeadsGatingSet[2], desiredAF = NULL,
subsets="singlets", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "beads",
Unstained = FALSE, ratiopopcutoff = 0.001, Verbose = TRUE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.19 of all events were negative and will be rounded to 0
#> Returning Peak Bead Detector Medians
#> TheDetector TheMedian
#> 1 V15 394556.2500
#> 2 YG1 768.0050
#> 3 V7 713.0750
#> 4 UV9 764.5007
#> 5 V5 765.1188
#> 6 V3 735.7625
#> 7 V4 719.6750
#> Fluors Counts
#> 1 V15 1146
#> [1] "V15"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V15 1146
For the staining beads we use, the only bright signal is the fluorophore stained beads as seen above. Beyond that, the next occupied peak detector is dictated often by noise and the normalize signature reflects this uncertainty, as we see when we set the desiredAF to retrieve the “YG1-A” instead.
pData(MyBeadsGatingSet)
#> name
#> CCR4_BUV615(Beads).fcs CCR4_BUV615(Beads).fcs
#> CCR6_BV786(Beads).fcs CCR6_BV786(Beads).fcs
#> CCR7_BV650(Beads).fcs CCR7_BV650(Beads).fcs
#> CD107a_APC-R700(Beads).fcs CD107a_APC-R700(Beads).fcs
#> CD127_BV421(Beads).fcs CD127_BV421(Beads).fcs
#> CD16_APC(Beads).fcs CD16_APC(Beads).fcs
#> CD161_BV480(Beads).fcs CD161_BV480(Beads).fcs
#> CD25_PE-Cy5(Beads).fcs CD25_PE-Cy5(Beads).fcs
#> CD26_PerCP-Cy5.5(Beads).fcs CD26_PerCP-Cy5.5(Beads).fcs
#> CD27_APC-Fire750(Beads).fcs CD27_APC-Fire750(Beads).fcs
#> CD3_AlexaFluor488(Beads).fcs CD3_AlexaFluor488(Beads).fcs
#> CD3_AlexaFluor647(Beads).fcs CD3_AlexaFluor647(Beads).fcs
#> CD3_SparkBlue550(Beads).fcs CD3_SparkBlue550(Beads).fcs
#> CD38_APC-Fire810(Beads).fcs CD38_APC-Fire810(Beads).fcs
#> CD4_BUV805(Beads).fcs CD4_BUV805(Beads).fcs
#> CD45RA_BV510(Beads).fcs CD45RA_BV510(Beads).fcs
#> CD56_BV605(Beads).fcs CD56_BV605(Beads).fcs
#> CD62L_BUV395(Beads).fcs CD62L_BUV395(Beads).fcs
#> CD69_BUV563(Beads).fcs CD69_BUV563(Beads).fcs
#> CD7_BV711(Beads).fcs CD7_BV711(Beads).fcs
#> CD8_BUV496(Beads).fcs CD8_BUV496(Beads).fcs
#> CXCR3_BUV737(Beads).fcs CXCR3_BUV737(Beads).fcs
#> Dump_CD14_PacificBlue(Beads).fcs Dump_CD14_PacificBlue(Beads).fcs
#> Dump_CD19_PacificBlue(Beads).fcs Dump_CD19_PacificBlue(Beads).fcs
#> IFNg_BV750(Beads).fcs IFNg_BV750(Beads).fcs
#> NKG2D_PE(Beads).fcs NKG2D_PE(Beads).fcs
#> PD1_PE-Vio770(Beads).fcs PD1_PE-Vio770(Beads).fcs
#> TNFa_PE-Dazzle594(Beads).fcs TNFa_PE-Dazzle594(Beads).fcs
#> Va7.2_AlexaFluor647(Beads).fcs Va7.2_AlexaFluor647(Beads).fcs
#> VD2_BUV661(Beads).fcs VD2_BUV661(Beads).fcs
NotBV786 <- Luciernaga_QC(x=MyBeadsGatingSet[2], desiredAF = "YG1-A",
subsets="singlets", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "beads",
Unstained = FALSE, ratiopopcutoff = 0.001, Verbose = TRUE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.19 of all events were negative and will be rounded to 0
#> Returning Peak Bead Detector Medians
#> TheDetector TheMedian
#> 1 V15 394556.2500
#> 2 YG1 768.0050
#> 3 V7 713.0750
#> 4 UV9 764.5007
#> 5 V5 765.1188
#> 6 V3 735.7625
#> 7 V4 719.6750
#> Fluors Counts
#> 1 V15 1146
#> [1] "V15"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V15 1146
pData(MyBeadsGatingSet)
#> name
#> CCR4_BUV615(Beads).fcs CCR4_BUV615(Beads).fcs
#> CCR6_BV786(Beads).fcs CCR6_BV786(Beads).fcs
#> CCR7_BV650(Beads).fcs CCR7_BV650(Beads).fcs
#> CD107a_APC-R700(Beads).fcs CD107a_APC-R700(Beads).fcs
#> CD127_BV421(Beads).fcs CD127_BV421(Beads).fcs
#> CD16_APC(Beads).fcs CD16_APC(Beads).fcs
#> CD161_BV480(Beads).fcs CD161_BV480(Beads).fcs
#> CD25_PE-Cy5(Beads).fcs CD25_PE-Cy5(Beads).fcs
#> CD26_PerCP-Cy5.5(Beads).fcs CD26_PerCP-Cy5.5(Beads).fcs
#> CD27_APC-Fire750(Beads).fcs CD27_APC-Fire750(Beads).fcs
#> CD3_AlexaFluor488(Beads).fcs CD3_AlexaFluor488(Beads).fcs
#> CD3_AlexaFluor647(Beads).fcs CD3_AlexaFluor647(Beads).fcs
#> CD3_SparkBlue550(Beads).fcs CD3_SparkBlue550(Beads).fcs
#> CD38_APC-Fire810(Beads).fcs CD38_APC-Fire810(Beads).fcs
#> CD4_BUV805(Beads).fcs CD4_BUV805(Beads).fcs
#> CD45RA_BV510(Beads).fcs CD45RA_BV510(Beads).fcs
#> CD56_BV605(Beads).fcs CD56_BV605(Beads).fcs
#> CD62L_BUV395(Beads).fcs CD62L_BUV395(Beads).fcs
#> CD69_BUV563(Beads).fcs CD69_BUV563(Beads).fcs
#> CD7_BV711(Beads).fcs CD7_BV711(Beads).fcs
#> CD8_BUV496(Beads).fcs CD8_BUV496(Beads).fcs
#> CXCR3_BUV737(Beads).fcs CXCR3_BUV737(Beads).fcs
#> Dump_CD14_PacificBlue(Beads).fcs Dump_CD14_PacificBlue(Beads).fcs
#> Dump_CD19_PacificBlue(Beads).fcs Dump_CD19_PacificBlue(Beads).fcs
#> IFNg_BV750(Beads).fcs IFNg_BV750(Beads).fcs
#> NKG2D_PE(Beads).fcs NKG2D_PE(Beads).fcs
#> PD1_PE-Vio770(Beads).fcs PD1_PE-Vio770(Beads).fcs
#> TNFa_PE-Dazzle594(Beads).fcs TNFa_PE-Dazzle594(Beads).fcs
#> Va7.2_AlexaFluor647(Beads).fcs Va7.2_AlexaFluor647(Beads).fcs
#> VD2_BUV661(Beads).fcs VD2_BUV661(Beads).fcs
NotBV786 <- Luciernaga_QC(x=MyBeadsGatingSet[2], desiredAF = "V7-A",
subsets="singlets", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "beads",
Unstained = FALSE, ratiopopcutoff = 0.001, Verbose = TRUE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.19 of all events were negative and will be rounded to 0
#> Returning Peak Bead Detector Medians
#> TheDetector TheMedian
#> 1 V15 394556.2500
#> 2 YG1 768.0050
#> 3 V7 713.0750
#> 4 UV9 764.5007
#> 5 V5 765.1188
#> 6 V3 735.7625
#> 7 V4 719.6750
#> Fluors Counts
#> 1 V15 1146
#> [1] "V15"
#> Normalizing Data for Signature Comparison
#> Fluors Counts
#> 1 V15 1146
Because each of these noisy background signatures is so completely
different from each other (unlike is the case with cells) we need to
provision Luciernaga_QC()
with a general unstained bead
autofluorescence signature to the BeadAF signature to
Luciernaga_QC()
to enable external autofluorescence
subtraction for the bead controls. Examples of how to do this are shown
below:
MyUnstainedBeadsGatingSet
#> A GatingSet with 1 samples
UnstainedBeads <- Luciernaga_QC(x=MyUnstainedBeadsGatingSet[1], desiredAF = NULL,
subsets="singlets", removestrings=removestrings,
sample.name="GUID", unmixingcontroltype = "beads",
Unstained = TRUE, ratiopopcutoff = 0.001, Verbose = TRUE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data.frame", SignatureReturnNow = TRUE,
outpath = NULL)
#> 0.32 of all events were negative and will be rounded to 0
#> Returning Peak Bead Detector Medians
#> TheDetector TheMedian
#> 1 YG1 757.4700
#> 2 V7 948.5438
#> 3 UV9 762.5669
#> 4 V5 819.8438
#> 5 V3 814.5500
#> 6 V8 773.3687
#> 7 V4 772.7500
#> 8 V6 792.4125
#> 9 UV7 768.3682
#> 10 UV2 768.2194
#> 11 R4 659.6800
#> 12 V2 744.3563
#> 13 V10 703.5875
#> Fluors Counts
#> 1 V7 215
#> 2 V5 115
#> 3 V3 64
#> 4 V6 50
#> Normalizing Data for Signature Comparison
#> Returning designated stats values for Beads_Unstained, please return
#> to LuciernagaQC as BeadsAF for subtraction for single color unmixing controls
UnstainedBeads
#> UV1-A UV2-A UV3-A UV4-A UV5-A UV6-A UV7-A UV8-A
#> 1 87.05594 113.8309 65.1525 98.58407 108.885 152.8406 214.1628 153.3984
#> UV9-A UV10-A UV11-A UV12-A UV13-A UV14-A UV15-A UV16-A
#> 1 317.5441 18.70531 84.89907 25.17594 21.97781 30.41938 20.04406 46.07532
#> V1-A V2-A V3-A V4-A V5-A V6-A V7-A V8-A V9-A
#> 1 135.1281 226.9438 339.5563 296.7594 363 323.6406 419.6844 295.8313 191.4
#> V10-A V11-A V12-A V13-A V14-A V15-A V16-A B1-A B2-A
#> 1 230.3469 121.825 69.50625 69.19688 61.94375 64.69375 11.20625 220.545 136.175
#> B3-A B4-A B5-A B6-A B7-A B8-A B9-A B10-A B11-A B12-A
#> 1 174.2325 124.9625 135.3625 107.5425 95.94 34.2875 73.9375 52.325 41.34 32.695
#> B13-A B14-A YG1-A YG2-A YG3-A YG4-A YG5-A YG6-A YG7-A YG8-A YG9-A
#> 1 39.65 35.035 486.465 163.1 125.37 100.24 36.68 48.125 69.58 30.38 56.525
#> YG10-A R1-A R2-A R3-A R4-A R5-A R6-A R7-A R8-A
#> 1 0 149.24 120.855 138.25 157.815 70.84 53.48 74.935 59.115
With this now provisioned, we can run single-color bead unmixing
controls through Luciernaga_QC()
in similar manner as the
cell single-color unmixing controls. We also need to provide a stand-in
argument for main autofluorescence detector (BeadMainAF=“UV1-A”) even
though the detector is not used (something I will update out going
forward)
StorageLocation <- file.path("C:", "Users", "12692", "Desktop")
SingleStain_Signatures <- map(.x=MyBeadsGatingSet[2], .f=Luciernaga_QC, subsets="singlets",
removestrings=removestrings, sample.name="GUID",
unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "data", SignatureReturnNow = FALSE,
outpath = StorageLocation, Increments=0.1,
SecondaryPeaks=2, experiment = "UnstainedSignature",
condition = "Test", SCData="subtracted",
NegativeType="default", BeadAF=UnstainedBeads,
BeadMainAF="UV1-A")
gt(SingleStain_Signatures[[1]])
Sample | Experiment | Condition | Cluster | Count | UV1 | UV2 | UV3 | UV4 | UV5 | UV6 | UV7 | UV8 | UV9 | UV10 | UV11 | UV12 | UV13 | UV14 | UV15 | UV16 | V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | V11 | V12 | V13 | V14 | V15 | V16 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | B8 | B9 | B10 | B11 | B12 | B13 | B14 | YG1 | YG2 | YG3 | YG4 | YG5 | YG6 | YG7 | YG8 | YG9 | YG10 | R1 | R2 | R3 | R4 | R5 | R6 | R7 | R8 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CCR6_BV786(Beads) | UnstainedSignature | Test | V15_10-UV16_02 | 1126 | 0.1 | 0.1 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 | 0.1 | 0.1 | 0.1 | 0.20 | 0.2 | 0.10 | 0.1 | 0.1 | 0.1 | 0.10 | 0.1 | 0.1 | 0.1 | 0.1 | 0.10 | 0.10 | 0.1 | 0.1 | 0.30 | 1 | 0.6 | 0.10 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 | 0.10 | 0.10 | 0.10 | 0.10 | 0.10 | 0.1 | 0.1 | 0.1 | 0.10 | 0.10 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 | 0.1 | 0.1 | 0.1 | 0.10 | 0.1 | 0.10 | 0.1 |
CCR6_BV786(Beads) | UnstainedSignature | Test | V15_10-UV16_00 | 14 | 0.0 | 0.0 | 0.1 | 0.05 | 0.0 | 0.05 | 0.0 | 0.05 | 0.0 | 0.05 | 0.2 | 0.1 | 0.2 | 0.1 | 0.05 | 0.0 | 0.05 | 0.0 | 0.1 | 0.1 | 0.05 | 0.0 | 0.0 | 0.0 | 0.0 | 0.05 | 0.15 | 0.0 | 0.0 | 0.15 | 1 | 0.3 | 0.05 | 0.0 | 0.05 | 0.1 | 0.25 | 0.0 | 0.15 | 0.05 | 0.05 | 0.05 | 0.05 | 0.0 | 0.0 | 0.1 | 0.05 | 0.05 | 0.0 | 0.05 | 0.1 | 0.05 | 0.0 | 0.05 | 0.1 | 0.05 | 0.1 | 0.1 | 0.2 | 0.0 | 0.05 | 0.2 | 0.05 | 0.0 |
Beads are not subject to the AFOverlap.csv, are set to use Subtraction=“External” by default. Consequently, they will accept SCData of “raw” or “subtracted”, and NegativeType = “default”, “artificial”, “sample” in a similar manner as was described in detail for the single-color cell unmixing controls for their export arguments.
StorageLocation <- file.path("C:", "Users", "JohnDoe", "Desktop")
SingleStain_Signatures <- map(.x=MyBeadsGatingSet[2], .f=Luciernaga_QC, subsets="singlets",
removestrings=removestrings, sample.name="GUID",
unmixingcontroltype = "cells", Unstained = FALSE,
ratiopopcutoff = 0.001, Verbose = FALSE,
AFOverlap = AFOverlap, stats = "median",
ExportType = "fcs", SignatureReturnNow = FALSE,
outpath = StorageLocation, Increments=0.1,
SecondaryPeaks=2, experiment = "UnstainedSignature",
condition = "Test", SCData="subtracted",
NegativeType="default", BeadAF=UnstainedBeads,
BeadMainAF="UV1-A")
Conclusion
You have made it through the most complicated of the
Luciernaga
package functions. I hope this helps explain the
function that makes the next two vignettes possible when scaled to
process all the specimens within the GatingSet. Congratulations!
#> R version 4.4.1 (2024-06-14 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 22631)
#>
#> Matrix products: default
#>
#>
#> locale:
#> [1] LC_COLLATE=English_United States.utf8
#> [2] LC_CTYPE=English_United States.utf8
#> [3] LC_MONETARY=English_United States.utf8
#> [4] LC_NUMERIC=C
#> [5] LC_TIME=English_United States.utf8
#>
#> time zone: America/New_York
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] htmltools_0.5.8.1 plotly_4.10.4 gt_0.11.1
#> [4] stringr_1.5.1 purrr_1.0.2 dplyr_1.1.4
#> [7] data.table_1.16.2 ggcyto_1.32.0 ncdfFlow_2.50.0
#> [10] BH_1.84.0-0 ggplot2_3.5.1 openCyto_2.16.1
#> [13] flowWorkspace_4.16.0 flowCore_2.16.0 Luciernaga_0.99.1
#> [16] BiocStyle_2.32.1
#>
#> loaded via a namespace (and not attached):
#> [1] RBGL_1.80.0 gridExtra_2.3 rlang_1.1.4
#> [4] magrittr_2.0.3 matrixStats_1.4.1 ggridges_0.5.6
#> [7] compiler_4.4.1 dir.expiry_1.12.0 png_0.1-8
#> [10] systemfonts_1.1.0 vctrs_0.6.5 reshape2_1.4.4
#> [13] pkgconfig_2.0.3 fastmap_1.2.0 labeling_0.4.3
#> [16] utf8_1.2.4 rmarkdown_2.28 graph_1.82.0
#> [19] ragg_1.3.3 xfun_0.48 zlibbioc_1.50.0
#> [22] cachem_1.1.0 jsonlite_1.8.9 SnowballC_0.7.1
#> [25] highr_0.11 parallel_4.4.1 R6_2.5.1
#> [28] bslib_0.8.0 stringi_1.8.4 RColorBrewer_1.1-3
#> [31] reticulate_1.39.0 lubridate_1.9.3 jquerylib_0.1.4
#> [34] figpatch_0.2 Rcpp_1.0.13 bookdown_0.41
#> [37] knitr_1.48 zoo_1.8-12 Matrix_1.7-0
#> [40] timechange_0.3.0 tidyselect_1.2.1 rstudioapi_0.17.0
#> [43] yaml_2.3.10 viridis_0.6.5 lattice_0.22-6
#> [46] tibble_3.2.1 plyr_1.8.9 Biobase_2.64.0
#> [49] basilisk.utils_1.16.0 withr_3.0.1 evaluate_1.0.1
#> [52] Rtsne_0.17 desc_1.4.3 xml2_1.3.6
#> [55] pillar_1.9.0 lsa_0.73.3 BiocManager_1.30.25
#> [58] filelock_1.0.3 stats4_4.4.1 generics_0.1.3
#> [61] S4Vectors_0.42.1 munsell_0.5.1 scales_1.3.0
#> [64] glue_1.8.0 lazyeval_0.2.2 tools_4.4.1
#> [67] hexbin_1.28.4 fs_1.6.4 XML_3.99-0.17
#> [70] grid_4.4.1 flowClust_3.42.0 tidyr_1.3.1
#> [73] RProtoBufLib_2.16.0 crosstalk_1.2.1 colorspace_2.1-1
#> [76] patchwork_1.3.0 basilisk_1.16.0 cli_3.6.3
#> [79] textshaping_0.4.0 fansi_1.0.6 cytolib_2.16.0
#> [82] viridisLite_0.4.2 uwot_0.2.2 Rgraphviz_2.48.0
#> [85] gtable_0.3.5 sass_0.4.9 digest_0.6.37
#> [88] BiocGenerics_0.50.0 htmlwidgets_1.6.4 farver_2.1.2
#> [91] pkgdown_2.1.1 lifecycle_1.0.4 httr_1.4.7