Skip to main content

Cross-sample contamination and identity detector for targeted-capture NGS runs

Project description

Sentinel

Sentinel finds cross-sample contamination, sample swaps, and identity drift on a run of targeted NGS samples. It works by comparing each sample's genotype calls to every other sample on the same run, looking for the asymmetric read-mixing signature that contamination leaves behind.

Around that core directional score, Sentinel layers a per-sample drop in alt allele frequency at hom-alt sites, a VAF anomaly check, a read-level haplotype consistency test, and an identity anchor that catches outright swaps. Each sample comes back with a PASS / WARN / FAIL verdict and a best-guess donor when it failed. The output is a self-contained HTML report with an interactive 96-well plate view, a flagged-samples table, and a per-sample contamination bar plot.


How it works

flowchart LR
    subgraph Inputs
        SS[sample sheet]
        BAM[BAMs]
        CAT[SNP catalog]
    end
    subgraph sentinel_run [sentinel run]
        EX[extract_ad<br/>walk each BAM<br/>at catalog sites]
        SC[pipeline<br/>identity, directional,<br/>deflation, tails,<br/>haplotypes]
        PP[post_process<br/>anchor check<br/>+ verdict]
    end
    subgraph Outputs
        H[report.html]
        X[report.xlsx]
        TSV[per_sample_report.tsv]
        CSV[per_sample_report.csv]
    end
    SS --> EX
    BAM --> EX
    CAT --> EX
    EX --> SC
    SC --> PP
    PP --> H
    PP --> X
    PP --> TSV
    PP --> CSV
    classDef input fill:#dde6f2,stroke:#1f4e79,stroke-width:1.5px,color:#0a1426
    classDef stage fill:#1f4e79,stroke:#0a1426,stroke-width:1.5px,color:#ffffff
    classDef output fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#0a1426
    class SS,BAM,CAT input
    class EX,SC,PP stage
    class H,X,TSV,CSV output

At each SNP site, for every ordered pair of samples (donor S, recipient T), Sentinel computes:

score(S, T) = mean alt-VAF in T over informative sites - background(T)

Informative sites are those at which the recipient T is called homozygous-reference at sufficient depth, the donor S is alt-bearing, and T meets the recipient-depth threshold. The score reported as score_homalt further restricts the informative set to sites where S is homozygous-alt (not just heterozygous). At a donor-hom-alt / recipient-hom-ref site, the expected alt fraction in a recipient contaminated by the donor at fraction α equals α - the donor contributes alt-bearing reads, the recipient contributes reference reads. At a donor-het site that expected value equals α / 2. Restricting to hom-alt sites therefore gives a direct estimate of the contamination fraction from that donor.

The background(T) term is the mean alt-VAF in T at T's own hom-ref sites, T's per-sample sequencing-error floor. Subtracting it makes score(S, T) zero when T has no contamination. The matrix is asymmetric: row sums over score(S, T) give the candidate donors pointing into recipient T, column sums give the samples T is leaking into. Within-identity-group pairs (replicates of the same individual) are masked out so they don't generate false donor inferences.

Around that score, Sentinel layers a CHARR-style hom-alt deflation metric, a VAF-tail anomaly score, a read-level 3+-haplotype check, and an identity-anchor cross-check. The verdict logic combines all of these with sample-type-aware thresholds: controls and FFPE samples are held to looser bars than fresh clinical samples.


Install

The detector ships as a Python package with a small CLI. Pick one:

# pip
pip install sentinel-cd

# conda (bioconda)
conda install -c bioconda sentinel-cd

# Docker
docker pull ghcr.io/<org>/sentinel:latest

samtools and bcftools must be on PATH (the conda and Docker images bundle them).

Tested with Python 3.10-3.12 on Linux and WSL. macOS works but isn't part of the CI matrix.


What you need

Aligned BAMs one per sample, indexed (.bai), GRCh38 reference
Sample sheet CSV, one row per sample, at minimum a sample_id column
Site catalog TSV from sentinel build-panel, scoped to your panel
Reference FASTA GRCh38, only required if you run the haplotype branch

The site catalog is a one-off per panel: build it once, reuse it for every run that uses the same baits.


Quick start

# 1. Build the catalog for your panel (one-off)
sentinel build-panel \
    --bed mypanel.bed \
    --master-db gnomad_v41_common.vcf.gz \
    --panel-name MyPanel \
    --out catalogs/

# 2. Run the detector on a run
sentinel run \
    --sample-sheet samples.csv \
    --bams-dir /path/to/bams \
    --catalog catalogs/MyPanel_sites.tsv \
    --ref GRCh38.fa \
    --out my_run/

After sentinel run finishes you'll find in my_run/results/:

  • report.html - the interactive single-file report (open in a browser)
  • report.xlsx - the same per-sample table, plus a column legend, as Excel
  • per_sample_report.tsv - one row per sample, machine-readable
  • per_pair_report.tsv - long-form donor-to-recipient table for flagged pairs

Open report.html and you get a 96-well plate view, a flagged-samples table, a per-sample contamination bar plot, and a sortable all-samples table. Click any row to isolate that sample on the plate.


Sample sheet

Minimum: one column.

sample_id
SAMPLE001
SAMPLE002
SAMPLE003

BAMs are matched in --bams-dir by <sample_id>_sorted.bam or <sample_id>.bam.

Optional columns:

Column Purpose
sample_type One of control, reference_fresh, reference_ffpe, mix, clinical. Drives the verdict thresholds; controls are held to a different bar than clinical samples. Defaults to clinical if absent.
well 96-well coordinate (e.g. A1, H12). When present, the report shows a plate-layout view that highlights neighbouring-well cross-contamination.
expected_identity The sample identity each BAM is labelled as. If the genetics don't match, the sample is flagged as a swap. Defaults to sample_id.
patient_id Trio / family grouping. Only used if multiple samples share a patient_id.

A template is in examples/sample_sheet_template.csv and an extended example with the optional columns is in examples/sample_sheet_with_optional.csv.


Commands

Command Purpose
sentinel build-panel Build a SNP catalog by intersecting your panel BED with a master VCF
sentinel run Extract allele depths, score, post-process, render reports
sentinel report Re-render reports from an existing results directory

Each command has --help with the full set of options.


The master SNP database

sentinel build-panel needs a master VCF of common SNPs. The recommended source is gnomAD v4.1, filtered to biallelic SNPs at MAF >= 5%.

Two pre-built distributions are published:

File Source Sites Size Host
gnomad_v41_exomes_common.vcf.gz gnomAD v4.1 exomes, MAF >= 5%, biallelic SNPs ~2.6M ~1.8 GB GitHub Releases
gnomad_v41_genomes_maf01.vcf.gz gnomAD v4.1 genomes, MAF >= 1%, biallelic SNPs ~15M ~15 GB Zenodo, DOI 10.5281/zenodo.20264481

The exomes file works for most capture panels (gene panels, exome panels, targeted hybridisation-capture assays). Use the genomes file when your panel includes non-coding bait regions (custom panels with intronic content, WGS, etc.).

Both files are bgzipped and tabix-indexed. Download, drop next to your analysis, point sentinel build-panel --master-db at them.

# exomes (1.8 GB) + index, from GitHub
curl -LO https://github.com/Trethewey/Sentinel/releases/download/v0.1.0/gnomad_v41_exomes_common.vcf.gz
curl -LO https://github.com/Trethewey/Sentinel/releases/download/v0.1.0/gnomad_v41_exomes_common.vcf.gz.tbi

# genomes (15 GB) + index, from Zenodo
curl -LO https://zenodo.org/records/20264481/files/gnomad_v41_genomes_maf01.vcf.gz
curl -LO https://zenodo.org/records/20264481/files/gnomad_v41_genomes_maf01.vcf.gz.tbi

To build your own from scratch see tools/build_master_db.sh.


Docker

docker run --rm -v $PWD:/data ghcr.io/<org>/sentinel:latest run \
    --sample-sheet /data/samples.csv \
    --bams-dir /data/bams \
    --catalog /data/catalogs/MyPanel_sites.tsv \
    --ref /data/GRCh38.fa \
    --out /data/run_output

The image is based on python:3.11-slim with samtools, bcftools, and all Python dependencies preinstalled.


DNA Nexus

A DNA Nexus app wrapper is shipped in dxapp/. Build with:

cd dxapp && dx build --upload

The app exposes the same inputs as the CLI: sample sheet, BAMs, catalog, reference FASTA, and the optional thread count. Outputs land back in the DNA Nexus project as report.html, per_sample_report.csv, and per_pair_report.csv.


Output schema

per_sample_report.tsv columns (the headline ones):

Column Meaning
sample_id From the sample sheet
well Plate well (if supplied)
sample_type From the sample sheet (defaults to clinical)
verdict PASS, WARN, or FAIL
top_score_homalt Headline contamination score; approximately equals the contamination fraction
top_donor_sample_id Best guess at the source of the contamination
homalt_deflation Drop in alt-VAF at the sample's own hom-alt sites
identity_match TRUE if the genetics match the declared identity, FALSE if swapped
concordance_to_expected Genetic similarity to the declared identity (0-1)

The Excel report carries every column and a legend sheet explaining each one.


Citation

If you use Sentinel in published work, please cite the GitHub repository (a preprint is in preparation) and the papers that introduced the methods Sentinel builds on.

Methods

  • Lu W, Gauthier LD, Poterba T et al. CHARR efficiently estimates contamination from DNA sequencing samples. Bioinformatics, 2023. [charr.py]
  • Jun G, Flickinger M, Hetrick KN et al. Detecting and estimating contamination of human DNA samples in sequencing and array-based genotype data. Am J Hum Genet 91:839-848, 2012. [conceptual baseline for per-sample contamination QC]
  • Pedersen BS, Bhetariya PJ, Brown J et al. Somalier: rapid relatedness estimation for cancer and germline studies using efficient genome sketches. Genome Medicine 12:62, 2020. [identity_loh.py, anchor_check.py]
  • Schmitt MW, Kennedy SR, Salk JJ et al. Detection of ultra-rare mutations by next-generation sequencing. PNAS 109:14508-14513, 2012. [read_haplotypes.py]
  • Weissensteiner H, Forer L, Fendt L et al. Contamination detection in sequencing studies using the mitochondrial phylogeny (Haplocheck). Genome Research 31:309-316, 2021. [mtdna_mixture.py, sct_discriminator.py]

Reference data

  • Chen S, Francioli LC, Goodrich JK et al. A genomic mutational constraint map using variation in 76,156 human genomes (gnomAD v4). Nature 625:92-100, 2024.
  • Trethewey C. Sentinel reference panel: gnomAD v4.1 genomes biallelic SNPs at MAF >= 1% (2026). Zenodo. DOI: 10.5281/zenodo.20264481.

Tools

  • Li H. The Sequence Alignment/Map format and SAMtools. Bioinformatics 25:2078-2079, 2009.
  • Danecek P, Bonfield JK, Liddle J et al. Twelve years of SAMtools and BCFtools. GigaScience 10:giab008, 2021.
  • pysam: github.com/pysam-developers/pysam

Scientific Python stack (if your venue requires it): NumPy (Harris et al. Nature 585:357-362, 2020), pandas (McKinney, SciPy 2010), PyArrow, Plotly.


License

See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sentinel_cd-0.1.0.tar.gz (14.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sentinel_cd-0.1.0-py3-none-any.whl (10.6 kB view details)

Uploaded Python 3

File details

Details for the file sentinel_cd-0.1.0.tar.gz.

File metadata

  • Download URL: sentinel_cd-0.1.0.tar.gz
  • Upload date:
  • Size: 14.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for sentinel_cd-0.1.0.tar.gz
Algorithm Hash digest
SHA256 fa0fdc284c2c53c35086ab4f6d624c4bf3cc266a21feff7f9fc66f9449a59acd
MD5 10ddf83c8538a27b9c734da3a58765b3
BLAKE2b-256 82c83a9cc56bd5b84d85127b288069a4a352ef4a43a82794c461315678303c49

See more details on using hashes here.

File details

Details for the file sentinel_cd-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: sentinel_cd-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 10.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for sentinel_cd-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1784fad3800181bb871baa2438abdc5a7b6f42ce5df3130b2c60d464d428106f
MD5 df4a553b9ac86117b6fea2824d1f1af1
BLAKE2b-256 498732b23b9b1ccccf39216e5214597ffa9e089e089768ed6f0faca0a1800e92

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page