Luciernaga Integration

For the YouTube recording, see here
For screen-shot slides, click here
Background
In the previous bonus walk-through, we generated signature matrices for both our bead and cell single-color unmixing controls using the flowGate and Luciernaga R packages. The normalized fluorescent signatures were generated by extracting cells (or beads) that fell within our peak detector gates, taking the median measurement across each detector, subtracting out the equivalent background autofluorescence, and then normalizing (scaling) the values to range from 0 to 1. The normalized fluorescent signatures from all the fluorophores were then assembled together into a signature matrix.
Overall, this workflow is representative of the approach taken within most commercial softwares in the steps leading up to unmixing. Unfortunately, relying on a summary measurement (“median” in this case) often limits what we are able to perceive, and when it comes to the unmixing controls,there remains plenty of additional information that can be gleaned from the individual cells that are present.
During Week 12, we showcased the use of the Luciernaga packages LuciernagaIntegration() wrapper function, which permitted grouping cells based on their individual normalized cell signatures based on shared peaks, of relatively same heights.

This approach proved useful, as it allowed us to identify the tandem degradation from APC-Fire 810 to APC that was occuring in the cell single-color controls (but strangely not the bead single-color control prepared from the same antibody vial).

In this walk-through, we will take a look at the other 28-fluorophores in the panel, and identify any other frequently encountered patterns that can be found when we look at our unmixing controls with such a granular view. We will also explore how to leverage this information to better inform which signatures we incorporate into the final fluorophore signature matrix for use in unmixing the full-stained samples.
Walk Through
Set Up
Retrieving GatingSet
We will begin by loading the R packages we need today to our local environment, via the library() call. As a reminder, we will need to have at least Luciernaga version 0.99.10 installed to use the functions (for reasons covered during the last bonus walk-through).
library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
library(purrr)
library(flowWorkspace)As part of improvements to flowWorkspace, some behavior of
GatingSet objects has changed. For details, please read the section
titled "The cytoframe and cytoset classes" in the package vignette:
vignette("flowWorkspace-Introduction", "flowWorkspace")
library(Luciernaga)
library(ggplot2)Next up, specify the storage (currently containing our .fcs files) and output (currently containing our intermediate .gs folder) locations. If you are following the courses organization scheme, these would be in the “data” and “outputs” folders respectively.
#StorageLocation <- file.path("course", "SDY3080", "data", "SDY3080")
StorageLocation <- file.path("data", "SDY3080")
#OutputLocation <- file.path("course", "SDY3080", "outputs")
OutputLocation <- file.path("outputs")With these file paths set up, lets proceed to load our CellSC_GatingSet back into the local environment from its .gs folder. This would be the “Amplified” version, where the peak detector gates had been created, checked and corrected.
CellSCPath <- file.path(OutputLocation, "CellsSC_GS_Amplified.gs")
CellsSC_GatingSet <- load_gs(CellSCPath)And lets go ahead and plot the gates, just to make sure everything remains as it should be.
plot(CellsSC_GatingSet)
Assembling Template
Since LuciernagaIntegration() wrapper function relies on the use of a template for matching the individual single-color control specimen with the corresponding peak detector gate, we can reasseble the template by retrieving the updated metadata using pData(). When re-retrieving metadata from a GatingSet, the metadata columns order may end up rearranged, so lets also reorganize the columns based on the expected order we previously had them in.
Template <- pData(CellsSC_GatingSet)
Template <- Template |> relocate(Fluorophore, Antigen, Type, Detector, Negative, .after="name")rownames(Template) <- NULL
head(Template, 3) name
1 DTR_2023_ILT_01-Reference Group-DR_CCR4 BUV615 (Cells).1235678.fcs
2 DTR_2023_ILT_01-Reference Group-DR_CCR6 BV786 (Cells).1235679.fcs
3 DTR_2023_ILT_01-Reference Group-DR_CCR7 BV650 (Cells).1235680.fcs
Fluorophore Antigen Type Detector Negative
1 BUV615 CCR4 Cells UV10 PBMC_Unstained
2 BV786 CCR6 Cells V15 PBMC_Unstained
3 BV650 CCR7 Cells V11 PBMC_Unstained
We can also optionally remove the 3 samples that we previously choose not to investigate using the dplyr packages filter() function on the data.frame object. We can save this subsetted output as a new object/variable, “KeptCells”.
KeptCells <- Template |>
filter(!name %in% "DTR_2023_ILT_01-Reference Group-DR_Dump_CD19 Pacific Blue (Cells).1235702.fcs") |>
filter(!name %in% "DTR_2023_ILT_01-Reference Group-DR_Viability_PMA Zombie NIR (Cells).1235709.fcs") |>
filter(!name %in% "DTR_2023_ILT_01-Reference Group-DR_CD3 Alexa Fluor 488 (Cells).1235688.fcs") KeptCells name
1 DTR_2023_ILT_01-Reference Group-DR_CCR4 BUV615 (Cells).1235678.fcs
2 DTR_2023_ILT_01-Reference Group-DR_CCR6 BV786 (Cells).1235679.fcs
3 DTR_2023_ILT_01-Reference Group-DR_CCR7 BV650 (Cells).1235680.fcs
4 DTR_2023_ILT_01-Reference Group-DR_CD107a APC-R700 (Cells).1235681.fcs
5 DTR_2023_ILT_01-Reference Group-DR_CD127 BV421 (Cells).1235682.fcs
6 DTR_2023_ILT_01-Reference Group-DR_CD16 APC (Cells).1235683.fcs
7 DTR_2023_ILT_01-Reference Group-DR_CD161 BV480 (Cells).1235684.fcs
8 DTR_2023_ILT_01-Reference Group-DR_CD25 PE-Cy5 (Cells).1235685.fcs
9 DTR_2023_ILT_01-Reference Group-DR_CD26 PerCP-Cy5.5 (Cells).1235686.fcs
10 DTR_2023_ILT_01-Reference Group-DR_CD27 APC-Fire 750 (Cells).1235687.fcs
11 DTR_2023_ILT_01-Reference Group-DR_CD3 Alexa Fluor 647 (Cells).1235689.fcs
12 DTR_2023_ILT_01-Reference Group-DR_CD3 FITC (Cells).1235690.fcs
13 DTR_2023_ILT_01-Reference Group-DR_CD3 Spark Blue 550 (Cells).1235691.fcs
14 DTR_2023_ILT_01-Reference Group-DR_CD38 APC-Fire 810 (Cells).1235692.fcs
15 DTR_2023_ILT_01-Reference Group-DR_CD4 BUV805 (Cells).1235693.fcs
16 DTR_2023_ILT_01-Reference Group-DR_CD45RA BV510 (Cells).1235694.fcs
17 DTR_2023_ILT_01-Reference Group-DR_CD56 BV605 (Cells).1235695.fcs
18 DTR_2023_ILT_01-Reference Group-DR_CD62L BUV395 (Cells).1235696.fcs
19 DTR_2023_ILT_01-Reference Group-DR_CD69 BUV563 (Cells).1235697.fcs
20 DTR_2023_ILT_01-Reference Group-DR_CD7 BV711 (Cells).1235698.fcs
21 DTR_2023_ILT_01-Reference Group-DR_CD8 BUV496 (Cells).1235699.fcs
22 DTR_2023_ILT_01-Reference Group-DR_CXCR3 BUV737 (Cells).1235700.fcs
23 DTR_2023_ILT_01-Reference Group-DR_Dump_CD14 Pacific Blue (Cells).1235701.fcs
24 DTR_2023_ILT_01-Reference Group-DR_IFNg BV750 (Cells).1235703.fcs
25 DTR_2023_ILT_01-Reference Group-DR_NKG2D PE (Cells).1235704.fcs
26 DTR_2023_ILT_01-Reference Group-DR_PD1 PE-Vio770 (Cells).1235705.fcs
27 DTR_2023_ILT_01-Reference Group-DR_TNFa PE-Dazzle594 (Cells).1235706.fcs
28 DTR_2023_ILT_01-Reference Group-DR_VD2 BUV661 (Cells).1235707.fcs
29 DTR_2023_ILT_01-Reference Group-DR_Viability Zombie NIR (Cells).1235708.fcs
Fluorophore Antigen Type Detector Negative
1 BUV615 CCR4 Cells UV10 PBMC_Unstained
2 BV786 CCR6 Cells V15 PBMC_Unstained
3 BV650 CCR7 Cells V11 PBMC_Unstained
4 APC-R700 CD107a Cells R4 PBMC_Activated_Unstained
5 BV421 CD127 Cells V1 PBMC_Unstained
6 APC CD16 Cells R1 CBMC_Unstained
7 BV480 CD161 Cells V5 PBMC_Unstained
8 PE-Cy5 CD25 Cells YG5 PBMC_Activated_Unstained
9 PerCP-Cy5.5 CD26 Cells B9 PBMC_Unstained
10 APC-Fire 750 CD27 Cells R7 PBMC_Unstained
11 Alexa Fluor 647 CD3 Cells R2 PBMC_Unstained
12 FITC CD3 Cells B2 PBMC_Unstained
13 Spark Blue 550 CD3 Cells B3 PBMC_Unstained
14 APC-Fire 810 CD38 Cells R8 CBMC_Unstained
15 BUV805 CD4 Cells UV16 PBMC_Unstained
16 BV510 CD45RA Cells V7 CBMC_Unstained
17 BV605 CD56 Cells V10 CBMC_Unstained
18 BUV395 CD62L Cells UV2 PBMC_Unstained
19 BUV563 CD69 Cells UV9 PBMC_Activated_Unstained
20 BV711 CD7 Cells V13 PBMC_Unstained
21 BUV496 CD8 Cells UV7 CBMC_Unstained
22 BUV737 CXCR3 Cells UV14 PBMC_Unstained
23 Pacific Blue Dump_CD14 Cells V3 PBMC_Monocyte_Unstained
24 BV750 IFNg Cells V14 PBMC_Activated_Unstained
25 PE NKG2D Cells YG1 PBMC_Unstained
26 PE-Vio 770 PD1 Cells YG9 CBMC_Unstained
27 PE-Dazzle 594 TNFa Cells YG3 PBMC_Activated_Unstained
28 BUV661 VD2 Cells UV11 PBMC_Unstained
29 Zombie NIR Viability Cells R6 CBMC_Dead_Unstained
Rearranging by Detector
Before diving too far in, you may notice the rows in our current template are not exactly ordered in terms of the laser and emission wavelength of the respective fluorophores. We can fix this setting factor() on the detector column (providing the correct detector order via the levels argument). At that point, we can use the dplyr packages arrange() function to re-shuffle the rows to match the designated factor ordering.
DetectorOrder <- c("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")
KeptCells$Detector <- factor(KeptCells$Detector, levels=DetectorOrder)
KeptCells <- KeptCells |> arrange(Detector)KeptCells name
1 DTR_2023_ILT_01-Reference Group-DR_CD62L BUV395 (Cells).1235696.fcs
2 DTR_2023_ILT_01-Reference Group-DR_CD8 BUV496 (Cells).1235699.fcs
3 DTR_2023_ILT_01-Reference Group-DR_CD69 BUV563 (Cells).1235697.fcs
4 DTR_2023_ILT_01-Reference Group-DR_CCR4 BUV615 (Cells).1235678.fcs
5 DTR_2023_ILT_01-Reference Group-DR_VD2 BUV661 (Cells).1235707.fcs
6 DTR_2023_ILT_01-Reference Group-DR_CXCR3 BUV737 (Cells).1235700.fcs
7 DTR_2023_ILT_01-Reference Group-DR_CD4 BUV805 (Cells).1235693.fcs
8 DTR_2023_ILT_01-Reference Group-DR_CD127 BV421 (Cells).1235682.fcs
9 DTR_2023_ILT_01-Reference Group-DR_Dump_CD14 Pacific Blue (Cells).1235701.fcs
10 DTR_2023_ILT_01-Reference Group-DR_CD161 BV480 (Cells).1235684.fcs
11 DTR_2023_ILT_01-Reference Group-DR_CD45RA BV510 (Cells).1235694.fcs
12 DTR_2023_ILT_01-Reference Group-DR_CD56 BV605 (Cells).1235695.fcs
13 DTR_2023_ILT_01-Reference Group-DR_CCR7 BV650 (Cells).1235680.fcs
14 DTR_2023_ILT_01-Reference Group-DR_CD7 BV711 (Cells).1235698.fcs
15 DTR_2023_ILT_01-Reference Group-DR_IFNg BV750 (Cells).1235703.fcs
16 DTR_2023_ILT_01-Reference Group-DR_CCR6 BV786 (Cells).1235679.fcs
17 DTR_2023_ILT_01-Reference Group-DR_CD3 FITC (Cells).1235690.fcs
18 DTR_2023_ILT_01-Reference Group-DR_CD3 Spark Blue 550 (Cells).1235691.fcs
19 DTR_2023_ILT_01-Reference Group-DR_CD26 PerCP-Cy5.5 (Cells).1235686.fcs
20 DTR_2023_ILT_01-Reference Group-DR_NKG2D PE (Cells).1235704.fcs
21 DTR_2023_ILT_01-Reference Group-DR_TNFa PE-Dazzle594 (Cells).1235706.fcs
22 DTR_2023_ILT_01-Reference Group-DR_CD25 PE-Cy5 (Cells).1235685.fcs
23 DTR_2023_ILT_01-Reference Group-DR_PD1 PE-Vio770 (Cells).1235705.fcs
24 DTR_2023_ILT_01-Reference Group-DR_CD16 APC (Cells).1235683.fcs
25 DTR_2023_ILT_01-Reference Group-DR_CD3 Alexa Fluor 647 (Cells).1235689.fcs
26 DTR_2023_ILT_01-Reference Group-DR_CD107a APC-R700 (Cells).1235681.fcs
27 DTR_2023_ILT_01-Reference Group-DR_Viability Zombie NIR (Cells).1235708.fcs
28 DTR_2023_ILT_01-Reference Group-DR_CD27 APC-Fire 750 (Cells).1235687.fcs
29 DTR_2023_ILT_01-Reference Group-DR_CD38 APC-Fire 810 (Cells).1235692.fcs
Fluorophore Antigen Type Detector Negative
1 BUV395 CD62L Cells UV2 PBMC_Unstained
2 BUV496 CD8 Cells UV7 CBMC_Unstained
3 BUV563 CD69 Cells UV9 PBMC_Activated_Unstained
4 BUV615 CCR4 Cells UV10 PBMC_Unstained
5 BUV661 VD2 Cells UV11 PBMC_Unstained
6 BUV737 CXCR3 Cells UV14 PBMC_Unstained
7 BUV805 CD4 Cells UV16 PBMC_Unstained
8 BV421 CD127 Cells V1 PBMC_Unstained
9 Pacific Blue Dump_CD14 Cells V3 PBMC_Monocyte_Unstained
10 BV480 CD161 Cells V5 PBMC_Unstained
11 BV510 CD45RA Cells V7 CBMC_Unstained
12 BV605 CD56 Cells V10 CBMC_Unstained
13 BV650 CCR7 Cells V11 PBMC_Unstained
14 BV711 CD7 Cells V13 PBMC_Unstained
15 BV750 IFNg Cells V14 PBMC_Activated_Unstained
16 BV786 CCR6 Cells V15 PBMC_Unstained
17 FITC CD3 Cells B2 PBMC_Unstained
18 Spark Blue 550 CD3 Cells B3 PBMC_Unstained
19 PerCP-Cy5.5 CD26 Cells B9 PBMC_Unstained
20 PE NKG2D Cells YG1 PBMC_Unstained
21 PE-Dazzle 594 TNFa Cells YG3 PBMC_Activated_Unstained
22 PE-Cy5 CD25 Cells YG5 PBMC_Activated_Unstained
23 PE-Vio 770 PD1 Cells YG9 CBMC_Unstained
24 APC CD16 Cells R1 CBMC_Unstained
25 Alexa Fluor 647 CD3 Cells R2 PBMC_Unstained
26 APC-R700 CD107a Cells R4 PBMC_Activated_Unstained
27 Zombie NIR Viability Cells R6 CBMC_Dead_Unstained
28 APC-Fire 750 CD27 Cells R7 PBMC_Unstained
29 APC-Fire 810 CD38 Cells R8 CBMC_Unstained
An added benefit of this means we can now use R notation for rows ([1,], [2,], etc.) to sequentially isolate out from the panel GatingSet just the individual fluorophores we want to work with by providing smaller versions of the actual template.
KeptCells[1,] name
1 DTR_2023_ILT_01-Reference Group-DR_CD62L BUV395 (Cells).1235696.fcs
Fluorophore Antigen Type Detector Negative
1 BUV395 CD62L Cells UV2 PBMC_Unstained
KeptCells[29,] name
29 DTR_2023_ILT_01-Reference Group-DR_CD38 APC-Fire 810 (Cells).1235692.fcs
Fluorophore Antigen Type Detector Negative
29 APC-Fire 810 CD38 Cells R8 CBMC_Unstained
Cell Single-Color Controls
Before getting started, lets quickly recall which fluorophores that are present in this 29-fluorophore panel
Panel <- KeptCells |> select(Fluorophore, Antigen) |> mutate(Day=1)
Table <- FluorophoreMatrix(data=Panel, NumberDetectors = 64) # Aurora 5-laserTable| D | WV | Fluorophore | Antigen | D | Fluorophore | Antigen | D | Fluorophore | Antigen | D | Fluorophore | Antigen | D | Fluorophore | Antigen |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| UV1 | 373 | ||||||||||||||
| UV2 | 388 | BUV395 | CD62L | ||||||||||||
| UV3 | 428 | V1 | BV421 | CD127 | |||||||||||
| UV4 | 443 | V2 | |||||||||||||
| UV5 | 458 | V3 | Pacific Blue | Dump_CD14 | |||||||||||
| UV6 | 473 | V4 | |||||||||||||
| 508 | V5 | BV480 | CD161 | B1 | |||||||||||
| UV7 | 514 | BUV496 | CD8 | ||||||||||||
| 525 | V6 | B2 | FITC | CD3 | |||||||||||
| UV8 | 542 | V7 | BV510 | CD45RA | B3 | Spark Blue 550 | CD3 | ||||||||
| 577 | YG1 | PE | NKG2D | ||||||||||||
| 581 | V8 | B4 | |||||||||||||
| UV9 | 582 | BUV563 | CD69 | ||||||||||||
| 598 | V9 | B5 | YG2 | ||||||||||||
| UV10 | 613 | BUV615 | CCR4 | ||||||||||||
| 615 | V10 | BV605 | CD56 | B6 | YG3 | PE-Dazzle 594 | TNFa | ||||||||
| UV11 | 662 | BUV661 | VD2 | V11 | BV650 | CCR7 | B7 | YG4 | R1 | APC | CD16 | ||||
| 679 | B8 | YG5 | PE-Cy5 | CD25 | R2 | Alexa Fluor 647 | CD3 | ||||||||
| UV12 | 695 | V12 | B9 | PerCP-Cy5.5 | CD26 | YG6 | R3 | ||||||||
| UV13 | 718 | V13 | BV711 | CD7 | B10 | YG7 | R4 | APC-R700 | CD107a | ||||||
| 738 | B11 | R5 | |||||||||||||
| UV14 | 750 | BUV737 | CXCR3 | V14 | BV750 | IFNg | YG8 | ||||||||
| 760 | B12 | R6 | Zombie NIR | Viability | |||||||||||
| UV15 | 781 | V15 | BV786 | CCR6 | B13 | YG9 | PE-Vio 770 | PD1 | R7 | APC-Fire 750 | CD27 | ||||
| UV16 | 812 | BUV805 | CD4 | V16 | B14 | YG10 | R8 | APC-Fire 810 | CD38 |
As we start to evaluate fluorophore signatures, an important thing to remember is that what we (and our cytometers) perceive as brightness is a combination of
The antigen density on the cell surface that our antibody-conjugated fluorophores will bind to.
The individual fluorophores capacity to emit photons back when the respective laser excites it (as well as our instruments ability to capture this emmitted signal)
Some variable amount of background autofluorescence (from either our cell or bead)
Some additional amount of uncertainty or noise for good measure from various sources.
As we consider signature variants while profiling the normalized fluorescent signatures of individual cells, its often useful to try to mentually contextualize what we are seeing in light of these factors listed above. We will revisit these points in finer detail as we navigate through this walk-through. But for now, lets get started!
Ultra Violet

BUV395
Lets start with our BUV395 single-color unmixing control, which is conjugated to CD62L.
Since it is located in the first row of our “data.frame”, we can subset it out from the broader “KeptCells” template using “[1,]”. We can pass this subsetted template alongside our GatingSet object to the LuciernagaIntegration() wrapper function for processing.
BUV395 <- LuciernagaIntegration(template=KeptCells[1,],
gs=CellsSC_GatingSet, GuessSimilar=TRUE)[[1]] # Square brackets to loose list notationOnly a single peak detector present.
No second peak
No second peak
Normalizing Data for Signature Comparison
The LuciernagaIntegration() function in turn returns to us a list object.
class(BUV395)[1] "list"
If we run names(), we can see the actual contents appear to be a mix of both “data.frames” and “ggplot plots”, corresponding to the various Luciernaga functions that were run by the wrapper.
names(BUV395) [1] "LuciernagaQC_Data" "LuciernagaQC_Signatures"
[3] "AveragedSignature_Data" "LuciernagaQC_AmalgamatedPlot"
[5] "GuessSimilar_Data" "GuessSimilar_Plot"
[7] "ProportionPlot" "CosineSimilarityPlot"
[9] "LinearSlices_Data" "LinearSlices_Plot"
This allows us to quickly visualize via the different options, while retaining the ability to subsequently retrieve the underlying data for subsequent downstream analysis steps.
We can start by retrieving the output of Luciernaga_LinearSlices(), which is stored under the “LinearSlices_Plot” entry. This function splits the gated region into 10% bins across, and returns the averaged normalized signature for the cells that are present within each of the bins. This plot in turn can be passed to plotly’s ggplotly() function to render it interactive.
Plot <- BUV395$LinearSlices_Plot
plotly::ggplotly(Plot)Across the board, it doesn’t appear that the normalized signature has changed substantially, as looking across the various bins the signatures appear mostly consistent.
Having noted this, we can next check the stored plot present under “LuciernagaQC_Signatures”, which is the wrapped return from the LuciernagaQC() function that orchestrates the grouping of cells based on their individual normalized signatures based on peak detector location and relative height.
Plot <- BUV395$LuciernagaQC_Signatures
plotly::ggplotly(Plot)Using this approach, we find the same signature that was seen by the prior approach (“UV2_10”, consisting of a single emission peak located at the “UV2” detector, i.e. normalized height of 1.00). However, we also retrieved a couple additional signature variants, with the presence of secondary peaks at the “V7” detector, that are of different heights relative to the peak “UV2” detector (“V7_02” ~ 20% height of UV2, “V7_03” ~ 30% height of UV2).
Given the location (“V7-A”), it is likely that this new peak in these two variant signatures is residual autofluorescence that wasn’t fully subtracted out for their individual cells.
While we can see there are variant signatures with residual autofluorescence present, this doesn’t tell us much information about how prevalent they might be. Within our returned list, we also have a “ProportionPlot” that tells us how the cells within our gated region where distributed between the signature variants.
Plot <- BUV395$ProportionPlot
plotly::ggplotly(Plot)In this case, around 80% of the cells had the standard signature, while 20% of the cells were better represented by the other two variant signatures. Within our LuciernagaIntegration() output list we also get back the QC_Amalgamate() functions output under the “LuciernagaQC_AmalgamatedPlot” entry. This will visualize all the signature variants, while also showing where the overall median signature would end up.
Plot <- BUV395$LuciernagaQC_AmalgamatedPlot
plotly::ggplotly(Plot)As we can see, in this case the “Average” and “UV2_10” signatures are practically indistinguishable, with the contribution of the variant signatures not affecting the overall shape of the signature.
Going back to the list, lets retrieve the underlying data for our plot (“AveragedSignature_Data”)
Data <- BUV395$AveragedSignature_Data
Data Cluster UV1-A UV2-A UV3-A UV4-A UV5-A UV6-A
1 UV2_10 0.2513256 1 0.4821444 0.3425974 0.3085460 0.2839456
2 UV2_10-V7_02 0.2548048 1 0.5012830 0.3707769 0.3268080 0.3237033
3 UV2_10-V7_03 0.2885756 1 0.4999609 0.3825903 0.3427424 0.3435264
4 Average 0.2513256 1 0.4821444 0.3425974 0.3085460 0.2839456
UV7-A UV8-A UV9-A UV10-A UV11-A UV12-A UV13-A
1 0.1504531 0.04285274 0.01264474 0.00106715 0.00000000 0.000000000 0.000000000
2 0.2223727 0.09474590 0.06470829 0.02349097 0.01225132 0.009411968 0.004118347
3 0.2744997 0.14037336 0.09838418 0.03500394 0.02968772 0.013867222 0.014231738
4 0.1504531 0.04285274 0.01264474 0.00106715 0.00000000 0.000000000 0.000000000
UV14-A UV15-A UV16-A V1-A V2-A V3-A
1 0.000000000 0.000000000 0.001904033 0.00000000 0.00000000 0.00000000
2 0.005792459 0.007751806 0.009691300 0.01323558 0.04533202 0.08073928
3 0.001079741 0.015705771 0.008887963 0.01977124 0.07096450 0.11201363
4 0.000000000 0.000000000 0.001904033 0.00000000 0.00000000 0.00000000
V4-A V5-A V6-A V7-A V8-A V9-A V10-A
1 0.00000000 0.0000000 0.0000000 0.0000000 0.0000000 0.00000000 0.0000000
2 0.08540567 0.1154807 0.1033242 0.1410537 0.1106576 0.07620757 0.0890226
3 0.14628798 0.1901788 0.1776486 0.2496261 0.1850824 0.12994337 0.1618359
4 0.00000000 0.0000000 0.0000000 0.0000000 0.0000000 0.00000000 0.0000000
V11-A V12-A V13-A V14-A V15-A V16-A B1-A
1 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
2 0.04759511 0.02587157 0.03176452 0.02408440 0.02412867 0.01027153 0.01992130
3 0.10044330 0.05597605 0.06323827 0.04389936 0.04613288 0.01858664 0.03988598
4 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
B2-A B3-A B4-A B5-A B6-A B7-A B8-A
1 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
2 0.02845729 0.04131444 0.03537153 0.02889839 0.03063174 0.02716681 0.01375170
3 0.05406482 0.07758813 0.06496203 0.05676359 0.05033631 0.04127038 0.03316218
4 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
B9-A B10-A B11-A B12-A B13-A B14-A
1 0.00000000 0.00000000 0.000000000 0.000000000 0.000000000 0.000000000
2 0.01803278 0.01120053 0.004392688 0.007137394 0.003642796 0.008279564
3 0.02857610 0.02306650 0.028355718 0.012135322 0.006072685 0.012754377
4 0.00000000 0.00000000 0.000000000 0.000000000 0.000000000 0.000000000
YG1-A YG2-A YG3-A YG4-A YG5-A YG6-A YG7-A
1 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
2 0.01536727 0.02351758 0.03763038 0.02716086 0.01965088 0.01448276 0.02351382
3 0.02841385 0.04694359 0.06462970 0.04709900 0.03865613 0.04273837 0.03606050
4 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000 0.00000000
YG8-A YG9-A YG10-A R1-A R2-A R3-A
1 0.00000000 0.000000000 0.000000000 0.000000000 0.000000000 0.00000000
2 0.01026488 0.002394388 0.005070468 0.006353108 0.004281728 0.00405074
3 0.01984583 0.008878654 0.004121015 0.007536607 0.005998216 0.01103197
4 0.00000000 0.000000000 0.000000000 0.000000000 0.000000000 0.00000000
R4-A R5-A R6-A R7-A R8-A
1 0.0001315737 0.000000000 0.000000000 0.000000000 0.0005800362
2 0.0045765666 0.001714569 0.005834794 0.005316479 0.0001690156
3 0.0060046888 0.004615174 0.000000000 0.000000000 0.0037542790
4 0.0001315737 0.000000000 0.000000000 0.000000000 0.0005800362
In this particular case, we have relatively few signature variants, but for practice for the upcoming fluorophores, lets retrieve the average as well as variants with minimal and maxinum autofluorescence contribution of the autofluorescence peak for subsequent signature comparison steps.
Data <- BUV395$AveragedSignature_Data |>
filter(Cluster %in% c("UV2_10", "Average", "UV2_10-V7_03"))With this accomplished, we can carry out some of the signature comparison steps we encountered during Week 13. We can start off by passing each individual signature to QC_WhatsThis() to better gage the differences between them.
QC_WhatsThis(x="UV2_10", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_UV2_10
1 BUV395 1.00
2 CellTrace Blue 0.99
3 SBUV400 0.99
4 Spark PLUS UV 395 0.99
5 Live-or-Dye 330-410 0.94
QC_WhatsThis(x="Average", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_Average
1 BUV395 1.00
2 CellTrace Blue 0.99
3 SBUV400 0.99
4 Spark PLUS UV 395 0.99
5 Live-or-Dye 330-410 0.94
QC_WhatsThis(x="UV2_10-V7_03", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_UV2_10-V7_03
1 CellTrace Blue 0.93
2 Live-or-Dye 330-410 0.93
3 BUV395 0.92
4 SBUV400 0.91
5 Spark PLUS UV 395 0.89
From the above, both “Average” and “UV2_10” (i.e. ‘single peak at UV2, height 1.0’) are matches for the “BUV395” reference signature. The cosine value drops for the “UV2_10-V7_03” (i.e. peak detector at UV2, height 1.0; second peak at V7, relative height 0.3 to main peak) signature variant, where the cosine value is 0.92
If we visualized the difference in these angles, we would see the following
Code
cos_vals <- c(1, 1, 0.92)
theta <- acos(cos_vals)
df <- data.frame(
label = paste0("cos = ", cos_vals),
angle_deg = round(theta * 180 / pi, 1),
x = sin(theta),
y = cos(theta)
)
ggplot(df) +
geom_segment(aes(x = 0, y = 0, xend = x, yend = y, color = label),
arrow = arrow(length = unit(0.25, "cm")),
linewidth = 1) +
coord_fixed(xlim = c(-0.1, 1), ylim = c(-0.1, 1.1)) +
geom_hline(yintercept = 0, color = "grey80") +
geom_vline(xintercept = 0, color = "grey80") +
labs(x = NULL, y = NULL, color = "Vector",
title = NULL) +
theme_bw()
So in this case, the variant with residual autofluorescence if utilized as the unmixing reference signature would likely result in a fairly substantial unmixing error.
80% of the cells within our gated region are a match for the BUV395 reference signature. The other 20% of cells have residual autofluorescence contribution, which can cause a shift in the overall signature. While not abundant enough to impact the overall signature, if we wanted to prevent this scenario, we could always shift our original positive gate to the right (retaining only the brighter cells) which should reduce the overall prevalence of these two signature variants.
BUV496
Next up in terms of laser and peak emission detecter we have one of the most autofluorescence similar fluorophores, BUV496, conjugated in this case to CD8. Given that CD8 is a “primary” marker, and how we placed the gate, this antigen-fluorophore combination has a decent chance of being reasonably bright.
BUV496 <- LuciernagaIntegration(template=KeptCells[2,], gs=CellsSC_GatingSet, GuessSimilar=TRUE)[[1]] # Square brackets to loose list notationOnly a single peak detector present.
Normalizing Data for Signature Comparison
Only one signature retrieved for BUV496 , Cosine not run
As was the case for the BUV395, we can start by checking the “LinearSlices_Plot”, which allows us to visualize the effect on the overall retrieved signature if we had split the region within out “positive” gate into 10% bins of increasing brightness.
Plot <- BUV496$LinearSlices_Plot
plotly::ggplotly(Plot)As we might anticipate given the antigen-fluorophore pairing (alongside best practices on gate placement), there are no significant differences in the signature retrieved across the individual 10% bins.
We can follow up by checking the LuciernagaQC() output stored in “LuciernagaQC_Signatures”, which will group individual cell normalized signatures on the basis of shared peak and height
Plot <- BUV496$LuciernagaQC_Signatures
plotly::ggplotly(Plot)At which point, we can see only one signature is present across the board. We can go ahead and call it for this fluorophore on the other comparisons.
For BUV496, only 1 normalized fluorescent signature was present in the cells within the gated region of our reference control, so no changes are needed.
BUV563
Next up, we have BUV563, which is conjugated to CD69.
One thing to keep in mind, this would be one of the single-color unmixing controls that would have been prepared with the PMA-ionomycin activated cells. The LuciernagaIntegration() wrappers default is to use the internal negative, but still worth keeping an eye on and see how it behaves compared to the previous fluorophores.
BUV563 <- LuciernagaIntegration(template=KeptCells[3,], gs=CellsSC_GatingSet, GuessSimilar=TRUE)[[1]] # Square brackets to loose list notationOnly a single peak detector present.
Normalizing Data for Signature Comparison
As was the case for the previous fluorophores, we can start by checking the “LinearSlices_Plot”, to see what the effect would have been on the signature if we had split the cells within our “positive” gate into 10% bins across.
Plot <- BUV563$LinearSlices_Plot
plotly::ggplotly(Plot)We have a similar situation as what we encountered with BUV496. Lets go ahead and check the LuciernagaQC() output to see if we see any variant signatures when we group the individual cell normalized signatures by shared peak and height
Plot <- BUV563$LuciernagaQC_Signatures
plotly::ggplotly(Plot)In this case, we get back a few variant signatures, but at first glance the differences seem minimal (secondary peak “YG2” being either 20 or 30% of the primary “UV9” peak, tertiary peak “B4” either 20 or 30% of the primary “UV9” peak). These differences are likely going to be minor, driven by how Luciernaga_QC() goes about its rounding.
Lets still carry out the comparisons for practice for the upcoming fluorophores. We can check to see what the average would have looked like by checking the output stored under “LuciernagaQC_AmalgamatedPlot”
Plot <- BUV563$LuciernagaQC_AmalgamatedPlot
plotly::ggplotly(Plot)As anticipated, minimal. How were these signatures distributed across the gated cells? We can also check the output under “ProportionPlot”
Plot <- BUV563$ProportionPlot
plotly::ggplotly(Plot)So most cells have a secondary peak “YG2_03”, with a bit of wobble on the “B4” detector. For good measure, lets go ahead and pull the “Cosine” plot.
Plot <- BUV563$CosineSimilarityPlot
plotly::ggplotly(Plot)At this point, it feels unecesary to dive further since the variants all have cosine values of 1, but we can check the “Average” signature using QC_WhatsThis() for final verification.
Data <- BUV563$AveragedSignature_DataQC_WhatsThis(x="Average", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_Average
1 BUV563 1.00
2 cFluor UV570 0.94
3 SBUV575 0.94
4 Qdot 565 0.74
5 MultiDot 580 0.62
For BUV563, we didn’t find any substantial signature variants, so our gate is likely fine as placed, and we can move on to screening the next fluorophore.
BUV615
Up next, we have BUV615, which is conjugated to CCR4. As far as classifying marker expression, this one is more on the chemokine side, so we should expect lower antigen density compared to what we saw for primary markers like BUV496 CD8.
BUV615 <- LuciernagaIntegration(template=KeptCells[4,], gs=CellsSC_GatingSet, GuessSimilar=TRUE)[[1]] # Square brackets to loose list notationOnly a single peak detector present.
Normalizing Data for Signature Comparison
First up, the “LinearSlices_Plot” output to check whether we would have retrieved any variant signatures had we split our “positive” gated region into 10% bins.
Plot <- BUV615$LinearSlices_Plot
plotly::ggplotly(Plot)Oooh look, a new pattern! While the main signature peaks are uniform across all the percentile signatures, there is step-ladder appearance around the “V5” through “V7” peaks (as well as the “UV7” detector).
With these differences noted, lets check out the LuciernagaQC() output stored under “LuciernagaQC_Signatures” to see how many variant signatures we got from the grouped individual cell normalized signatures.
Plot <- BUV615$LuciernagaQC_Signatures
plotly::ggplotly(Plot)We have unlocked our first misbehaving fluorophore! We can see that all the signature variants share the same main peak (“UV10”). They also share the secondary peak (“YG3”), although this varies in height (from 80% to 100% of the main “UV10” detector). The third shared peak height is even more variable (ranging from 20 to 60% of the main “UV10” peak). All together, this may be indicative of ongoing tandem decoupling to a certain extent.
Additionally, the signatures that have a higher tertiary “V10” peak also exhibit higher signal in the regions that are typical of lymphocyte autofluorescence (“UV7”, “V7”, “B3”). This suggest potentially that residual autofluorescence is being leftover in the signature after background subtraction.
So we have signature variants, but we don’t know how prevalent they are. Lets go ahead and check under the “LuciernagaQC_AmalgamatedPlot” output to see what the median (“Average”) signature ends up at when accounting for all the cells present in the gate
Plot <- BUV615$LuciernagaQC_AmalgamatedPlot
plotly::ggplotly(Plot)Unlike the case we saw for BUV395 CD62L, the “Average” seems to be in the middle of the signature variants for the “V10” detector, suggesting its being influenced by the variants. For comparison, lets see what the BUV615 reference signature looks like for context.
Plot <- QC_ReferenceLibrary("BUV615", returnPlots=TRUE, NumberDetectors=64)[[2]]
plotly::ggplotly(Plot)So overall, the reference has a “YG3” detector closer to 0.9, a “V10” detector closer to “0.3”, so we are retrieving signature variants positioned both below and above it in regards to the “V10” detector height. We can next filter() for the average, as well as variant signatures with minimal and maxinum heights of the tertiary “V10” peak.
Data <- BUV615$AveragedSignature_Data |>
filter(Cluster %in% c("UV10_10-YG3_09-V10_02", "Average", "UV10_10-YG3_09-V10_06"))Lets pass these individually to QC_WhatsThis() and gage the difference.
QC_WhatsThis(x="UV10_10-YG3_09-V10_02", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_UV10_10-YG3_09-V10_02
1 BUV615 1.00
2 cFluor UV610 0.86
3 SBUV605 0.86
4 Alexa Fluor 594 0.72
5 DyLight 594 0.72
QC_WhatsThis(x="Average", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_Average
1 BUV615 1.00
2 cFluor UV610 0.89
3 SBUV605 0.89
4 Alexa Fluor 594 0.74
5 DyLight 594 0.74
QC_WhatsThis(x="UV10_10-YG3_09-V10_06", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_UV10_10-YG3_09-V10_06
1 BUV615 0.90
2 cFluor UV610 0.89
3 SBUV605 0.89
4 Vio Bright V600 0.74
5 SBY605 0.73
As we may have anticipated, the signature variant “UV10_10-YG3_09-V10_06” which appeared to have the greatest amount of autofluorescence residual is the most different, coming back with a cosine value of 0.9. Lets visualize these retrieved cosines.
Code
cos_vals <- c(1, 1, 0.90)
theta <- acos(cos_vals)
df <- data.frame(
label = paste0("cos = ", cos_vals),
angle_deg = round(theta * 180 / pi, 1),
x = sin(theta),
y = cos(theta)
)
ggplot(df) +
geom_segment(aes(x = 0, y = 0, xend = x, yend = y, color = label),
arrow = arrow(length = unit(0.25, "cm")),
linewidth = 1) +
coord_fixed(xlim = c(-0.1, 1), ylim = c(-0.1, 1.1)) +
geom_hline(yintercept = 0, color = "grey80") +
geom_vline(xintercept = 0, color = "grey80") +
labs(x = NULL, y = NULL, color = "Vector",
title = NULL) +
theme_bw()
And finally, lets go ahead and check the “CosineSimilarityPlots” and the “ProportionPlot” before we make a decision
Plot <- BUV615$ProportionPlot
plotly::ggplotly(Plot)Plot <- BUV615$CosineSimilarityPlot
plotly::ggplotly(Plot)So ultimately, we have 5% of overall cells with the degraded/high-autofluorescence residual signature.
While there are still cells present that maintain BUV615 signature (and we might still rescue this control by moving the gate substantially to the right), there are clear warning signs that this particular single-color unmixing control is not in good shape, with evidence for both tandem-degradation and also residual autofluorescence contamination. Whether this is due to cell-specific degradation (like what we encountered for APC-Fire 810 during Week 12) or at the antibody vial level is something that would need to be investigated separately.
Ultimately, this was one of the antibody-fluorophore combinations for our panel that consistently caused notable unmixing issues, and it wasn’t until we switched it over to a bead single-color unmixing control that the issues cleared up, which formed the basis for our CYTO 2025 poster.
BUV661
Continuing on, we reach the BUV661 fluorophore, which was conjugated to Vδ2. For the original study, the Vγ9Vδ2 T cells cells were one the main cell subsets of interest.
While the antigen is typically expressed highly, we would normally anticipate having bright antibody-fluorophore staining. However, the Vδ2 T cell frequency can vary person to person, which means routine use in a cell single-color unmixing control requires pre-screening donors for one with high enough percentage to make acquisition sufficient bright events feasible.
BUV661 <- LuciernagaIntegration(template=KeptCells[5,], gs=CellsSC_GatingSet, GuessSimilar=TRUE)[[1]] # Square brackets to loose list notationOnly a single peak detector present.
Normalizing Data for Signature Comparison
As usual, we can check the “LinearSlices_Plot”, to see what the effect would have been on the signature if we had split the “positive” gate into 10% bins across.
Plot <- BUV661$LinearSlices_Plot
plotly::ggplotly(Plot)So far, minimal differences across the percentile bins. Lets isntead check the LuciernagaQC() output to see if there are any signature variants retrieved when we group individual cell normalized signatures based on shared peak and height.
Plot <- BUV661$LuciernagaQC_Signatures
plotly::ggplotly(Plot)Two signature variants, with slightly different R1/R2 peak heights. When taking the median, where does the “Average” signature end up?
Plot <- BUV661$LuciernagaQC_AmalgamatedPlot
plotly::ggplotly(Plot)“Average” ends up closer to the signature with the secondary “R2_09” peak. So overall, this antigen-fluorophore combination appears to be behaving. We can wrap up by checking the cosine values.
Data <- BUV661$AveragedSignature_DataPlots <- QC_WhatsThis(x="UV11_10-R2_08-YG5_04", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=TRUE)
Plots[[1]] Fluorophore ID_UV11_10-R2_08-YG5_04
1 BUV661 1.00
2 SBR670 0.92
3 cFluor UV670 0.85
4 SBUV665 0.85
5 APC 0.80
Plots <- QC_WhatsThis(x="UV11_10-R2_09-YG5_04", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=TRUE)
Plots[[1]] Fluorophore ID_UV11_10-R2_09-YG5_04
1 BUV661 1.00
2 SBR670 0.93
3 cFluor UV670 0.85
4 SBUV665 0.85
5 APC 0.81
plotly::ggplotly(Plots[[2]])For our BUV661, only two signature variants, both which match the reference fluorophore (with minor difference in the secondary “R2” peaks height). This minor difference may be due to difference in tandem batch conjugation or instrumental factors, but as long as we are using the same antibody on our full-stained samples as we do our single-color control, we should be okay.
BUV737
Up next, we have BUV737, conjugated to CXCR3. Given the ligand, the antigen-fluorophore combination may be expected to have some brightness issues similar to what we saw for BUV615 CCR4
BUV737 <- LuciernagaIntegration(template=KeptCells[6,], gs=CellsSC_GatingSet, GuessSimilar=TRUE)[[1]] # Square brackets to loose list notationNormalizing Data for Signature Comparison
Lets first take a look at the “LinearSlices_Plot”, to see if there would have been an effect on the signature if we had split the “positive” gated region across 10% bins.
Plot <- BUV737$LinearSlices_Plot
plotly::ggplotly(Plot)Right off, we notice patterns similar to what we saw for BUV615 CCR4, with the main fluorophore peaks (“UV14” and “R5”) being shared across 10% bins, but uncertainty ladden step-ladder peaks for “V7”, “B3” and “UV7” detectors.
Lets break out LuciernagaQC() output and see what the grouped individual cell normalized signature variants are up to.
Plot <- BUV737$LuciernagaQC_Signatures
plotly::ggplotly(Plot)We have a fuzzy caterpillar! At this point, we are getting back way to many signature variants, many of these not well defined (likely due to insufficient number events) which contributes to the fuzzy appearance.

Lets see if we can at least make out the main signature appearance by checking the QC_AmalgamatedPlot() output
Plot <- BUV737$LuciernagaQC_AmalgamatedPlot
plotly::ggplotly(Plot)While “UV14” appears to be fairly shared, “R5” and “B11” appear to be wildly variable in terms of height. This is typical either in situations where the antibody didn’t really stain well (ending up with very dim signal, mainly consistent of non-specific staining), or major tandem degradation.
Let’s check “ProportionPlot” and identify the signatures that represent the majority of the cells present
Plot <- BUV737$ProportionPlot
plotly::ggplotly(Plot)Not particularly helpful, but if we zoom in, we can find “UV14_10-R5_06-B11_03”, “UV14_10-R5_05-B11_03”, “UV14_10-R5_04-B11_03”, “UV14_10-R5_04-B11_02” and “UV14_10-R5_03-B11_02” have more than 5% of total cells each.
Lets retrieve the underlying data for this plot, and select for these.
Data <- BUV737$AveragedSignature_Data |>
filter(Cluster %in% c("UV14_10-R5_06-B11_03","UV14_10-R5_05-B11_03", "UV14_10-R5_04-B11_03", "UV14_10-R5_03-B11_02", "Average"))Lets pass these individually to QC_WhatsThis() and gage the difference.
QC_WhatsThis(x="UV14_10-R5_04-B11_03", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) # 0.92 Fluorophore ID_UV14_10-R5_04-B11_03
1 BUV737 0.92
2 cFluor UV745 0.82
3 SBUV740 0.82
4 SBR715 0.58
5 cFluor UV790 0.57
QC_WhatsThis(x="Average", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) # 0.97 Fluorophore ID_Average
1 BUV737 0.97
2 cFluor UV745 0.86
3 SBUV740 0.86
4 SBR775 0.61
5 cFluor UV790 0.60
QC_WhatsThis(x="UV14_10-R5_06-B11_03", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) # 0.98 Fluorophore ID_UV14_10-R5_06-B11_03
1 BUV737 0.98
2 cFluor UV745 0.90
3 SBUV740 0.90
4 SBR775 0.59
5 cFluor UV790 0.56
Overall, for these more prevalent signatures, none match the BUV737 reference, ranging from 0.92 to 1.0 in terms of their cosine. Lets plot these angles for reference
Code
cos_vals <- c(1, 0.92, 0.97, 0.98)
theta <- acos(cos_vals)
df <- data.frame(
label = paste0("cos = ", cos_vals),
angle_deg = round(theta * 180 / pi, 1),
x = sin(theta),
y = cos(theta)
)
ggplot(df) +
geom_segment(aes(x = 0, y = 0, xend = x, yend = y, color = label),
arrow = arrow(length = unit(0.25, "cm")),
linewidth = 1) +
coord_fixed(xlim = c(-0.1, 1), ylim = c(-0.1, 1.1)) +
geom_hline(yintercept = 0, color = "grey80") +
geom_vline(xintercept = 0, color = "grey80") +
labs(x = NULL, y = NULL, color = "Vector",
title = NULL) +
theme_bw()
Ultimately, this just looks like a mess. Whether its due to a dim antigen-fluorophore combinations or tandem degradation is a story for another day.
For BUV737, this one is an immediate NO! The sheer number of variants with different heights around the secondary and tertiary peaks suggest that the tandem likely degraded.
Likewise, the step-ladders around the autofluorescence typical peaks result when the antigen-fluorophore combination is not bright enough, resulting in leftover autofluorescence residuals after background subtraction contributing significantly more to the signature shape.
This is also consistent with tandem degradation scenario, since the peaks would not be as bright as they should be to render this leftover residual insignificant compared to the overall signature.
Additionally, we can’t rule out that the antibody clone could also be a bad one, since if there was no binding to the antigen, there would no direct staining, and what we could be gating on might just be the result of non-specific noise, further adding to the dim signal/overall fuzzyness.
Ultimately, replacing this cell unmixing control for its bead single-color unmixing control is no brainer, and we should check the full-stained sample to see if there is CXCR3 staining after unmixing to determine whether the antibody clone is the root cause or not.
BUV805
And wrapping up our exploration of the UV laser fluorophores, lets check out the BUV805 fluorophore, which is conjugated to CD4. Given antigen density on a CD4 T cell, this should prove less complicated than the mess that was BUV737 CXCR3
BUV805 <- LuciernagaIntegration(template=KeptCells[7,], gs=CellsSC_GatingSet, GuessSimilar=TRUE)[[1]] # Square brackets to loose list notationOnly a single peak detector present.
Normalizing Data for Signature Comparison
Only one signature retrieved for BUV805 , Cosine not run
Lets first take a look at the “LinearSlices_Plot”, to see what effect would have been on the signature if we had split the cells within our “positive” gate into 10% bins across based on brightness.
Plot <- BUV805$LinearSlices_Plot
plotly::ggplotly(Plot)Pretty much no difference on the autofluorescence detectors, so very similar to what we saw for BUV496 CD8. Lets check out the LuciernagaQC() output to see what happens when we group individual cell normalized signatures based on shared peaks of similar height
Plot <- BUV805$LuciernagaQC_Signatures
plotly::ggplotly(Plot)And we end up with just 1 signature. Lets wrap this up by comparing to the reference.
Data <- BUV805$AveragedSignature_Data QC_WhatsThis(x="Average", columnname="Cluster", data=Data, NumberHits=5, NumberDetectors=64, returnPlots=FALSE) Fluorophore ID_Average
1 BUV805 1.00
2 cFluor UV790 0.92
3 SBUV795 0.92
4 Qdot 800 0.55
5 SBR815 0.55
For BUV805, we only retrieved 1 signature, likely due to the antigen-fluorophore combination being a bright one, and the tandem being in good shape. We are good to proceed to the next fluorophore!
Violet

Due to the sheer number of plotly ggplotly() interactive plots that the website would need to load if we had visualized all the LuciernagaIntegration() plots for the 29-fluorophore panel in one go, we have broken the exploration of the fluorophore outputs by laser. The walk-through of the “Violet” fluorophores has been migrated here.
Blue

Due to the sheer number of plotly ggplotly() interactive plots that the website would need to load if we had visualized all the LuciernagaIntegration() plots for the 29-fluorophore panel in one go, we have broken the exploration of the fluorophore outputs by laser. The walk-through of the “Blue” fluorophores has been migrated here.
Yellow-Green

Due to the sheer number of plotly ggplotly() interactive plots that the website would need to load if we had visualized all the LuciernagaIntegration() plots for the 29-fluorophore panel in one go, we have broken the exploration of the fluorophore outputs by laser. The walk-through of the “Yellow-Green” fluorophores has been migrated here.
Red

Due to the sheer number of plotly ggplotly() interactive plots that the website would need to load if we had visualized all the LuciernagaIntegration() plots for the 29-fluorophore panel in one go, we have broken the exploration of the fluorophore outputs by laser. The walk-through of the “Red” fluorophores has been migrated here.
Autofluorescence

Due to the sheer number of plotly ggplotly() interactive plots that the website would need to load if we had visualized all the LuciernagaIntegration() plots for the 29-fluorophore panel in one go, we have broken the exploration of the fluorophore outputs by laser. The walk-through of the “Unstained” signatures has been migrated here.
Take Away
In this walk-through (and its subsetted “Laser” chapters), we have systematically worked our way through the unmixing controls (both single-color and unstained) for this 29-color panel. Overall, most of the fluorophores were in good shape, only returning a single signature for cells within our gated region, or with signature variants that were highly similar and not likely to cause much trouble at the time of unmixing.
And then, we also encountered 6 problematic cell single-color unmixing controls. These included
- BUV615

- BUV737

- BV421

- PerCP-Cy5.5 (not a proper fail, but a caution)

- PE-Cy5

- APC-Fire 810

While differing in their extent, BUV737, PE-Cy5 and APC-Fire 810 are in the worst shape, and would likely cause issues with the unmixing of the full-stained samples if they were included within the unmixing matrix.
Likewise, we looked at unstained samples and characterized the autofluorescence variation that is present within. Main things we noticed were shift in secondary peak usage for cord blood vs peripheral blood mononuclear cells, and some interesting variation in the height of the secondary “UV7” detector that ties into how similar the signature compares to our autofluorescence-similar fluorophores.
- INF052 Ctrl (CBMC)

- ND006 Ctrl (PBMC)

We will circle back to these bad unmixing control signatures during Week 15 and evaluate what kind of unmixing errors they would generate compared to full-stained samples that had been unmixed with more representative fluorophore signatures. Until then, have fun looking at your own single-color unmixing controls, and make sure to share any interesting tandem-degradation finds on the Discussions Page



