Generates a self-contained HTML QC report and a filtered Seurat RDS object for
each sample processed by CellRanger.
Can be run on a single sample interactively or batched across many samples via
a sample sheet.
GitHub repository: https://github.com/mgildea87/scRNAseq_QC
| File | Purpose |
|---|---|
sample_QC.Rmd |
Parameterised R Markdown template for per-sample QC |
merge_analysis.Rmd |
R Markdown template for post-merge analysis across samples |
integrate_RNA.Rmd |
Optional post-merge RNA integration report template |
qc_batch_runner.R |
Main batch runner — renders sample_QC.Rmd for each sample in the sample sheet |
run_qc_batch_local.sh |
Local shell launcher — loads the CVRC R conda environment, then runs qc_batch_runner.R |
submit_QC_batch.sh |
Cluster launcher — submits one SLURM job per sample (plus a dependent merge job) |
samples.csv |
Sample sheet — edit this to point to your data |
support_files/ |
Bundled TF and haemoglobin reference files used by the templates |
The following R packages must be installed:
install.packages(c("rmarkdown", "ggplot2", "patchwork", "viridis",
"pheatmap", "dplyr", "tidyr", "reshape2", "Hmisc", "knitr"))
BiocManager::install(c("Seurat", "scater", "scDblFinder"))
# From GitHub / internal
remotes::install_github("immunogenomics/presto")
# CVRCFunc — install from internal source as neededThe sample sheet is a CSV file with the following columns:
| Column | Required | Description |
|---|---|---|
sample_name |
Yes | Unique label used for output filenames |
cellranger_dir |
Yes | Path to the CellRanger output directory (usually the folder that contains the outs/ subfolder) |
batch |
No | Batch label stored in the per-sample Seurat object metadata (defaults to A if omitted or blank) |
min_nCount_RNA |
No | Hard lower bound on UMI count per cell |
max_nCount_RNA |
No | Hard upper bound on UMI count per cell |
min_nFeature_RNA |
No | Hard lower bound on genes detected per cell |
max_nFeature_RNA |
No | Hard upper bound on genes detected per cell |
max_percent_mt |
No | Hard upper bound on mitochondrial read fraction (%) |
min_malat1 |
No | Minimum normalised MALAT1 expression (default 1) |
Set any threshold column to NA (or omit the column entirely) to use the
automatic MAD-based default for that metric.
The MAD defaults are:
min_nCount_RNA/min_nFeature_RNA→ median − 4 × MADmax_nCount_RNA/max_nFeature_RNA→ no upper cap (Inf)max_percent_mt→ median + 5 × MADmin_malat1→ 1
sample_name,cellranger_dir,batch,min_nCount_RNA,max_nCount_RNA,min_nFeature_RNA,max_nFeature_RNA,max_percent_mt,min_malat1
Control_1,/path/to/cellranger/count-Control-1,A,NA,NA,NA,NA,NA,1
Treatment_1,/path/to/cellranger/count-Treatment-1,A,500,25000,250,6000,20,1Control_1 uses all MAD-based defaults. Treatment_1 uses hardcoded thresholds.
Matrix folder names are auto-detected from the chosen base directory as long as
they contain valid 10x MEX files (matrix.mtx(.gz), barcodes.tsv(.gz), and
features.tsv(.gz) or genes.tsv(.gz)).
Tip: Run the pipeline on each sample with default thresholds first, inspect the QC reports, then rerun with hardcoded thresholds for any samples that need manual adjustment.
run_qc_batch_local.sh: many samples on one machine from a shell (no SLURM)sample_QC.Rmdin RStudio/R console: one sample interactivelysubmit_QC_batch.sh: many samples on the cluster via SLURM (recommended at scale)
Use this when running directly in a shell and you want environment setup handled for you automatically.
By default, this mode is sequential (--ncores 1), so samples are
processed one at a time. To parallelise per-sample QC on a single machine,
set --ncores to a value greater than 1.
When running the full sample sheet with more than one sample, merge is
performed automatically after all samples complete successfully. To skip this,
set --skip_merge TRUE.
To run the optional post-merge integration report template (integrate_RNA.Rmd)
after a successful merge, set --run_integration TRUE and provide
--integration_level Batch or --integration_level Sample.
bash /path/to/templates/QC/run_qc_batch_local.sh \
--sample_sheet /abs/path/to/samples.csv \
--output_dir /abs/path/to/results/QC# Example: process samples in parallel on 8 cores
bash /path/to/templates/QC/run_qc_batch_local.sh \
--sample_sheet /abs/path/to/samples.csv \
--output_dir /abs/path/to/results/QC \
--ncores 8
# Example: render all samples but skip merged_QC outputs
bash /path/to/templates/QC/run_qc_batch_local.sh \
--sample_sheet /abs/path/to/samples.csv \
--output_dir /abs/path/to/results/QC \
--skip_merge TRUE
# Example: run optional integration report after merge
bash /path/to/templates/QC/run_qc_batch_local.sh \
--sample_sheet /abs/path/to/samples.csv \
--output_dir /abs/path/to/results/QC \
--run_integration TRUE \
--integration_level Batch
# Example: run only integration from an existing merged_QC.rds
bash /path/to/templates/QC/run_qc_batch_local.sh \
--sample_sheet /abs/path/to/samples.csv \
--output_dir /abs/path/to/results/QC \
--integration_only TRUE \
--integration_level SampleOpen sample_QC.Rmd and knit with custom parameters, or run from
the R console:
When using rmarkdown::render(..., params = list(...)), parameter values are
resolved as follows:
- Any parameter supplied in
params = list(...)overrides the value in the YAMLparams:block ofsample_QC.Rmd. - Any parameter not supplied in
params = list(...)falls back to the default value defined in the YAMLparams:block.
In other words, console-supplied params take precedence, and YAML params act as defaults.
rmarkdown::render(
"sample_QC.Rmd",
params = list(
sample_name = "Control_1",
filtered_path = "/path/to/outs/filtered_feature_bc_matrix",
raw_path = "/path/to/outs/raw_feature_bc_matrix",
output_dir = "results/QC"
)
)The recommended way to run across many samples. The coordinator script reads
your sample sheet and submits one independent SLURM job per sample on the
cpu_short partition. Run it with plain bash — do not use sbatch.
bash /path/to/templates/QC/submit_QC_batch.sh \
--sample_sheet /abs/path/to/samples.csv \
--output_dir /abs/path/to/results/QCFor sample sheets with more than one sample, this mode submits a dependent
merge job by default. To skip that merge job, pass --skip_merge TRUE.
To run the optional integration report after merge in the merge job, pass
--run_integration TRUE --integration_level Batch (or Sample).
To run only the integration stage on an existing merged object, pass
--integration_only TRUE --integration_level Batch (or Sample).
Use absolute paths for
--sample_sheetand--output_dir— each sample runs as a separate job on a compute node where relative paths may not resolve.
Each job writes its log to <output_dir>/logs/QC_<sample_name>_<jobid>.log.
| Flag | Default | Description |
|---|---|---|
--sample_sheet |
(required) | Absolute path to CSV sample sheet |
--output_dir |
QC |
Directory for HTML reports, RDS files, and logs |
--outs_subdir |
outs |
Subdirectory inside cellranger_dir used as the search base for filtered/raw matrix folders. Set to "" if cellranger_dir already points to the base directory containing those folders |
--mem |
32 |
Memory per job in GB |
--merge_mem |
64 |
Memory for the merge job in GB |
--integration_mem |
64 |
Memory for the integration job in GB |
--skip_merge |
FALSE |
Skip the post-sample merge step (merged_QC.rds and merge_analysis.html) |
--run_integration |
FALSE |
Run optional post-merge integration report (integrate_RNA.Rmd) |
--integration_level |
(required when integration runs) | Integration grouping level: Batch or Sample |
--integration_only |
FALSE |
Skip sample QC and merge; run only integrate_RNA.Rmd using existing merged_QC.rds |
--time |
12:00:00 |
Wall time per job — max on cpu_short is 12:00:00 |
# Example: larger samples needing more memory
bash /path/to/submit_QC_batch.sh \
--sample_sheet /abs/path/to/samples.csv \
--output_dir /abs/path/to/results/QC \
--mem 32 \
--merge_mem 96 \
--integration_mem 128Monitor submitted jobs with squeue -u $USER.
For each sample <sample_name>, the pipeline writes:
| File | Description |
|---|---|
<output_dir>/<sample_name>_QC.html |
Full interactive QC report |
<output_dir>/<sample_name>_QC.rds |
Filtered Seurat object with doublet scores, ready for integration |
When the sample sheet contains more than one sample, the merge step also writes:
| File | Description |
|---|---|
<output_dir>/merged_QC.rds |
Merged Seurat object produced from all <sample_name>_QC.rds files |
<output_dir>/merge_analysis.html |
Post-merge analysis report rendered from merge_analysis.Rmd |
When --run_integration TRUE is used and merge succeeds, the integration step can also write:
| File | Description |
|---|---|
<output_dir>/integrate_RNA.html |
Integration report rendered from integrate_RNA.Rmd |
<output_dir>/integrated.rds |
Integrated Seurat object if produced by integrate_RNA.Rmd |
The Rmd template performs the following steps in order:
- Load CellRanger filtered and raw count matrices
- Barcode rank (knee) plot
- Remove genes detected in fewer than 0.1% of barcodes
- Compute per-cell QC metrics: UMI count, genes detected, % mitochondrial, % haemoglobin
- Build MAD-based threshold tables for each metric
- Pre-filter QC plots: violins, histograms, scatter pairs
- MALAT1 scatter (proxy for cell viability)
- Apply filters (hard thresholds from params, or MAD defaults)
- Post-filter QC plots
- Normalise → variable features → PCA → clustering → UMAP
- Mean–variance plot and SCTransform model assessment
- Per-cluster QC metric distributions
- Top marker heatmap (Wilcoxon, top 20 per cluster)
- Doublet detection with
scDblFinder(annotated, not removed) - Save filtered + annotated Seurat object to RDS
- The repository is version-controlled at https://github.com/mgildea87/scRNAseq_QC.
- The old
merge_RNAseq.Rmdfile has been removed and is not part of the current pipeline. - TF and hemoglobin support files live under
support_files/and are resolved relative to the R Markdown templates so cloned copies remain portable.