# Genome Loci: Reviewing Variant Calls in igv.js

Source: https://www.potatoannotator.com/docs/annotation-types/genome-loci

*New in v2.12.0*

The `genome_locus` display opens a region of a genome in [igv.js](https://github.com/igvteam/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 option | Default | Meaning |
|---|---|---|
| `reference` | none | `{fasta: <path>, index: <path>, name: <label>}`. `index` defaults to the FASTA path plus `.fai` |
| `genome` | none | An igv genome ID. Needs `allow_remote: true`. Give `reference` or `genome`, not both |
| `tracks` | `[]` | The track list |
| `flank` | `50` | Bases shown on either side of a one-position locus |
| `select_features` | `true` | Clicking a call or feature toggles it in the selection instead of opening igv's popup |
| `allow_remote` | `false` | Allows 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

| File | Index | Typical commands |
|---|---|---|
| Reference FASTA | `.fai` | `samtools faidx ref.fa` |
| Reads (BAM, CRAM) | `.bai`, `.crai` | `samtools sort -o s.bam in.bam && samtools index s.bam` |
| Calls ([VCF](https://en.wikipedia.org/wiki/Variant_Call_Format)) | `.tbi` | `bgzip calls.vcf && tabix -p vcf calls.vcf.gz` |
| Regions (BED, GFF3) | `.tbi`, or none for a small uncompressed file | `bgzip 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](/docs/guides/inter-annotator-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

- [Protein Structures](/docs/annotation-types/protein-structures) - Residue selection in 3Dmol.js or Mol*
- [Instance Display](/docs/core-concepts/instance-display) - How display fields are configured
- [Air-Gapped Annotation](/docs/guides/air-gapped-annotation) - Running Potato with no internet access

For implementation details, see the source documentation on [genome loci](https://github.com/davidjurgens/potato/blob/master/docs/annotation-types/science/genome_loci.md) and [variant review](https://github.com/davidjurgens/potato/blob/master/docs/guides/genomics-variant-review.md).
