variant-calling/gatk-variant-calling/SKILL.md
Call germline SNPs and indels with GATK HaplotypeCaller and the GVCF joint-genotyping workflow. Covers the local-reassembly + PairHMM mechanism (why HC beats pileup callers on indels), the -ERC GVCF reference-confidence model and <NON_REF> allele, BQSR-vs-DRAGSTR and --dragen-mode error modeling, allele-specific (AS_) annotations, and edge cases (ploidy, Mutect2 mitochondria mode, sex chromosomes/PAR, contamination gating). Use when deciding whether to use HaplotypeCaller vs a pileup or DRAGEN caller, whether BQSR still earns its place, whether to call per-sample GVCFs for a cohort, or how to handle non-diploid, mitochondrial, sex-chromosome, or contaminated samples. Not for post-calling filtering depth (see variant-calling/filtering-best-practices) or cohort joint-genotyping scaling (see variant-calling/joint-calling).
npx skillsauth add GPTomics/bioSkills bio-gatk-variant-callingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference examples tested with: GATK 4.5+, bcftools 1.19+
Before using code patterns, verify installed versions match. If versions differ:
<tool> --version then <tool> --help to confirm flagsIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
Note: flag DEFAULTS (e.g. --max-alternate-alleles, --heterozygosity, --standard-min-confidence-threshold-for-calling) drift across GATK 4.x releases and DRAGEN-mode ports models into the caller. Confirm any default with gatk <Tool> --help rather than trusting a memorized value.
"Call germline variants from my BAM with GATK" -> Detect SNPs/indels by locally reassembling candidate haplotypes in active regions and genotyping reads against them.
gatk HaplotypeCaller (germline diploid), gatk Mutect2 (somatic / mitochondria / mosaic)A pileup/position-based caller (bcftools mpileup, the old UnifiedGenotyper) genotypes each column of the alignment independently, trusting the mapper's per-read placement. Near indels and clustered variation that placement is a per-read greedy optimum, not a locus-consistent one, so the same indel gets (mis)placed differently in different reads and is systematically misrepresented. HaplotypeCaller (HC) discards the local alignment and re-derives it: in any region showing signal it performs local de-novo reassembly of candidate haplotypes, then realigns every read against those haplotypes. The indel is then represented ONCE, in an assembled haplotype, not independently in each read. This is why HC (and DRAGEN, and DeepVariant) beat pileup callers on indels and complex loci, and why HC is the reference implementation everything else is benchmarked against.
--active-probability-threshold, default 0.002) to seed active regions, padded so the assembler sees flanking reference. Non-active bases still get a reference-confidence emission in GVCF mode. Pathologically high-depth or highly repetitive regions can fail to assemble -- exactly where DeepVariant/DRAGEN pull ahead.--pair-hmm-implementation and --native-pair-hmm-threads. DRAGEN moves this same kernel onto an FPGA.--sample-ploidy 2), which hard-codes true alleles at fraction 0, 0.5, or 1.0. Anything violating that (pooled, polyploid, CNV, mosaic, hemizygous chrX/Y) needs an explicit --sample-ploidy or a somatic caller -- see Edge Cases.What is the analysis context?
├── Single sample, want DRAGEN-like accuracy, open-source -> HaplotypeCaller --dragen-mode (hard-filter on QUAL)
├── Cohort < ~2000, human -> per-sample -ERC GVCF -> joint genotype -> VQSR/VETS (see joint-calling)
├── Cohort > ~2000, human -> ReblockGVCF + GnarlyGenotyper "Biggest Practices", or DeepVariant + GLnexus (see joint-calling)
├── Non-human / non-model organism -> hard filtering (no VQSR training resources)
├── Targeted panel / small exome -> hard filtering (too few variants for VQSR)
├── Non-diploid / pooled / sex chromosomes / mitochondria -> set --sample-ploidy or use Mutect2 (see Edge Cases)
└── Somatic / mosaic variants -> Mutect2 (not HaplotypeCaller)
Goal: Call germline SNPs and indels from one sample.
Approach: Run HaplotypeCaller directly to VCF; add annotations, intervals, or a calling-confidence floor as needed.
# Basic call
gatk HaplotypeCaller -R reference.fa -I sample.bam -O sample.vcf.gz
# Exome / panel: restrict to capture targets (much faster, fewer off-target artifacts)
gatk HaplotypeCaller -R reference.fa -I sample.bam -L targets.interval_list -O sample.vcf.gz
# Add standard annotations explicitly (usually emitted by default; force when a downstream filter needs them)
gatk HaplotypeCaller -R reference.fa -I sample.bam -O sample.vcf.gz \
-A Coverage -A QualByDepth -A FisherStrand -A StrandOddsRatio \
-A MappingQualityRankSumTest -A ReadPosRankSumTest
MarkDuplicates is required before calling for all modes. Whether BQSR precedes it is a real decision -- see below.
-ERC GVCF)Goal: Make per-sample calls that can later be combined into a cohort without re-visiting BAMs.
Approach: Emit a GVCF that records confidence at EVERY position, then joint-genotype separately.
-ERC GVCF records, at every position (variant and non-variant), how confidently the site is homozygous reference. Two properties make it the backbone of scalable cohort calling:
<NON_REF> symbolic allele. Every record carries a symbolic ALT <NON_REF> ("any allele not yet observed") with AD/PL computed against it. At joint-genotyping time a variant discovered in ANOTHER sample can therefore be evaluated in THIS sample even though this sample looked reference -- the evidence against <NON_REF> supplies it. This is what makes per-sample GVCFs forward-compatible with alleles found later in the cohort, and lets joint genotyping distinguish confident hom-ref from no-data (the "squaring-off" of a ragged genotype matrix).-ERC BP_RESOLUTION disables banding (one line per base, larger files).# Per-sample GVCF (do this once per sample; reusable when the cohort grows -- the N+1 win)
gatk HaplotypeCaller -R reference.fa -I sample.bam -O sample.g.vcf.gz -ERC GVCF
# Single-sample genotyping straight from one GVCF
gatk GenotypeGVCFs -R reference.fa -V sample.g.vcf.gz -O sample.vcf.gz
The N+1 problem: naive joint calling re-processes all N samples whenever the cohort changes; the GVCF captures the expensive assembly/likelihood work once per sample, so adding sample N+1 only re-runs the cheap consolidation + GenotypeGVCFs. Cohort consolidation (GenomicsDBImport vs CombineGVCFs), scaling to tens of thousands (ReblockGVCF, GnarlyGenotyper), and joint-genotyping mechanics live in variant-calling/joint-calling -- not duplicated here.
Does BQSR still earn its place? (honestly unsettled). BaseRecalibrator/ApplyBQSR builds an empirical error model over base-call covariates (reported quality, read group, cycle, sequence context) to correct systematic, instrument-specific miscalibration. On older continuous-quality 4-color instruments (HiSeq, MiSeq) it mattered. On modern 2-color chemistry (NovaSeq/NextSeq) qualities are emitted in only ~4 coarse bins, leaving little smooth structure to recalibrate; empirically the callset is largely unchanged with vs without BQSR, with the delta concentrated in borderline-GQ sites. Broad keeps BQSR in Best Practices for pipeline consistency; several large pipelines drop it on binned data. Treat it as caller/instrument-dependent, not mandatory -- there is no consensus universal recommendation.
gatk BaseRecalibrator -R reference.fa -I sample.bam \
--known-sites dbsnp.vcf.gz --known-sites Mills_and_1000G_gold_standard.indels.vcf.gz \
-O recal.table
gatk ApplyBQSR -R reference.fa -I sample.bam --bqsr-recal-file recal.table -O sample.recal.bam
Where the indel-accuracy lever actually moved: DRAGSTR. Indel errors scale with tandem-repeat context, so the bigger gain is STR-aware indel modeling, not base-quality recalibration. DRAGEN-GATK adds DRAGSTR: a per-sample auto-calibration (CalibrateDragstrModel -> --dragstr-params-path) that models a-priori indel error/variant probability as a function of STR period (repeat-unit length) and length (copy number), adjusting the PairHMM indel gap priors before genotyping.
--dragen-mode. DRAGEN is Illumina's FPGA-accelerated map-align-call engine (a genome in ~20-25 min; wins the difficult-to-map precisionFDA V2 regions using alt-aware mapping). Illumina and Broad co-developed DRAGEN-GATK, porting DRAGEN's error models into open-source GATK to a functionally equivalent pipeline (Regier et al. 2018: pipelines are functionally equivalent when their call differences are smaller than sequencing-replicate differences). HaplotypeCaller --dragen-mode enables DRAGSTR plus BQD (Base Quality Dropout, systematic local quality collapse) and FRD (Foreign Read Detection, contaminating/mismapped reads), and replaces classic BQSR -- error modeling moves inside the caller. QUAL is well-calibrated, so hard-filtering on QUAL is sufficient without VQSR.
# Single-sample DRAGEN mode (no separate BQSR); GVCF variant for cohorts
gatk HaplotypeCaller -R reference.fa -I sample.markdup.bam -O sample.g.vcf.gz -ERC GVCF --dragen-mode
# DRAGEN-mode hard filter: GATK-recommended QUAL floor for DRAGEN-GATK output
gatk VariantFiltration -R reference.fa -V sample.vcf.gz -O sample.filtered.vcf.gz \
--filter-expression "QUAL < 10.4139" --filter-name "DRAGENHardQUAL" # Broad's documented DRAGEN-mode QUAL cutoff
DRAGEN-ML's advertised FP/FN reductions and speed figures are vendor-reported (Illumina), trained on GIAB truth; treat GIAB benchmark dominance with the overfitting caveat that DL/ML callers may not transfer identically to non-GIAB ancestries.
Standard annotations (QD, FS, MQ, ...) lump all reads at a site together, so at a multiallelic site a real allele co-located with an error-driven allele shares one site-level pass/fail. AS_ annotations (request with -G AS_StandardAnnotation during GVCF calling / genotyping) compute each metric per allele (AS_QD, AS_FS, AS_SOR, AS_MQ, AS_MQRankSum, AS_ReadPosRankSum), letting AS_VQSR (-AS) filter each allele independently. The benefit grows with cohort size, because multiallelic sites -- where a true allele and an artifact collide at one position -- proliferate as sample count rises. The GVCF workflow propagates the raw per-allele data through joint genotyping to enable this.
Filtering separates real variants from artifacts AFTER calling; SNPs and indels always filter separately (different annotation distributions). Pick the method here; the full VariantRecalibrator/ApplyVQSR, VETS, and hard-filter recipes with threshold rationale live in variant-calling/filtering-best-practices.
| Context | Method | Why |
|---|---|---|
| Human WGS cohort, many variants | VQSR (or its successor VETS: ExtractVariantAnnotations -> TrainVariantAnnotationsModel -> ScoreVariantAnnotations) | Enough variants + truth resources to fit the model |
| Large cohort with many multiallelics | AS_VQSR (-AS) | Per-allele filtering at colliding sites |
| Single exome / gene panel | Hard filtering | Too few variants for a stable GMM (a single WGS has enough; the floor is exome/panel-specific) |
| Non-model organism | Hard filtering | No HapMap/1000G/Mills truth resources |
| DRAGEN-mode output | Hard filter on QUAL | QUAL is well-calibrated |
| Somatic (Mutect2) | FilterMutectCalls | Dedicated somatic filtering, not VQSR |
VQSR needs many variants overlapping the truth resources to fit a stable multivariate density; it is unreliable on single exomes or panels -- those must hard-filter. GATK is deprecating VQSR toward VETS (isolation-forest backend); verify the current recommended path in the GATK release notes before committing a pipeline.
Ploidy (--sample-ploidy). The number of genotypes is the multiset coefficient C(ploidy + alleles - 1, ploidy), so PL vectors blow up in both ploidy and allele count -- the reason high-ploidy and pooled calling are memory-heavy.
| Case | Setting | Why |
|---|---|---|
| Pooled samples (n individuals) | --sample-ploidy 2n | Estimate an allele count, not an individual genotype; diploid collapses intermediate frequencies |
| Polyploid organism | --sample-ploidy 4 (etc.) | Dosage genotypes (AAAB=0.25, AABB=0.5) cannot be represented as het |
| Non-PAR chrX/Y in a 46,XY sample | --sample-ploidy 1 (or --ploidy-regions BED) | Hemizygous; diploid calling emits impossible "hets" from error/paralog mismap |
| PAR1/PAR2 | Diploid (mask PAR on Y, call X-PAR as diploid) | PARs recombine and are diploid in both sexes |
Mitochondria -> Mutect2, not HaplotypeCaller. mtDNA heteroplasmy is a continuous VAF (mathematically identical to subclonal somatic variation) that a diploid genotype model cannot express, so use a somatic caller. Run gatk Mutect2 --mitochondria-mode (raises low-AF sensitivity). Because rCRS (NC_012920.1, 16,569 bp circular) is linearized in the control region, align twice -- to the standard reference and to one shifted ~8,000 bp (ShiftFasta) that moves the artificial breakpoint out of the D-loop -- call control-region variants on the shifted reference, LiftoverVcf back, and merge. NUMT-derived reads inflate false low-heteroplasmy calls, so distrust calls below ~5% AF (this underpins the gnomAD mtDNA callset, Laricchia et al. 2022).
Contamination is a gate before trusting any call. Even 1-3% cross-sample contamination injects minority alleles that push allele balance far enough from 0/0.5/1 to be scored as low-fraction hets. Estimate it first: VerifyBamID2 (Zhang et al. 2020, ancestry-agnostic, genotype-free -- models sample ancestry via a PCA/SVD panel, avoiding v1's population-mismatch bias), or CHARR (Lu et al. 2023, variant-level only -- needs just a gVCF/VCF, ~$0.0003/sample, from reference-allele leakage at hom-alt sites). Convention: FREEMIX >= 0.03 (3%) flags a probable contaminated/swapped sample (a guide, not a hard constant). For somatic work, feed CalculateContamination's table into FilterMutectCalls (--contamination-table) so genuine low-fraction variants are separated from contamination artifacts.
# Scatter HaplotypeCaller by contig, then gather
for interval in chr{1..22} chrX chrY; do
gatk HaplotypeCaller -R reference.fa -I sample.bam -L $interval \
-O sample.${interval}.g.vcf.gz -ERC GVCF &
done
wait
gatk GatherVcfs $(for c in chr{1..22} chrX chrY; do echo "-I sample.${c}.g.vcf.gz"; done) -O sample.g.vcf.gz
# PairHMM is the compute bottleneck: give it native SIMD threads
gatk HaplotypeCaller -R reference.fa -I sample.bam -O sample.vcf.gz --native-pair-hmm-threads 4
| Symptom | Cause | Fix |
|---|---|---|
| Spurious heterozygous calls across non-PAR chrX/Y in a male | Called as diploid | --sample-ploidy 1 for non-PAR X/Y (split intervals or --ploidy-regions) |
| mtDNA low-heteroplasmy variants missed or all filtered | HaplotypeCaller's diploid model cannot express continuous VAF | Use Mutect2 --mitochondria-mode + shifted reference |
| Excess false hets genome-wide, allele balance off 0.5 | Sample contamination / swap | Estimate with VerifyBamID2 or CHARR before trusting calls |
| Real variant not called in a repeat/high-depth region | Assembly failed (cyclic graph at all k, or region too complex/deep) | Compare against DeepVariant/DRAGEN; check --max-assembly-region-size, downsampling |
| VariantRecalibrator fails to converge / errors | Too few variants or too little truth-resource overlap | Hard-filter instead (single sample, exome, panel, non-model organism) |
| Indel mis-genotyped near a homopolymer/STR | Base-quality recalibration does not model repeat-context indel error | Use --dragen-mode (DRAGSTR STR-aware indel model) |
| A variant appears as */A or */* after joint genotyping | Spanning-deletion * allele: this position is inside an upstream deletion in some samples | Expected; bcftools norm-decompose and let annotators special-case * |
tools
End-to-end CLIP-seq pipeline from FASTQ to ENCODE-compliant binding sites, single-nucleotide crosslink maps, annotation, motifs, and (optionally) differential binding. Use when running the full Yeo lab eCLIP / iCLIP / iCLIP2 / iCLIP3 / irCLIP / PAR-CLIP analysis with SMInput control, protocol-specific UMI extraction, ENCODE STAR parameters, CLIPper or Skipper peak calling with stringent log2 FC and -log10 p thresholds, IDR rescue and self-consistency QC, and downstream motif registration with mCross or PEKA.
development
Detect, date, and contextualize whole-genome duplication (WGD / paleopolyploidy) events using wgd v2 (Chen et al 2024), KsRates (Sensalari 2022 substitution-rate-corrected Ks dating), DupGen_finder (Qiao 2019), MAPS (Li 2018 phylogenomic), POInT (Conant 2008 ordered-block), SLEDGe (2024 ML-based), Whale.jl (Bayesian DL+WGD), and synteny-anchored paranome construction. Use when identifying ancient polyploidy from Ks distributions and synteny block analysis, positioning WGD events relative to speciation, distinguishing tandem from segmental from WGD duplications, dating the 2R/3R vertebrate / fish / salmonid WGDs, building paranome and Ks-age mixture models, applying KsRates substitution-rate correction across lineages, or testing alternative biased-fractionation / dosage-balance models post-WGD.
tools
Build whole-genome alignments using Progressive Cactus (Armstrong 2020 reference-free clade-level WGA), Minigraph-Cactus (Hickey 2024 pangenome-aware), LASTZ chain/net (UCSC pipeline), MUMmer4 (Marçais 2018 pairwise), minimap2 -x asm5/10/20 (Li 2018 fast pairwise), AnchorWave (Song 2022 WGD-aware), and Mauve / progressiveMauve (bacterial). Operates the HAL toolkit (Hickey 2013) for downstream extraction including halSynteny, halLiftover, halBranchMutations, and hal2maf. Use when constructing multi-species alignments for comparative-annotation projection (TOGA), synteny detection, conservation analyses (phyloP / PhastCons), or pangenome graph construction; selecting between reference-free (Cactus) and reference-anchored (LASTZ chains/nets) approaches; tuning sensitivity for closely vs distantly related genomes; or producing HAL files for genome-wide downstream tools.
development
Detect syntenic blocks and structural rearrangements between genomes using MCScanX (Wang 2012), JCVI/MCScan (Tang 2008 Python), GENESPACE (Lovell 2022) for orthology-anchored riparian visualization, SyRI for structural variation, AnchorWave for sequence-level synteny, i-ADHoRe 3.0 for highly diverged species, SynNet for synteny networks, and ntSynt for multi-genome macrosynteny. Use when identifying collinear gene blocks across species, distinguishing macrosynteny from microsynteny, detecting inversions/translocations/duplications, anchoring orthology in WGD lineages, producing publication riparian plots, computing synteny block age via Ks (cross-references whole-genome-duplication), or running synteny-aware ortholog inference in polyploids.