Skip to content

Annotating copy-number variants from your own whole-genome data

Oak
A lab instrument holds a glass column where a glowing DNA strand shows repeated segment stacks and missing gaps.

By the end of this guide you will have a single tab-delimited table in which every copy-number variant (CNV) called from your genome carries a set of columns you can sort and filter on. Those columns cover which genes and exons it touches, how often it appears in population reference sets, and whether the affected genes are known to be dosage sensitive. They also include an ACMG-style classification score and a link to a screenshot-ready view for manual inspection. You need a CRAM or BAM aligned to GRCh38 and a CNV or structural variant VCF from at least one caller. You also need roughly 50 GB of scratch space and a Linux machine with conda or Docker. Everything below assumes GRCh38 with the standard chr-prefixed contig names, because mixing builds is the single most common way to produce annotations that look plausible and are wrong. A copy-number variant is a segment of the genome, conventionally 50 bp or larger, present in more or fewer copies than the reference expects. A single-nucleotide polymorphism (SNP) changes one base. The two are annotated with completely different machinery, which is why CNV pipelines exist separately from the SNP pipelines most people meet first.

1. Produce a call set you can defend

Annotation inherits every error in the calls, so start by understanding where your calls came from. Short-read callers fall into two families. Read-depth methods such as CNVpytor, cn.mops, and GATK gCNV detect changes in coverage and are good at large events and at estimating copy number. Split-read or discordant-pair methods such as Manta, Delly, and smoove resolve breakpoints to near base pair but miss large repeat-flanked events. Systematic comparisons across array, short-read, and long-read data show only partial agreement between platforms and callers on the same sample. Each technology contributes calls the others miss 1. If you have only exome data, expect a coarser picture: CNV detection from whole-exome coverage works and does find diagnostically relevant events, but resolution is limited by the bait design and the smallest reliable call is usually a multi-exon event 2.

A practical minimum for a personal genome is one depth-based caller plus one breakpoint-based caller. CNVpytor is the fastest way to get depth calls directly from a CRAM:

cnvpytor -root sample.pytor -rd sample.cram -T GRCh38.fa
cnvpytor -root sample.pytor -his 1000 10000 100000
cnvpytor -root sample.pytor -partition 1000 10000 100000
cnvpytor -root sample.pytor -call 10000 > calls.10k.tsv

Then import the SNP allele depths, which give you B-allele frequency (the fraction of reads supporting the alternate allele at heterozygous sites) as an independent line of evidence:

cnvpytor -root sample.pytor -snp sample.snps.vcf.gz -sample SAMPLE
cnvpytor -root sample.pytor -baf 10000

BAF is useful because a true heterozygous deletion removes heterozygous sites entirely across the interval, and a duplication shifts the expected 0.5 ratio toward roughly 0.33 and 0.67. Methods that infer joint CNV and SNP haplotypes from genotype data formalize this relationship and were built precisely because the SNP signal disambiguates copy state where depth alone is noisy 3. If you are working from long reads instead, follow published best-practice workflows for second- and third-generation germline analysis rather than adapting a short-read pipeline by hand. Alignment, calling, and filtering conventions differ at each step 4.

2. Normalize everything to one BED and one VCF

Annotation tools disagree about input formats. AnnotSV takes VCF or BED, ClassifyCNV takes a four-column BED, and TADA takes BED. Build both representations once from your merged calls and use them consistently.

bcftools view -i 'SVTYPE=="DEL" || SVTYPE=="DUP"' -Oz -o cnv.vcf.gz merged.sv.vcf.gz
bcftools index -t cnv.vcf.gz

bcftools query -f '%CHROM\t%POS0\t%INFO/END\t%INFO/SVTYPE\t%ID\t%INFO/SVLEN\n' cnv.vcf.gz \
  | awk -F'\t' 'BEGIN{OFS="\t"} $3>$2 {print $1,$2,$3,$4,$5}' \
  | sort -k1,1 -k2,2n > cnv.bed

Two things break here regularly. Deletion SVLEN is negative in some callers and positive in others, so derive length from END - POS rather than trusting the field. And symbolic ALT alleles (<DEL>, <DUP>) require END in INFO. Callers that emit <CNV> with a per-sample CN format field instead need the type inferred from CN relative to the expected ploidy, which means handling chrX and chrY separately if you are male. Write the ploidy assumption into the file rather than leaving it implicit.

3. Attach genes and exons with the annotation you trust

Gene annotation means assigning biological identity to genomic coordinates. It records which transcripts, exons and coding sequence overlap, and what the gene is known to do. For CNVs the distinction that matters is between overlapping a gene at all and overlapping its coding exons, because a 40 kb deletion sitting entirely in an intron has very different consequences from one removing exons 3 through 7.

Use GENCODE rather than RefSeq for personal work, because the versioning is clean and the tags let you drop transcripts you do not want:

zcat gencode.v45.annotation.gtf.gz \
  | awk -F'\t' '$3=="exon"' \
  | grep -E 'transcript_type "protein_coding"' \
  | grep -E 'tag "MANE_Select"' \
  | bedtools sort -i - > mane.exons.bed

bedtools intersect -a cnv.bed -b mane.exons.bed -wa -wb -loj > cnv.exon.overlap.txt

Restricting to MANE Select (one agreed representative transcript per protein-coding gene) collapses the combinatorial explosion of transcript isoforms and makes the output readable. Keep a second pass against all protein-coding transcripts for any variant you plan to look at closely, since a minority of genes have clinically relevant exons outside the MANE transcript.

Expect the raw overlap list to be dominated by gene families that are polymorphic in copy number in everyone. The olfactory receptor repertoire is the clearest example: this family shows extensive copy-number variation between healthy individuals, with a substantial share of loci differing in copy number across a modest sample of people 5. Seeing OR genes, HLA genes, or immunoglobulin segments at the top of your list is a sign the pipeline is working rather than a finding.

4. Add population frequency, and be careful whose population

The single most informative annotation is how common your CNV is. Use gnomAD-SV site VCFs matched to your build, and require reciprocal overlap rather than any overlap, so that a 2 Mb population deletion does not silently “explain” your 8 kb one:

bcftools query -f '%CHROM\t%POS0\t%INFO/END\t%INFO/SVTYPE\t%INFO/AF\n' \
  gnomad.sv.sites.vcf.gz | sort -k1,1 -k2,2n > gnomad_sv.bed

bedtools intersect -a cnv.bed -b gnomad_sv.bed -f 0.50 -r -wa -wb \
  > cnv.gnomad.rec50.txt

A 50 percent reciprocal overlap threshold with type matching is the common convention and the one we use. Loosen it to 0.30 for large events where breakpoint uncertainty is high, and tighten it to 0.70 when you care about a specific gene boundary. Add the Database of Genomic Variants as a second reference for benign structural variation, accepting that its frequencies are not comparable to gnomAD’s because it aggregates heterogeneous studies.

Frequency is ancestry-dependent, and reference panels are not evenly representative. Population-specific CNV maps built in single populations, such as the Korean genome variation database that catalogues CNVs and the SNPs that tag them, exist because allele frequencies estimated elsewhere transfer imperfectly 6. If your ancestry is under-represented in gnomAD, treat “absent from gnomAD” as weaker evidence of rarity than it looks. Deeper evolutionary context can also help you recognize structurally unstable regions: comparative analyses across great apes show that certain genomic regions are recurrently copy-number variable across species, and cross-vertebrate CNV databases make the same point at longer time scales 78. Recurrence across lineages usually means segmental duplication and mapping difficulty. Biological importance is not the usual explanation.

5. Score dosage sensitivity and constraint

A rare CNV matters mainly if the genes inside it are sensitive to dose. Two resources cover most of this. The ClinGen dosage sensitivity map assigns each gene and recurrent region a haploinsufficiency and triplosensitivity score from 0 (no evidence) to 3 (sufficient evidence), and it is the substrate that the ACMG/ClinGen CNV scoring framework is built on. The gnomAD constraint table gives pLI and LOEUF, which measure how depleted a gene is of loss-of-function variation in the general population.

bedtools intersect -a cnv.bed -b clingen_dosage_hg38.bed -wa -wb > cnv.dosage.txt

Two interpretive points. LOEUF and pLI speak to loss of function, so they are directly relevant to deletions and only weakly relevant to duplications. And triplosensitivity evidence is far thinner than haploinsufficiency evidence across the genome, so a duplication overlapping a high-pLI gene is not the mirror image of a deletion of the same gene. Whole-gene duplications may be tandem and intact, or they may be inserted elsewhere and disruptive, and short reads usually cannot tell you which.

6. Run AnnotSV and ClassifyCNV

Rather than assembling all of the above by hand, run the two tools that package it. AnnotSV joins dozens of annotation sources onto your variants and emits both full-length and split-by-gene rows:

AnnotSV \
  -SVinputFile cnv.vcf.gz \
  -genomeBuild GRCh38 \
  -annotationMode both \
  -SVminSize 50 \
  -overlap 70 \
  -outputDir annotsv_out \
  -outputFile sample.annotsv.tsv

The -annotationMode both flag gives you one row per variant plus one row per overlapping gene, which is what you want for filtering. The AnnotSV_ranking_score and ACMG_class columns implement the ClinGen scoring rules.

ClassifyCNV implements the same 2019 ACMG/ClinGen framework independently and is worth running as a cross-check, because the two tools weight evidence slightly differently and disagreements flag variants that deserve manual review:

python3 ClassifyCNV.py \
  --infile cnv.bed \
  --GenomeBuild hg38 \
  --precise \
  --outdir classifycnv_out

Use --precise only when your breakpoints came from a breakpoint-aware caller. For depth-based calls with bin-size uncertainty, drop the flag so the tool treats the boundaries as approximate. The output Scoresheet.txt shows the individual evidence codes that summed to the final class, which is more useful than the class itself: it tells you whether a “likely pathogenic” came from a well-established dosage-sensitive gene or from a large gene count heuristic.

One warning that is not technical. Clinical reporting by trained personnel is what these classifications are built for. A class 4 or 5 output on your own data is a reason to consult a clinical geneticist. The output is not a conclusion. Confirmation by an orthogonal assay in an accredited laboratory is standard practice before anything is acted on.

7. Prioritize non-coding and position-effect CNVs

Gene-overlap scoring misses CNVs that change gene regulation without touching coding sequence, typically by deleting an enhancer or disrupting a topologically associating domain (TAD) boundary so that a promoter gains access to enhancers it should never see. TADA is a machine-learning tool that scores CNVs using functional annotations such as TAD structure, conserved elements, and enhancer maps. It is designed to prioritize pathogenic CNVs that gene-centric rules rank low 9. Run it as a complement to ACMG scoring rather than a replacement, and read its output as a ranking rather than a classification.

8. Look at every variant you are going to take seriously

No annotation replaces looking at the alignment. For each candidate, open the region in IGV with soft-clipped reads shown and coverage tracked, or produce a depth plot from your CNVpytor root file:

cnvpytor -root sample.pytor -plot manhattan 100000 -o plots/
cnvpytor -root sample.pytor -plot regions chr7:117000000-117400000 10000 -o plots/

A real heterozygous deletion shows coverage near half the flanking median across the whole interval, an abrupt transition at both ends, and a BAF track with no heterozygous SNPs inside. A false positive from mapping bias shows ragged coverage, boundaries that coincide with segmental duplication annotations, and heterozygous SNPs persisting through the “deleted” region. Tools that combine plotting with structured annotation capture, such as ShinyCNV, exist because this review step is where most calls are rejected and because recording the reason for rejection is what makes the review reproducible 10.

Common problems

Build mismatch is the most frequent and most silent failure. A GRCh37 call set annotated against GRCh38 gene models produces a table full of genes, all of them wrong. Check by taking a known coordinate and verifying the gene it returns. If you must convert, use CrossMap or UCSC liftOver on the BED and expect some intervals to fail or split, and never lift over a CNV and then trust its breakpoints to base pair precision.

Reciprocal overlap thresholds chosen carelessly will either flood your table with spurious frequency matches or leave genuinely common variants looking rare. If you are comparing call sets rather than just annotating, use truvari bench with explicit --pctseq 0 and --pctsize settings, which makes your matching criteria part of the record instead of a buried awk threshold.

Segmental duplications and other repeat classes generate most false CNVs from short reads. Annotate against the UCSC segmental duplication track and the ENCODE exclusion list, and deprioritize anything where more than half the interval falls in either. This is also where multi-allelic gene families live, so a call with copy number 4 in a region where the population ranges from 2 to 8 is a genotype. Such a call is not a variant.

Duplication interpretation is the weak spot of the whole pipeline. Short reads tell you a region is present in extra copies but not where those copies sit or whether they are intact. Long reads or optical mapping resolve this, and comparisons across platforms confirm that structural resolution improves substantially with read length 1. Until you have that data, treat duplication classifications as provisional.

Finally, mosaic and subclonal events break the assumptions of most germline callers. A CNV present in 30 percent of your blood cells produces a depth shift of roughly 15 percent, well inside the noise band of standard segmentation at typical 30x coverage. If you see something with an intermediate depth ratio and a correspondingly subtle BAF shift, do not force it into a germline genotype. Either raise coverage or treat it as an open question for follow-up with a clinician who can order a targeted assay.

Footnotes

  1. Ksenia Lavrichenko, Stefan Johansson, Inge Jonassen. Comprehensive characterization of copy number variation (CNV) called from array, long- and short-read data. BMC Genomics, 2021. https://doi.org/10.1186/s12864-021-08082-3 ↩ ↩2

  2. Faidon-Nikolaos Tilemis, Nikolaos M. Marinakis, Danai Veltra, et al. Germline CNV Detection through Whole-Exome Sequencing (WES) Data Analysis Enhances Resolution of Rare Genetic Diseases. Genes, 2023. https://doi.org/10.3390/genes14071490 ↩

  3. Shu-Yi Su, Julian E. Asher, Marjo-Riita Jarvelin, et al. Inferring combined CNV/SNP haplotypes from genotype data. Bioinformatics, 2010. https://doi.org/10.1093/bioinformatics/btq157 ↩

  4. Ferdinando Bonfiglio, Andrea Legati, Vito Alessandro Lasorsa, et al. Best practices for germline variant and DNA methylation analysis of second- and third-generation sequencing data. Human Genomics, 2024. https://doi.org/10.1186/s40246-024-00684-8 ↩

  5. Janet M. Young, RaeLynn M. Endicott, Sean S. Parghi, et al. Extensive Copy-Number Variation of the Human Olfactory Receptor Gene Family. The American Journal of Human Genetics, 2008. https://doi.org/10.1016/j.ajhg.2008.07.005 ↩

  6. Sanghoon Moon, Kwang Su Jung, Young Jin Kim, et al. KGVDB: a population-based genomic map of CNVs tagged by SNPs in Koreans. Bioinformatics, 2013. https://doi.org/10.1093/bioinformatics/btt173 ↩

  7. Elodie Gazave, Fleur Darré, Carlos Morcillo-Suarez, et al. Copy number variation analysis in the great apes reveals species-specific patterns of structural variation. Genome Research, 2011. https://doi.org/10.1101/gr.117242.110 ↩

  8. Feng-Chi Chen, Yen-Zho Chen, Trees-Juen Chuang. CNVVdb: a database of copy number variations across vertebrate genomes. Bioinformatics, 2009. https://doi.org/10.1093/bioinformatics/btp166 ↩

  9. Jakob Hertzberg, Stefan Mundlos, Martin Vingron, et al. TADA—a machine learning tool for functional annotation-based prioritisation of pathogenic CNVs. Genome Biology, 2022. https://doi.org/10.1186/s13059-022-02631-z ↩

  10. Zhaohui Gu, Charles G Mullighan. ShinyCNV: a Shiny/R application to view and annotate DNA copy number variations. Bioinformatics, 2018. https://doi.org/10.1093/bioinformatics/bty546 ↩

Woolf Software builds longitudinal molecular profiles of individuals: whole-genome sequencing, blood RNA sequencing, and plasma proteomics, delivered as a detailed report plus every data file. Build your profile.