Skip to content

Genome Loci: Reviewing Variant Calls in igv.js

Show a genome locus in igv.js inside Potato with a local reference and BAM, CRAM, VCF or BED tracks, and have reviewers mark each variant call real or an artifact.

New in v2.12.0

The genome_locus display opens a region of a genome in igv.js with the tracks a reviewer needs: the reference sequence, read alignments, variant calls, annotations and signal. Its usual job is manual variant review, where an annotator looks at the reads under a call and answers an ordinary radio question such as real, artifact or uncertain. A viewer_selection scheme can also record the locus the annotator was viewing and any calls or features they clicked.

  • Engine: igv.js 3.8.9 ships with Potato. It draws with canvas 2D, so it does not need WebGL.
  • Files are served from the media directory. The /media/ route answers HTTP range requests, so igv.js reads an indexed BAM, CRAM, VCF or FASTA a slice at a time. Gzipped files are sent as their compressed bytes, which is what a tabix-indexed .vcf.gz or .bed.gz needs.
  • Reference: an indexed FASTA in the media directory (.fa with its .fai). igv's built-in genome IDs (hg38, mm39) download their files from the internet, so they are accepted only with allow_remote: true.
  • Offline by default: with a local reference and local tracks, the page loads nothing from outside the Potato server, so the task runs on an air-gapped network.

Configuration

yaml
media_directory: media
data_files: [data/calls.json]
item_properties: {id_key: id, text_key: note}
 
instance_display:
  fields:
    - key: note
      type: text
    - key: locus                 # item field holding the locus
      type: genome_locus
      label: "Locus"
      display_options:
        reference:
          fasta: genome/ref.fa           # .fai next to it
          name: ref
        flank: 60
        tracks:
          - name: "Calls"
            url: calls/cohort.vcf.gz     # same file for every item; .tbi next to it
          - name: "Reads"
            field: bam                   # an item field names the file
            height: 260
          - name: "Regions"
            url: annotation/regions.bed
 
annotation_schemes:
  - annotation_type: radio
    name: call_review
    description: "Is this variant call real?"
    labels: ["Real", "Artifact", "Uncertain"]
    label_requirement:
      required: true
 
  - annotation_type: multiselect
    name: evidence_seen
    description: "What did you see in the reads?"
    labels:
      - "Alt allele on both strands"
      - "Alt allele on one strand only"
      - "Low base quality at the alt"
      - "In a homopolymer or repeat"
 
  - annotation_type: viewer_selection
    name: evidence
    description: "Evidence (optional): click the call or region you based this on"
    viewer_field: locus
    max_selections: 4
json
{"id": "call_1", "locus": "chr1:1201", "note": "C>G at chr1:1,201", "bam": "reads/sample1.bam"}

The locus field takes the forms igv's locus box takes, 1-based and inclusive: chr1:1,450-1,550 opens that range, chr1:1500 opens position 1500 with flank bases on each side, and chr1 opens the whole sequence. With a local reference, the sequence name is checked against the .fai, and an item naming a sequence the reference lacks shows a message listing the sequences it has.

Display optionDefaultMeaning
referencenone{fasta: <path>, index: <path>, name: <label>}. index defaults to the FASTA path plus .fai
genomenoneAn igv genome ID. Needs allow_remote: true. Give reference or genome, not both
tracks[]The track list
flank50Bases shown on either side of a one-position locus
select_featurestrueClicking a call or feature toggles it in the selection instead of opening igv's popup
allow_remotefalseAllows http(s) URLs for the reference and tracks, and genome IDs

Each track takes name, and either url (a file in the media directory, the same for every item) or field (an item field holding the file, so each item brings its own). format is bam, cram, vcf, bed, gff3, gff, gtf, bigbed, bigwig, bedgraph or wig, taken from the extension when left out. index or index_field names the index, defaulting to .bai, .crai or .tbi beside the file. height, color and display_mode pass through to igv.

A field track's value is checked in the browser before it loads. An empty value, a path outside the media directory, a value that is not a path, a remote URL when allow_remote is off, or a name whose format cannot be told keeps the track off the view, and a line under the viewer names the track and the reason.

Preparing the files

FileIndexTypical commands
Reference FASTA.faisamtools faidx ref.fa
Reads (BAM, CRAM).bai, .craisamtools sort -o s.bam in.bam && samtools index s.bam
Calls (VCF).tbibgzip calls.vcf && tabix -p vcf calls.vcf.gz
Regions (BED, GFF3).tbi, or none for a small uncompressed filebgzip regions.bed && tabix -p bed regions.bed.gz

Use the reference the reads were aligned to, with the same sequence names, since chr1 and 1 are different sequences to igv. A .vcf.gz made with plain gzip cannot be indexed, so recompress it with bgzip. For a review set drawn from a large cohort, slice the BAMs to the regions under review so the media folder stays small. A reverse proxy that adds Content-Encoding or strips Range breaks indexed reads, so serve /media/ unchanged.

Reviewing a call

The annotator reads the call in the note, then looks at the reads at the centre of the view, where igv colours mismatching bases and the coverage bar shows the allele fractions. They can pan by dragging, zoom with the buttons or the wheel, and type a locus into igv's search box. With select_features on, clicking a call or feature selects it, shades it in the view and lists it under the viewer_selection question. Opening an item does not count as an answer, and the radio shortcuts (1, 2, 3) work while igv is on the page.

Stored answer

json
{"viewer": "genome_locus",
 "locus": "chr1:1141-1261",
 "items": [{"chr": "chr1", "start": 1201, "end": 1201, "kind": "variant",
            "name": "call_1", "ref": "C", "alt": "G", "track": "Calls"}]}

locus is the view the annotator last left the item at. start and end are 1-based and inclusive, so a BED feature chr1 900 2400 is stored as start: 901, end: 2400. kind is variant for a call and feature for an annotation-track feature. The radio and multiselect answers export like any other scheme, and the viewer_selection JSON is the scheme's value in the jsonl, csv and tsv exports. To find calls reviewers disagree on, group the call_review answers by item ID, or use the agreement report on the radio scheme.

Example

examples/advanced/variant-review/ has a synthetic genome, 144 simulated reads and four calls:

bash
python potato/flask_server.py start examples/advanced/variant-review/config.yaml -p 8000

Two of its calls are supported by the reads. One is carried only by reverse-strand reads at low base quality, and one is a one-base deletion inside a poly-A run, so the example shows each answer.

Further Reading

For implementation details, see the source documentation on genome loci and variant review.