Skip to content

How to View Your Own Genome Data in a Genome Browser

Oak
A canyon wall in a glowing forest made of hundreds of stacked bands of luminous moss filaments, with one violet seam running through them.

By the end of this you will have a local genome browser session showing your own aligned reads, your variant calls, your RNA-seq coverage and splice junctions, and a set of public annotation tracks, all on one coordinate system, with none of your data leaving your machine. You need: the output of a sequencing run (a CRAM or BAM plus index, a VCF, optionally RNA-seq BAMs or bigWigs), the matching reference FASTA, roughly 150 GB of free disk for a 30x WGS CRAM plus a reference and a few annotation files, 16 GB of RAM, and samtools, bcftools, and htslib (the tabix/bgzip pair) on your PATH. Budget an hour the first time. Most of that hour goes into confirming which reference build your files were aligned to, which is the single thing that quietly ruins everything downstream if you get it wrong.

Note up front: a genome browser shows you evidence. Reading a variant in IGV tells you what the sequencer saw at that position. It does not tell you what it means for your health. Anything with clinical weight belongs in front of a clinical geneticist or a genetic counselor working from a CLIA-validated report. It does not belong in front of your terminal.

1. Inventory the files and pin down the reference build

Start with the alignment header. This is authoritative in a way that filenames are not.

samtools view -H sample.cram | grep -m3 '^@SQ'
samtools view -H sample.cram | grep '^@PG'

You are looking at two things. First, the SN: naming style: SN:chr1 (UCSC-style) versus SN:1 (Ensembl/GRC-style). Second, the LN: length of chromosome 1. LN:248956422 is GRCh38. LN:249250621 is GRCh37/hg19. If you see LN:248956422 with SN:chr1 and an @SQ line for chr14_KI270726v1_random, you have the UCSC hg38 analysis set. If you see SN:chr1 plus SN:chrEBV, you have the GATK/Broad hg38 bundle, which includes decoy and HLA alt contigs.

Do the same for the VCF:

bcftools view -h sample.vcf.gz | grep -E '^##(reference|contig=<ID=(chr)?1,)'

If the alignment says chr1 and the VCF says 1, they will never line up in a browser. Fix that now with a rename map rather than later with confusion:

printf '1\tchr1\n2\tchr2\n' > chr_map.txt   # generate the full 25-line file programmatically
bcftools annotate --rename-chrs chr_map.txt -Oz -o sample.chr.vcf.gz sample.vcf.gz
bcftools index -t sample.chr.vcf.gz

If your data is on GRCh37 and you want to compare against modern annotation, lift it rather than lifting the annotation. picard LiftoverVcf with CHAIN=hg19ToHg38.over.chain.gz, R=hg38.fa, and a REJECT= file is the standard route. Expect 0.1 to 1 percent of sites to land in the reject file, mostly in segmental duplications and regions where the assembly changed. Do not lift alignments. Realign from FASTQ if you need reads on a new build.

2. Index everything, and verify the indexes

A browser is a random-access client. Without indexes it will either refuse to load or try to read a 60 GB file linearly.

samtools faidx GRCh38.fa                       # produces .fai
samtools index -@ 8 sample.cram                # produces .crai
bcftools index -t sample.vcf.gz                # produces .tbi
tabix -p bed -f annotations.bed.gz             # BED/GFF must be bgzip'd first

The common failure is a BED or GFF compressed with gzip instead of bgzip. tabix will reject it with not a BGZF file. Recompress:

zcat annotations.bed.gz | sort -k1,1 -k2,2n | bgzip -c > annotations.sorted.bed.gz
tabix -p bed annotations.sorted.bed.gz

For files over 512 Mbp on a single contig, or when you want future-proof indexes, use CSI instead of TBI: tabix --csi -p vcf file.vcf.gz. IGV 2.16+ reads CSI fine.

Confirm the CRAM resolves against your FASTA before you open a GUI:

samtools view -T GRCh38.fa -c sample.cram chr17:43044295-43125483

If that returns a read count, the browser will work. If it throws unable to fetch reference sequence, your FASTA is not the one used at alignment time, and no amount of IGV configuration will fix it.

3. Configure IGV desktop before the first launch

We use IGV desktop as the primary viewer for personal data. It is the only one of the common options that reads local CRAM with a local reference, renders base-level mismatches with per-read detail, and never phones home with your coordinates.

Set the heap before launching. IGV ships with a modest default that will OOM on a 30x WGS CRAM when you zoom out. Edit igv.args in the IGV install directory:

-Xmx12g
-XX:+UseG1GC
-Djava.net.useSystemProxies=true

Then set two preferences that matter (Preferences > Alignments, or edit ~/igv/prefs.properties):

SAM.MAX_VISIBLE_RANGE=100
SAM.DOWNSAMPLE_READS=TRUE
SAM.SAMPLING_WINDOW=50
SAM.SAMPLING_READ_COUNT=100
SAM.SHOW_SOFT_CLIPPED=TRUE

MAX_VISIBLE_RANGE=100 (kb) means IGV will render reads up to a 100 kb window. Raising it further on a WGS CRAM is how you hang the UI. SHOW_SOFT_CLIPPED=TRUE is the one preference we always turn on: soft clips are the visual signature of structural variants and of misalignment near indels, and hiding them makes bad calls look clean.

Load your reference as a genome. Use Genomes > Load Genome from File and pick GRCh38.fa with its .fai alongside. Do not use one of the hosted builds unless the hosted build is byte-identical to yours. Hosted hg38 in IGV is the UCSC analysis set. If your CRAM was aligned to the GRC no-alt set with 1-style names, the hosted genome will not match.

Set the CRAM reference cache so samtools-family tools and IGV agree:

export REF_PATH=/data/ref/cache/%2s/%2s/%s
export REF_CACHE=/data/ref/cache/%2s/%2s/%s

4. Load your tracks and set their display so they tell the truth

File > Load from File, then select in this order. Load the CRAM, the VCF, any RNA-seq BAM and then any bigWig. IGV places them in load order, which is the order you will read them.

On the alignment track, right-click and set:

  • Group alignments by: none at first, then “read strand” when you suspect strand bias.
  • Color alignments by: “insert size and pair orientation”. This is the setting that makes a deletion or an inversion visible as a color block instead of as nothing.
  • Shade base by quality: on.
  • View as pairs: on for targeted inspection, off at scale because it doubles memory per read.

On the variant track, set “Color by allele frequency” off and “Color by allele fraction” on if your VCF carries FORMAT/AD. What you want to see is whether a heterozygous call sits near 0.5 or near 0.15. The latter is where mapping artifacts and mosaicism both live, and the browser is how you tell them apart by eye before you do anything more careful.

For quick coverage at whole-chromosome scale, precompute rather than asking IGV to summarize on the fly:

igvtools count -w 25 --pairs sample.cram sample.tdf GRCh38.fa

Or, if you already have deeptools installed and want something you can also use outside IGV:

bamCoverage -b sample.bam -o sample.cpm.bw \
  --binSize 25 --normalizeUsing CPM --numberOfProcessors 8 \
  --ignoreDuplicates --minMappingQuality 20

bigWig is the better format long-term: it is a standard, it streams over HTTP range requests, and every browser reads it. TDF is IGV-only.

5. Add public annotation tracks locally, without uploading your data

This is the step where people leak their genome. UCSC’s “custom track” and “My Data” features upload your file to UCSC servers. NCBI Genome Data Viewer likewise accepts uploads. Both are excellent tools for public reference data and poor choices for your personal VCF. The safe pattern reverses the direction: download the public annotation to your machine and load it next to your private data in a local browser.

Useful tracks, and how to get them in a form IGV will index:

  • ENCODE candidate cis-regulatory elements (SCREEN), as a bigBed. These give you promoter, enhancer, and CTCF-bound regions across cell types, which is the context you need when a variant lands outside a coding exon. The ENCODE consortium’s integrated annotation assigned biochemical function to roughly 80 percent of the genome, most of it regulatory rather than protein-coding, which is why an exon-only view of a personal genome misses most of what is there.1
  • GENCODE basic annotation GTF, bgzipped and tabix-indexed with -p gff.
  • ClinVar sites VCF from NCBI. Note that NCBI ships it with 1-style chromosome names on GRCh38, so run the --rename-chrs step from step 1 if your genome uses chr.
  • gnomAD sites VCF. The full v4 genomes release is large enough that you should not download it. Load it by URL: IGV will stream a remote bgzipped VCF if the .tbi sits next to it at the same URL. That sends only the coordinates you are viewing. It does not send your data.

For interpreting somatic or tumor-derived data, the mutation and expression patterns catalogued by large tumor cohorts are the reference frame. No individual genome serves as that frame. TCGA’s clear cell renal cancer analysis is a good example of the kind of cohort-level structure a single browser session cannot show you.2

6. RNA-seq: coverage plus junctions, because reads alone are not enough

RNA-seq in a browser is mostly about exon-intron structure. Load the BAM, then right-click the track and choose “Sashimi Plot”. IGV computes junction arcs from the N CIGAR operations and labels each arc with its supporting read count.

If you aligned with STAR, you already have SJ.out.tab. Convert it to a BED that IGV will draw as a junction track:

awk 'BEGIN{OFS="\t"} $7>=3 {
  strand = ($4==1 ? "+" : ($4==2 ? "-" : "."));
  print $1, $2-1, $3, "JUNC"NR, $7, strand
}' SJ.out.tab | sort -k1,1 -k2,2n | bgzip -c > junctions.bed.gz
tabix -p bed junctions.bed.gz

The $7>=3 filter drops junctions with fewer than three uniquely-mapping supporting reads. Without it you get a hairball. Raise it to 10 for a first pass on a deeply sequenced sample.

The pairing that earns its keep is the DNA variant track, RNA coverage track, junction track and GENCODE track, all stacked. A splice-region variant that changes junction usage is visible as a coordinated shift across those four rows in a way no table shows you.

7. Make the view reproducible with an IGV batch script

Clicking is fine for exploration and useless for anything you want to repeat. IGV reads batch scripts over a socket or from the command line.

new
genome /data/ref/GRCh38.fa
load /data/sample.cram
load /data/sample.chr.vcf.gz
load /data/rna.cpm.bw
load /data/junctions.bed.gz
snapshotDirectory /data/snapshots
maxPanelHeight 1200
goto chr17:43,044,295-43,125,483
sort base
snapshot BRCA1_locus.png
goto chr7:117,480,000-117,670,000
snapshot CFTR_locus.png
exit

Run it headless:

igv.sh --batch view.bat --igvDirectory /data/igv_prefs

On a server without a display, wrap it in xvfb-run -a. This is how you generate a PNG for every variant in a candidate list and review 200 of them in a contact sheet instead of 200 clicks.

8. Self-host igv.js when you want a shareable view

igv.js renders the same track types in a browser tab from static files plus HTTP range requests. Useful when you want to hand a specific locus to a collaborator without handing over the whole dataset.

The failure that costs people an afternoon: Python’s http.server does not implement HTTP range requests, so igv.js will either load nothing or attempt full-file downloads. Use a server that does.

npx http-server /data/tracks -p 8080 --cors -c-1

Then a minimal page:

igv.createBrowser(document.getElementById("igv-div"), {
  reference: {
    id: "GRCh38",
    fastaURL: "http://localhost:8080/GRCh38.fa",
    indexURL: "http://localhost:8080/GRCh38.fa.fai"
  },
  locus: "chr17:43,044,295-43,125,483",
  tracks: [
    { type: "alignment", format: "cram",
      url: "http://localhost:8080/sample.cram",
      indexURL: "http://localhost:8080/sample.cram.crai" },
    { type: "variant", format: "vcf",
      url: "http://localhost:8080/sample.vcf.gz",
      indexURL: "http://localhost:8080/sample.vcf.gz.tbi" }
  ]
});

Put it behind auth if it is reachable from anywhere but localhost. A directory of CRAMs on an open port is a directory of genomes on an open port.

9. Sanity-check a variant before you believe it

The point of the browser is adjudication. For any variant you care about, look for these in the reads:

  • Allele fraction near 0.5 for a het, near 1.0 for a hom. A het at 0.2 with 40x coverage is usually a mapping artifact or a nearby indel.
  • Support on both strands. All-forward support at a site with bidirectional coverage is a red flag.
  • MAPQ. Hover a read: MAPQ 0 means the aligner could not place it uniquely. A pile of MAPQ 0 reads is a segmental duplication. Such a pile is not a variant.
  • Soft clips converging on one coordinate. That is a breakpoint, and a small-variant caller will describe it badly.
  • Coverage in the flanking 500 bp. A sharp coverage cliff means a CNV boundary, and variant calls inside a CNV need different math.

None of this is interpretation of significance. It is quality control on the measurement. The genome was mapped and sequenced as a community project over more than a decade precisely so individual sequences could be placed on a shared coordinate system,3 and a browser is where you check that your sequence landed there correctly.

Common problems

IGV shows an empty track with the reads clearly present in samtools view. Almost always a chromosome-naming mismatch between genome and track. Check samtools view -H against the genome’s .fai first line.

Error: Unable to load CRAM reference. The .fa you loaded is not the one used at alignment. CRAM stores only differences from the reference, so it is unforgiving here. Find the original FASTA, or set REF_PATH to a cache that can fetch by MD5.

The UI freezes when you zoom out. SAM.MAX_VISIBLE_RANGE is too high, or downsampling is off. Both are in Preferences > Alignments. A 30x WGS CRAM at 1 Mbp visible range is roughly 300,000 reads to render.

tabix says the region is not sorted. Sort with sort -k1,1 -k2,2n for BED, or bcftools sort for VCF, then re-bgzip. Sorting a bgzipped file with plain sort on the compressed bytes does nothing useful.

gnomAD loads by URL but nothing appears. The remote index must be at the URL you gave plus .tbi, and the server must support range requests. Test with curl -r 0-100 -o /dev/null -w '%{http_code}\n' <url>. You want 206, not 200.

Two VCFs from different callers disagree. That is normal and informative. Load both as separate tracks over the same alignment and look at the reads. The alignment is the evidence, and the VCFs are two opinions about it.

A variant looks convincing and matches something alarming in ClinVar. Stop and take it to a clinical geneticist. Research-grade sequencing is not a diagnostic test, ClinVar entries vary widely in evidence quality, and confirmatory testing in a clinical lab is the step that makes a finding real.

Footnotes

  1. The ENCODE Project Consortium. An integrated encyclopedia of DNA elements in the human genome. Nature, 2012. https://doi.org/10.1038/nature11247 ↩

  2. The Cancer Genome Atlas Research Network. Comprehensive molecular characterization of clear cell renal cell carcinoma. Nature, 2013. https://doi.org/10.1038/nature12222 ↩

  3. P.S Harper. Mapping and sequencing the human genome. Endeavour, 1989. https://doi.org/10.1016/0160-9327(89)90035-5 ↩

Oak 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.