Date: 2026-04-28

# Using scifer to filter single-cell sorted B cell receptor (BCR) sanger sequences

Rodrigo Arcoverde Cerveira, Matthew James Hinchcliff1,2*

1Division of Immunology & Allergy, Department of Medicine Solna, Karolinska Institutet and Karolinska University Hospital, Stockholm, Sweden
2Center for Molecular Medicine, Karolinska Institutet, Stockholm, Sweden

*rodrigo.arcoverdi@gmail.com

#### 28 April 2026

#### Package

scifer 1.14.0

# Contents

## 1 Introduction

Have you ever index sorted cells in a 96 or 384-well plate and then sequenced using Sanger sequencing? If so, you probably had some struggles to either check the chromatogram of each cell sequenced manually or to identify which cell was sorted where after sequencing the plate. Scifer was developed to solve this issue by performing basic quality control of Sanger sequences and merging flow cytometry data from probed single-cell sorted B cells with sequencing data. Scifer can export summary tables, fasta files, chromatograms for visual inspection, and generate reports.

Single-cell sorting of probed B/T cells for Sanger sequencing of their receptors is widely used, either for identifying antigen-specific antibody sequences or studying antigen-specific B and T cell responses. For this reason, scifer R package was developed to facilitate the integration and QC of flow cytometry data and sanger sequencing.

## 2 Dataset example and description

This vignette aims to show one example of how to process your own samples based on a test dataset. This dataset contains raw flow cytometry index files (file extension: `.fcs`) and raw sanger sequences (file extension: `.ab1`). These samples are of antigen-specific B cells that were probed and single-cell sorted in a plate to have their B cell receptors (BCR) sequenced through sanger sequencing. This package can also be used for T cell receptors but you should have extra attention selecting the QC parameters according to your intended sequence. The sorted cells had their RNA reverse transcribed into cDNA and PCR amplified using a set of primer specific for rhesus macaques (sample origin), the resulting PCR products were sequenced using an IgG specific primer designed to capture the entire VDJ fragment of the BCRs.

## 3 Folder organization

Regardless of where is your data, you should have two folders, one for flow cytometry data and a second one for sanger sequences. The nomenclature of the `.fcs` files and the sanger sequence subfolders should be matching, this is fundamental for merging both datasets.

### 3.1 Extra information

If you want to have a more detailed explanation of installation steps and folder organization, check the README file in the package github here.

## 4 Installation instructions

Get the latest stable `R` release from CRAN. Then install `scifer` using from Bioconductor the following code:

    if (!requireNamespace("BiocManager", quietly = TRUE)) {
        install.packages("BiocManager")
    }

    BiocManager::install("scifer")

## 5 Load required packages

    library(ggplot2)
    library(scifer)

## 6 Checking flow cytometry data

It is important to check your flow data to check how is your data being processed, if it is already compensated, and if the cells were probed which thresholds you should use.

### 6.1 Example 1

Here is an example of a poor threshold using the forward and side scatter (cell size and granularity).

    fcs_data <- fcs_processing(
        folder_path = system.file("/extdata/fcs_index_sorting", package = "scifer"),
        compensation = FALSE, plate_wells = 96,
        probe1 = "FSC.A", probe2 = "SSC.A",
        posvalue_probe1 = 600, posvalue_probe2 = 400
    )

    fcs_plot(fcs_data)

You can play around with the threshold and the different channels available. You can check the name of each channel using this:

    colnames(fcs_data)
    #> NULL

You can see that the well position was already extracted from the file and a column named `specificity` was added. This specificity is named based on your selected channels and their thresholds.

### 6.2 Example 2

If you did not probe your cells for a specific antigen, you can just use the following and ignore the `specificity` column. This approach will add all of your cells, regardless of the detected fluorescence in a channel.

    fcs_data <- fcs_processing(
        folder_path = system.file("/extdata/fcs_index_sorting", package = "scifer"),
        compensation = FALSE, plate_wells = 96,
        probe1 = "FSC.A", probe2 = "SSC.A",
        posvalue_probe1 = 0, posvalue_probe2 = 0
    )

    fcs_plot(fcs_data)

### 6.3 Example 3

If you have probed your cells based on a specific marker, you can use the name of the channel or the custom name you have added during the sorting to that channel.

    fcs_data <- fcs_processing(
        folder_path = system.file("/extdata/fcs_index_sorting", package = "scifer"),
        compensation = FALSE, plate_wells = 96,
        probe1 = "Pre.F", probe2 = "Post.F",
        posvalue_probe1 = 600, posvalue_probe2 = 400
    )

    fcs_plot(fcs_data)

### 6.4 Example 4

Finally, the above data used compensation as set to `FALSE`, which is not usually the case since you probably have compensated your samples before sorting. You can set it to `TRUE` and the compensation matrix within the index files will be already automatically applied.

    fcs_data <- fcs_processing(
        folder_path = system.file("/extdata/fcs_index_sorting", package = "scifer"),
        compensation = TRUE, plate_wells = 96,
        probe1 = "Pre.F", probe2 = "Post.F",
        posvalue_probe1 = 600, posvalue_probe2 = 400
    )

    fcs_plot(fcs_data)

The `specificity` column uses these thresholds to name your sorted cells. In this example, you would have a `Pre.F` single-positive, `Post.F` single-positive, double-positive cells named as`DP`, and double-negative cells named as `DN`.

    unique(fcs_data$specificity)
    #> NULL

## 7 Sanger sequence dataset

### 7.1 Processing a single B cell receptor sanger sequence

Here is just an example of if you would like to process a single sanger sequence

    ## Read abif using sangerseqR package
    abi_seq <- sangerseqR::read.abif(
        system.file("/extdata/sorted_sangerseq/E18_C1/A1_3_IgG_Inner.ab1",
            package = "scifer"
        )
    )
    ## Summarise using summarise_abi_file()
    summarised <- summarise_abi_file(abi_seq)
    head(summarised[["summary"]])
    #>              raw.length          trimmed.length              trim.start
    #>                     465                       0                       0
    #>             trim.finish     raw.secondary.peaks trimmed.secondary.peaks
    #>                       0                     347                       0
    head(summarised[["quality_score"]])
    #>   score position
    #> 1    25        1
    #> 2     7        2
    #> 3     6        3
    #> 4     3        4
    #> 5     2        5
    #> 6     5        6

### 7.2 Processing a group of B cell receptors sanger sequences

Most of the time, if you have sequenced an entire plate, you want to automate this processing. Here you would process recursively all the `.ab1` files within the chosen folder.

*Note: To speed up, you can increase the `processors` parameter depending on your local computer’s number of processors or set it to `NULL` which will try to detect the max number of cores.

    sf <- summarise_quality(
        folder_sequences = system.file("extdata/sorted_sangerseq", package = "scifer"),
        secondary.peak.ratio = 0.33, trim.cutoff = 0.01, processor = 1
    )
    #> Looking for .ab1 files...
    #> Found 5 .ab1 files...
    #> Loading reads...
    #> Calculating read summaries...
    #> Cleaning up

Here are the columns from the summarised `data.frame`:

    ## Print names of all the variables
    colnames(sf[["summaries"]])
    #>  [1] "file.path"               "folder.name"
    #>  [3] "file.name"               "raw.length"
    #>  [5] "trimmed.length"          "trim.start"
    #>  [7] "trim.finish"             "raw.secondary.peaks"
    #>  [9] "trimmed.secondary.peaks" "raw.mean.quality"
    #> [11] "trimmed.mean.quality"    "raw.min.quality"
    #> [13] "trimmed.min.quality"

Here is the example `data.frame` with the summarised results of all the files within the selected path:

    ## Print table
    head(sf[["summaries"]][4:10])
    #>   raw.length trimmed.length trim.start trim.finish raw.secondary.peaks
    #> 1        464            153         68         220                 342
    #> 2        465            256          1         256                 347
    #> 3        450            190         21         210                 300
    #> 4        466            249         24         272                 141
    #> 5        458            428         14         441                   7
    #>   trimmed.secondary.peaks raw.mean.quality
    #> 1                     114         12.34698
    #> 2                     187         13.17345
    #> 3                     116         14.56889
    #> 4                      57         23.45299
    #> 5                       1         52.35153

## 8 Joining flow cytometry and sanger sequencing datasets

Finally, the function you will use to integrate both datasets and export data from `scifer` is `quality_report()`. This function aims to basically merge the two datasets, assign sorting specificity based on the selected thresholds for each channel/probe and write different files.

This function generated the following files:

- general quality report (.html)
- individualized report per ID (.html)
- table containing the good quality sequences filtered in and their QC summary (.csv)
- fasta file containing all the sequences (.fasta)
- fasta files per ID (.fasta)
- Chromatogram of the approximate CDR3 region if secondary peaks are detected (.pdf)

    quality_report(
        folder_sequences = system.file("extdata/sorted_sangerseq", package = "scifer"),
        outputfile = "QC_report.html", output_dir = "~/full/path/to/your/location",
        folder_path_fcs = system.file("extdata/fcs_index_sorting",
            package = "scifer"
        ),
        probe1 = "Pre.F", probe2 = "Post.F",
        posvalue_probe1 = 600, posvalue_probe2 = 400
    )

## 9 IgBlast analysis for sequencing results

The `igblast()` function will generate a data frame with the IgBlast results for each sequence.

Before running IgBlast, select the database to be downloaded (e.g., ORGDB, IMGT , KIMDB and adjust it accordingly. As shown in the example below:

KIMDB_rm/ ├── V.fasta ├── D.fasta ├── J.fasta

The database argument is the path to the preferred database for species and receptor type. The database folder should contain separate `.fasta` files corresponding to the genes associated with each receptor type, e.g., `V`-, `D`-, and `J.fasta` files for BCR/TCR. The quality report creates a `sequences.fasta` file found in the `output_dir`. The path to this file should be used as the path input for the `fasta` argument in the `igblast()` function and the path to the database folder.

Scifer has included the KIMDB database for rhesus macaques, which is used for testing the functions.

As shown here:

    ighv_res <- igblast(
            database = system.file("/extdata/test_fasta/KIMDB_rm", package = "scifer"),
            fasta = system.file("/extdata/test_fasta/test_igblast.txt", package = "scifer"),
            threads = 1 #For parallel processing. Default = 1
    )

    head(ighv_res, 2)

    sessionInfo()
    #> R version 4.6.0 Patched (2026-04-24 r89963)
    #> Platform: aarch64-apple-darwin23
    #> Running under: macOS Tahoe 26.3.1
    #>
    #> Matrix products: default
    #> BLAS:   /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
    #> LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
    #>
    #> locale:
    #> [1] C/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
    #>
    #> time zone: America/New_York
    #> tzcode source: internal
    #>
    #> attached base packages:
    #> [1] stats     graphics  grDevices utils     datasets  methods   base
    #>
    #> other attached packages:
    #> [1] scifer_1.14.0    ggplot2_4.0.3    BiocStyle_2.40.0
    #>
    #> loaded via a namespace (and not attached):
    #>  [1] tidyselect_1.2.1      viridisLite_0.4.3     dplyr_1.2.1
    #>  [4] farver_2.1.2          filelock_1.0.3        Biostrings_2.80.0
    #>  [7] S7_0.2.2              fastmap_1.2.0         promises_1.5.0
    #> [10] digest_0.6.39         mime_0.13             lifecycle_1.0.5
    #> [13] pwalign_1.8.0         magrittr_2.0.5        compiler_4.6.0
    #> [16] rlang_1.2.0           sass_0.4.10           tools_4.6.0
    #> [19] yaml_2.3.12           data.table_1.18.2.1   knitr_1.51
    #> [22] reticulate_1.46.0     here_1.0.2            plyr_1.8.9
    #> [25] xml2_1.5.2            RColorBrewer_1.1-3    withr_3.0.2
    #> [28] RProtoBufLib_2.24.0   BiocGenerics_0.58.0   grid_4.6.0
    #> [31] stats4_4.6.0          xtable_1.8-8          MASS_7.3-65
    #> [34] scales_1.4.0          isoband_0.3.0         dichromat_2.0-0.1
    #> [37] cli_3.6.6             rmarkdown_2.31        crayon_1.5.3
    #> [40] generics_0.1.4        otel_0.2.0            rstudioapi_0.18.0
    #> [43] DBI_1.3.0             cachem_1.1.0          flowCore_2.24.0
    #> [46] stringr_1.6.0         parallel_4.6.0        BiocManager_1.30.27
    #> [49] XVector_0.52.0        matrixStats_1.5.0     basilisk_1.24.0
    #> [52] vctrs_0.7.3           Matrix_1.7-5          jsonlite_2.0.0
    #> [55] dir.expiry_1.20.0     cytolib_2.24.0        bookdown_0.46
    #> [58] IRanges_2.46.0        S4Vectors_0.50.0      systemfonts_1.3.2
    #> [61] jquerylib_0.1.4       glue_1.8.1            sangerseqR_1.48.0
    #> [64] stringi_1.8.7         gtable_0.3.6          later_1.4.8
    #> [67] tibble_3.3.1          pillar_1.11.1         basilisk.utils_1.24.0
    #> [70] htmltools_0.5.9       Seqinfo_1.2.0         R6_2.6.1
    #> [73] textshaping_1.0.5     rprojroot_2.1.1       evaluate_1.0.5
    #> [76] kableExtra_1.4.0      shiny_1.13.0          lattice_0.22-9
    #> [79] Biobase_2.72.0        png_0.1-9             DECIPHER_3.8.0
    #> [82] httpuv_1.6.17         bslib_0.10.0          Rcpp_1.1.1-1.1
    #> [85] svglite_2.2.2         gridExtra_2.3         xfun_0.57
    #> [88] pkgconfig_2.0.3