Skip to content

Structural variant annotation

Load structural variants (SVs) with an explicit VCF option, then use the same effects() interface as for small variants.

Basic usage

from varcode import load_vcf

variants = load_vcf(
    "structural_variants.vcf",
    genome=81,  # GRCh38; use the annotation chosen for your input
    parse_structural_variants=True,
)
effects = variants.effects()
for effect in effects:
    if effect.variant.is_structural:
        print(effect.variant.sv_type, effect.short_description)
        print(effect.mutant_protein_sequence)  # may be None

Without parse_structural_variants=True, symbolic alleles and breakends are skipped with a warning. No separate annotator selection is needed. Reference setup explains the genome argument.

Reading SV results

Effects include LargeDeletion, LargeDuplication, Inversion, GeneFusion, and TranslocationToIntergenic. They may contain alternatives in .candidates; see alternative outcomes.

A DNA rearrangement does not by itself establish a complete expressed fusion protein. Sequence may be unknown or partial; None is not an unchanged protein.

Filtering by protein change

retained = effects.drop_silent_and_noncoding()
# Require a positive prediction of protein change, excluding unknowns:
resolved_changes = effects.drop_silent_and_noncoding(keep_unresolved=False)

SV effects report modifies_protein_sequence and modifies_coding_sequence as True (changed), False (unchanged), or None (unresolved). A changed CDS can still encode the same protein. Comparisons use the transcript being annotated; for a fusion, the 5′ partner supplies the initiation site.

The flags cover the whole candidate set: any changed alternative makes the flag True; otherwise an unresolved alternative makes it None. The filter retains the original set, including provenance, rather than selecting one candidate. Unresolved effects are kept by default. To test for a known unchanged SV, use effect.modifies_protein_sequence is False, not not effect.modifies_protein_sequence.

These flags describe existing predictions; they do not establish expression. Partial BND fragments and assemblies without a mapped ORF stay unresolved. So do partial observations (protein_completeness other than start_to_stop) unless a mapped, in-frame observed codon differs from the reference. The filter does not construct missing proteins or change the effect class.

Selenocysteine (U in the Ensembl reference protein) is encoded by UGA, which is decoded as Sec only with a SECIS element in the mRNA's 3′ UTR. Ensembl doesn't annotate SECIS positions. A fusion's stored protein reads Sec unless no selenoprotein 3′ UTR remains (transcript models). For the flags, UGA is read as Sec where a model keeps the transcript intact from that codon through its 3′ end, and as a stop where no selenoprotein 3′ UTR remains. Otherwise the protein flag is reported only if both readings agree, else None; for example, a partial 3′ UTR deletion leaves the protein unresolved. The coding flag compares CDS bases, which don't depend on how Sec is decoded. A start codon other than ATG counts as the initiator methionine, even though Ensembl writes CTG and TTG starts as L.

Fusion protein candidates

For an annotated effect, inspect every compatible partner isoform rather than only the protein on the first result:

from varcode.effects import GeneFusion

for candidate in effect.candidates:
    fusion = candidate.effect
    if isinstance(fusion, GeneFusion):
        print(fusion.five_prime_transcript.id, fusion.three_prime_transcript.id)
        print(fusion.mutant_protein_sequence)  # None when unresolved

There is no candidate-count cap. Distinct transcript pairs remain separate even when their predicted proteins match. The first candidate follows annotation order, not measured likelihood. These are reference-isoform predictions, not every possible splice/phase combination or evidence that a fusion is expressed. See fusion rules and RNA imports.

Which transcripts get annotated

effects() produces one effect per overlapping transcript, as for any variant. What "overlapping" means depends on the SV:

Variant Transcripts annotated
Breakend record (BND) Those at its own breakpoint. The mate's transcripts are annotated from the mate's record.
DEL, DUP, INV, INS, CNV Every transcript overlapping start..end.

Two outcomes apply before any SV logic:

  • No gene at the variant's position(s) → a single Intergenic effect.
  • A non-coding transcript → NoncodingTranscript.

Pairing breakend records

pair_breakends joins the two records of a breakend pair:

  • If both carry the same SVTYPE of DEL, DUP or INV and their kept sides fit it, the result is that typed event, annotated over its whole span. See pairing rules and examples.
  • Otherwise the result is one BND anchored at the record whose VCF ID sorts first, and only that end's transcripts are annotated. If that end is intergenic the combined variant reports just Intergenic; the other end is still reachable through combined.source_variants.

Effect classes

Class Meaning
GeneFusion The SV joins this transcript sense-to-sense with a coding transcript in another gene.
TranslocationToIntergenic A breakend in this transcript that doesn't form a gene fusion.
LargeDeletion A deletion (or <CN0>) removing one or more exons.
LargeDuplication A duplication, insertion or CNV overlapping exons.
Inversion An inversion overlapping exons.
Intronic A span inside the transcript that overlaps no exon.
Intergenic No gene at the variant's position.
NoncodingTranscript The transcript isn't protein-coding.

Limitations

  • Partner isoforms are enumerated but not ranked by RNA support or likelihood.
  • Chains of several SVs aren't assembled into one allele, and regulatory effects (promoter or enhancer hijacking) aren't modeled.
  • Annotating multi-megabase spans can be slow (#407).