Skip to content

How to Lift Genome Annotations Between Assemblies

Oak
A bridge of glowing organic links spans a forest gorge, carrying luminous pods between two striped cliff faces at night.

By the end of this you will have your own data expressed in a single target assembly. That means a VCF of variants, a set of BED intervals, and a GFF3 gene annotation. You will also have two audit files you should keep. The first is the list of records that failed to lift. The second is a per-gene comparison showing which models changed sequence or structure in transit. You need the FASTA for both the source and target assemblies, the matching UCSC chain file, and a machine with about 16 GB of RAM and 100 GB of free disk. The software is the following.

  • bcftools 1.20 or newer with the liftover plugin
  • CrossMap 0.7+
  • UCSC liftOver
  • minimap2
  • Liftoff
  • LiftOn
  • LiftoffTools

Everything below assumes human data, but the same mechanics apply to any pair of assemblies of the same or a closely related species.

1. Decide whether you are lifting coordinates or gene models

These are two different problems and people conflate them constantly. Lifting coordinates means taking a position (chr1:154,000,100 in GRCh37) and asking where the equivalent base sits in GRCh38. That question is answered by a chain file, which is a precomputed pairwise alignment between the two assemblies broken into collinear blocks. It is fast and it is stateless, and it fails silently in exactly the regions you care most about. Chain files are thin or absent where the assemblies genuinely disagree.

Lifting gene models means taking an annotation (exon structures, coding frames, transcript boundaries) and reconstructing it on the target assembly. Chain-based tools handle this badly, because they lift each exon independently and have no notion that the result must still encode an open reading frame. Liftoff instead extracts the sequence of each reference gene, aligns it to the target assembly with minimap2, and then finds the exon placement that maximizes sequence identity while preserving gene structure 1. That difference matters: the alignment is done gene by gene, so an intron that grew by a repeat expansion does not break the model.

The practical rule we use is simple. Variants, intervals, peaks, and any coordinate-only track go through a chain file. Anything with exon structure or a reading frame goes through Liftoff or LiftOn. If you are building an annotation for an assembly that has no chain file at all, Liftoff is the only option of the two.

2. Assemble the inputs

Get the references and the chains from the source of record. Do not take them from a mirror in someone’s Drive folder. For the common human transitions:

# References
wget https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.fa.gz
wget https://hgdownload.soe.ucsc.edu/goldenPath/hs1/bigZips/hs1.fa.gz   # T2T-CHM13v2.0

# Chains
wget https://hgdownload.soe.ucsc.edu/goldenPath/hg19/liftOver/hg19ToHg38.over.chain.gz
wget https://hgdownload.soe.ucsc.edu/goldenPath/hg38/liftOver/hg38ToHs1.over.chain.gz

gunzip -c hg38.fa.gz > hg38.fa && samtools faidx hg38.fa

Two naming traps will cost you an afternoon if you skip them. First, UCSC references use chr1 and chrM. Ensembl and the Broad’s b37 bundle use 1 and MT. Chain files are UCSC-style. Normalize with bcftools annotate --rename-chrs and a two-column map file before lifting, then rename back afterwards if your downstream tools expect Ensembl contigs.

Second, the mitochondrial genome is not liftable by chain in the hg19 case. UCSC hg19 chrM is the older 16,571 bp sequence, while GRCh38 and the Broad’s b37 both use the 16,569 bp revised Cambridge Reference Sequence. Drop chrM before lifting and handle it separately, ideally by realigning the reads rather than translating coordinates.

3. Lift a VCF from GRCh37 to GRCh38

The tool we reach for is the bcftools +liftover plugin, because it does the two things naive liftover gets wrong: it checks the lifted REF allele against the target reference and swaps REF and ALT when the assemblies differ at that base, and it re-normalizes indels after the move.

bcftools +liftover --no-version -Ou variants.hg19.vcf.gz -- \
    -s hg19.fa \
    -f hg38.fa \
    -c hg19ToHg38.over.chain.gz \
    --reject rejected.hg19.vcf.gz \
    --reject-type z \
    --write-src \
  | bcftools sort -Oz -o variants.hg38.vcf.gz -
bcftools index variants.hg38.vcf.gz

--write-src stores the original CHROM, POS, REF, and ALT in INFO fields, which is the single most useful habit in this whole workflow: when a downstream result looks strange you can always ask where the record came from. --reject is not optional. Read that file. For a 4–5 million variant whole-genome VCF, expect on the order of tens of thousands of rejected records. They are concentrated in centromeric, acrocentric, and segmental-duplication regions.

Then normalize and count what happened:

bcftools norm -f hg38.fa -c w -m -any -Oz -o variants.hg38.norm.vcf.gz variants.hg38.vcf.gz
bcftools view -H rejected.hg19.vcf.gz | cut -f1 | sort | uniq -c | sort -rn | head -20

The -c w flag warns rather than fails on REF mismatches so you can quantify them. A handful is normal. Thousands means your source FASTA does not match the assembly your VCF was called against, and you should stop and fix that before going further.

Picard’s LiftoverVcf is the alternative and is still the reference implementation many pipelines use. RECOVER_SWAPPED_REF_ALT=true is required if you want ref/alt swaps handled, and it rewrites genotypes accordingly. It is slower and more memory-hungry than the bcftools plugin on whole-genome inputs, and it will not re-normalize indels for you.

A note on array data, since many people start with a genotyping chip export. Chip genotypes are reported on a manufacturer strand and not necessarily the reference strand, and A/T and C/G sites are strand-ambiguous. Lifting the coordinates does not resolve the strand question. Convert to VCF against the source build first with a strand file, verify allele frequencies against a population panel, then lift.

4. Lift intervals and coordinate tracks

For BED, GTF-as-intervals, bigWig, and similar, CrossMap is the most convenient front end because it keeps the extra columns intact and handles several formats through one interface:

CrossMap bed hg19ToHg38.over.chain.gz regions.hg19.bed regions.hg38.bed
CrossMap bigwig hg19ToHg38.over.chain.gz signal.hg19.bw signal.hg38

Unmapped records go to regions.hg38.bed.unmap. If you prefer the UCSC binary, these are the flags that matter. The -minMatch flag sets the fraction of bases that must remap, with a default of 0.95. The -bedPlus=N flag preserves columns beyond the first N, and -tab forces tab parsing when a name field contains spaces. The -multiple flag emits every placement when a region maps to more than one target locus:

liftOver -bedPlus=6 -tab -minMatch=0.95 regions.hg19.bed \
    hg19ToHg38.over.chain.gz regions.hg38.bed regions.unmapped.bed

Lower -minMatch to about 0.8 only for long intervals where partial mapping is acceptable, such as broad chromatin domains. Never lower it for small features like transcription factor motifs or exons. And be careful with epigenomic tracks in particular: large integrative annotations of the human genome are built on a specific assembly and on datasets aligned to it, so a lifted chromatin state segmentation inherits both the chain’s errors and the original mapping’s assembly-specific biases 2.

5. Lift a gene annotation with Liftoff

Now the gene models. Liftoff takes a reference FASTA, its GFF3 or GTF, and a target FASTA. It then rebuilds the annotation on the target:

liftoff \
  -g gencode.v44.annotation.gff3 \
  -o gencode.v44.chm13.gff3 \
  -u unmapped_features.txt \
  -dir /scratch/liftoff_intermediate \
  -p 16 \
  -a 0.5 -s 0.5 \
  -copies -sc 0.95 \
  -polish \
  chm13v2.0.fa GRCh38.p14.fa

The parameters worth understanding are as follows. -a is the minimum fraction of a gene’s exonic bases that must align for the mapping to be accepted, and -s is the minimum sequence identity for an exon alignment. Both default to 0.5. -copies with -sc 0.95 looks for additional paralogous copies of each gene in the target that are at least 95% identical to the reference copy. That is how you recover extra copies of genes in segmental duplications that a chain file would collapse. -polish re-runs the alignment around each protein-coding gene to try to restore a valid start codon, stop codon, and frame. -flank 0.1 adds flanking sequence to the alignment and sometimes rescues genes at contig edges.

Liftoff was designed precisely for this case, mapping annotations between assemblies of the same species, and it preserves gene structure rather than lifting exons independently 1. It reports unmapped genes explicitly in the file you pass to -u, which is your first quality signal. Read it. Genes that fail to map in a same-species lift are usually in regions the two assemblies represent differently. That is information about the assemblies and should not be discarded as noise 3.

Runtime for GENCODE onto a human-scale target is on the order of an hour with 16 threads, dominated by minimap2. The -dir intermediate directory is reusable across runs with different parameters, which saves most of that time when you are tuning.

6. Refine protein-coding models with LiftOn

Liftoff optimizes DNA alignment, which means a lifted transcript can be nearly perfect at the nucleotide level and still encode a truncated or frameshifted protein. LiftOn addresses this by combining the DNA alignment with a protein alignment (via miniprot) and choosing the exon structure that best preserves the reference protein sequence, which measurably improves protein-coding gene models relative to DNA-only lifting 4.

lifton \
  -g gencode.v44.annotation.gff3 \
  -o gencode.v44.chm13.lifton.gff3 \
  -P gencode.v44.pc_translations.fa \
  -copies \
  -t 16 \
  chm13v2.0.fa GRCh38.p14.fa

LiftOn emits a per-transcript protein identity score, and we treat anything below about 0.95 as a model to inspect by hand rather than to trust. Our default is to run LiftOn for protein-coding genes and keep the Liftoff output for non-coding features, then merge. If you only run one tool, run LiftOn.

Neither tool invents annotation. Both propagate an existing one, with all of its errors, which is a general property of lift-based annotation and one reason the field is moving toward direct evidence-based annotation of each new assembly 5. Independent gene sets for a new assembly, built from long-read RNA sequencing and protein evidence, remain the higher-quality option when the sequencing data exist 6.

7. Audit the result

Do not ship a lifted annotation you have not compared against its source. LiftoffTools exists for exactly this and runs three comparisons: sequence-level variants introduced between the reference and lifted copies, changes in gene synteny and order, and changes in paralog cluster membership 7.

liftofftools all \
  -r GRCh38.p14.fa -t chm13v2.0.fa \
  -rg gencode.v44.annotation.gff3 \
  -tg gencode.v44.chm13.lifton.gff3 \
  -dir liftofftools_out

The variants report is the one to read first. It classifies each lifted protein-coding gene by the most severe consequence of the sequence differences between assemblies. The classes are the following.

  • synonymous
  • nonsynonymous
  • frameshift
  • start-lost
  • stop-gained
  • stop-lost

A frameshift or premature stop in the lifted copy of a gene you care about is a flag to check manually, and it is often a real difference between the assemblies rather than a lifting error.

Add two cheap independent checks. First, count features before and after by type and confirm the losses match your unmapped file:

for f in source.gff3 lifted.gff3; do
  echo "$f"; awk '$1!~/^#/{print $3}' $f | sort | uniq -c | sort -rn | head
done

Second, spot-check a known locus with unusual structure. Pick a gene in a segmental duplication and confirm the copy number in your lifted annotation matches expectation for the target assembly.

8. When to realign instead of lift

Lifting is an approximation of an answer that alignment can give exactly. If you have the raw reads, aligning them to the target assembly and recalling variants is strictly better than lifting a VCF, and the gap is largest in the regions where lifting fails. The T2T-CHM13 assembly adds roughly 200 Mb of sequence that has no counterpart in GRCh38, so no chain can place anything there.

There is a middle path worth knowing. Aligning reads to a complete reference and then lifting the read alignments back to the older coordinate system improves mapping accuracy relative to aligning directly to the older reference, because the complete reference absorbs reads that would otherwise be forced into the wrong place 8. In practice that means: align to CHM13, then lift the BAM to GRCh38 if your downstream tools require GRCh38 coordinates, rather than aligning to GRCh38 and lifting the variants.

If any of this feeds into a health decision, the interpretation belongs with a clinician and a clinical laboratory. A lifted coordinate is a computational claim about two assemblies, and clinical variant interpretation needs the variant confirmed on the assembly and in the assay the reporting laboratory validated.

Common problems

A high REF mismatch rate after lifting a VCF almost always means the source FASTA does not match the assembly the variants were called against. The usual culprits are hg19 versus b37 (chrM and contig naming differ) and analysis sets with decoy or EBV contigs. Verify by checking a few hundred records’ REF alleles against the source FASTA before you lift anything.

Records that lift to an alternate or patch contig will quietly move your variant out of the primary assembly. Filter the output to the primary chromosomes, or restrict the chain to them, and decide deliberately what to do with the rest.

Duplicated output positions happen when two source intervals map to one target interval or when -multiple is on. Downstream tools that assume a sorted, unique VCF will behave unpredictably. Always pipe through bcftools sort and check with bcftools norm -d exact.

Genes that Liftoff maps with high coverage but broken reading frames usually reflect a real assembly difference or a reference annotation error. They do not usually reflect a tool failure. Run LiftOn on those specific genes with protein evidence before concluding anything about the biology 4.

Interval features smaller than the chain block granularity, such as short motifs or single-base methylation calls, can lift with apparently high confidence into positions that are off by a few bases at block boundaries. For base-resolution data, verify a sample of lifted positions by checking that the reference base at the target coordinate matches the source.

Finally, keep the provenance. Every lifted file should carry the chain file name, the tool version, and the source coordinates in its header or INFO fields. Six months from now, the question “which build is this in” should be answerable from the file itself.

Footnotes

  1. Alaina Shumate, Steven L Salzberg. Liftoff: accurate mapping of gene annotations. Bioinformatics, 2021. https://doi.org/10.1093/bioinformatics/btaa1016 ↩ ↩2

  2. Ha Vu, Jason Ernst. Universal annotation of the human genome through integration of over a thousand epigenomic datasets. Genome Biology, 2022. https://doi.org/10.1186/s13059-021-02572-z ↩

  3. Alaina Shumate, Steven L. Salzberg. Liftoff: an accurate gene annotation mapping tool. 2020. https://doi.org/10.1101/2020.06.24.169680 ↩

  4. Kuan-Hao Chao, Jakob M. Heinz, Celine Hoh, et al. Combining DNA and protein alignments to improve genome annotation with LiftOn. Genome Research, 2024. https://doi.org/10.1101/gr.279620.124 ↩ ↩2

  5. Hyun Joo Ji, Mihaela Pertea, Steven L. Salzberg. Annotating genomes at increased scale and resolution. Nature Reviews Genetics, 2026. https://doi.org/10.1038/s41576-026-00937-3 ↩

  6. Roderic Guigó. Genome annotation: From human genetics to biodiversity genomics. Cell Genomics, 2023. https://doi.org/10.1016/j.xgen.2023.100375 ↩

  7. Alaina Shumate, Steven Salzberg. LiftoffTools: a toolkit for comparing gene annotations mapped between genome assemblies. F1000Research, 2024. https://doi.org/10.12688/f1000research.124059.2 ↩

  8. Nae-Chyun Chen, Luis F. Paulin, Fritz J. Sedlazeck, et al. Improved sequence mapping using a complete reference genome and lift-over. Nature Methods, 2023. https://doi.org/10.1038/s41592-023-02069-6 ↩

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.