Annotations¶
An annotation says where the genes, transcripts and exons sit on an assembly's coordinates. It is a GTF file, one line per feature, and registering it builds a SQLite database next to that text so later queries do not have to re-read it. A genome can hold several annotations, each under a short name.
from genome import Genome
worm = Genome("ce11")
worm.annotations.registered # ['wormbase_ws298']
worm.default_gtf # 'wormbase_ws298'
Matching gene ids from a published table onto an annotation's own is covered on Gene identifiers.
Registering an annotation¶
Name one the annotation table lists for this assembly:
That fetches the GTF from the URL the table's row pins, checks the unpacked file against the sha256 in the same row, checks its chromosome names against the assembly's, and builds the database. The record saying all of it finished is written last.
The files land in the shared data directory, where every project on the machine reads the same copy.
Each annotation gets its own directory, <assembly dir>/gtf/<name>/, holding the GTF, the
.db and that record.
How long it takes depends on the size of the GTF. Yeast's ensgene_v101 is a 5 MB download
and the whole registration finishes in about a second. Worm's wormbase_ws298 is 183 MB and
builds a 452 MB database, which is 21 seconds of work once the file is on disk, plus the
download. Registering a name that is already registered returns straight away: nothing is
fetched, nothing is rebuilt.
The same work runs from a shell:
$ genome annotation register ce11 wormbase_ws298
registered wormbase_ws298 for ce11 in /Users/hanqing/liulab_data/genome/ce11/gtf/wormbase_ws298
source https://ftp.ebi.ac.uk/pub/databases/wormbase/releases/WS298/species/c_elegans/PRJNA13758/c_elegans.PRJNA13758.WS298.canonical_geneset.gtf.gz
sha256 f6e4569453a80908e1182039e9af5fc0184d2607db582e57788e979b40b8ca81
files wormbase_ws298.db, wormbase_ws298.gtf
chromosomes checked — every name the GTF uses is one the assembly carries
The first use downloads. The lab's CPU compute nodes have no internet, so run genome annotation register hg38 gencode_v50 once on a login node before submitting a job that needs it.
What is registered, offered or broken¶
worm.annotations holds every annotation this assembly has. Three lists say where each one
stands:
registered— built on this machine and trustworthyoffered— listed in the annotation table for this assembly, whether or not anyone here has built itbroken— a directory that is here and cannot be trusted; each entry says what is wrong with it and names the command that repairs it
worm.annotations.registered # ['wormbase_ws298']
worm.annotations.broken # []
[row.name for row in worm.annotations.offered] # ['wormbase_ws298']
A name in none of the three is the fourth state: not here, and not offered either. There is
no len(), no in and no iterating over worm.annotations, so name the list you mean.
if worm.annotations: is always true and asks nothing; if worm.annotations.registered:
is the question.
genome annotation list prints what is here against what the table offers:
$ genome annotation list ce11
annotations for ce11 in /Users/hanqing/liulab_data/genome/ce11
wormbase_ws298 registered WormBase WS298
default: wormbase_ws298
path gives the GTF's location, ready to hand to another tool:
worm.annotations.path("wormbase_ws298")
# PosixPath('/Users/hanqing/liulab_data/genome/ce11/gtf/wormbase_ws298/wormbase_ws298.gtf')
Asking for an annotation that is not registered raises AnnotationNotRegisteredError. The
message names the command that repairs it.
Registering your own GTF¶
For a GTF the table does not list, hand over the path and a name to address it by:
A .gz source is decompressed on the way in and a plain GTF is copied as it is. No checksum
is compared, because an unlisted GTF has none pinned for it. Everything else matches a
listed annotation: the same directory, the same chromosome check, the same database, the
same record. my_genes is addressed the same way from then on, and genome annotation list
shows it as registered but not offered.
The shell spelling takes the assembly, the file and the name, in that order:
The default annotation¶
default_gtf is what a call gets when it names no annotation: the default_gtf= passed at
construction, else the one the table flags for this assembly, else the sole registered
annotation, else None.
worm.default_gtf # 'wormbase_ws298'
worm.default_gtf_path
# PosixPath('/Users/hanqing/liulab_data/genome/ce11/gtf/wormbase_ws298/wormbase_ws298.gtf')
Naming a default does not register it. Opening a genome starts no download, so on a machine
where nothing has been registered yet the default points at an annotation that is not there.
That is the usual state of a fresh machine and not an error. genome annotation list reports
the gap without raising:
$ genome annotation list hg38
annotations for hg38 in /Users/hanqing/liulab_data/genome/hg38
gencode_v50 offered, not registered GENCODE v50
default: gencode_v50 — not registered here; register it with `genome annotation register hg38 gencode_v50`
default_gtf_path resolves that name to a file, so asking for a default that is not
registered raises AnnotationNotRegisteredError. To pick a different default, pass it at
construction:
When a registration is refused¶
Every chromosome name the GTF uses has to be one the assembly carries. The check is one streaming pass over the GTF and runs before the database build, so a mismatch fails in seconds. An Ensembl-spelled GTF against a UCSC-spelled assembly is the common case:
sacCer3 = Genome("sacCer3")
sacCer3.annotations.register_path("ensembl.gtf", "ensembl_spelled")
# ChromosomeMismatchError: the GTF for 'ensembl_spelled' names 2 chromosomes the
# assembly does not carry: 1, MT. An annotation and its assembly must spell
# chromosomes the same way, and the usual cause is a UCSC-versus-Ensembl mismatch
# ('chr1' against '1', 'chrM' against 'MtDNA'). The assembly carries: chrI, chrII,
# chrIII, chrIV, chrIX, chrM, chrV, chrVI, chrVII, chrVIII (and 7 more). Register
# the annotation built for this assembly, or pass check_chromosomes=False —
# --no-check-chromosomes from a shell — to register this one anyway.
Nothing was created, and the annotation directory is left as it was found. The other direction is fine: an assembly may carry scaffolds the annotation never mentions.
Gene and transcript inference is off by default. GENCODE, Ensembl, RefSeq and WormBase GTFs
declare gene and transcript features already, and rebuilding them from exon lines is
gffutils' slow path. Pass disable_infer_genes=False, or --infer-genes from a shell, for a
bare exon-level GTF, which otherwise registers as a database of exons and nothing else.
Gene categories¶
A gene category is a named set of an annotation's own gene ids. Ask for one by name and the ids come back in the spelling that annotation uses:
rrna = worm.gene_list("rRNA")
rrna.gene_ids[:3] # ['WBGene00004512', 'WBGene00004513', 'WBGene00004567']
len(rrna.gene_ids) # 23
gene_lists() says what may be asked for. Nothing in the package holds a vocabulary of
categories; they are whatever the curated list for that annotation declares, and today every
one of them declares rRNA and nothing else:
rRNA holds everything rRNA-derived that annotation carries: the mature genes, the
pseudogene copies, the mitochondrial rRNAs. It is built for counting rRNA-derived reads as a
QC metric rather than for describing rRNA biology, so a pseudogene copy that captures reads
counts the same as a functional gene. Each answer carries a description of what it holds and
where the ids came from. The wording differs per annotation, so read it before using the ids
for a measurement. The description below is trimmed after the first two clauses:
rrna.sources[0].description
# 'Every rRNA-derived gene WormBase WS298 annotates on ce11: the chrI 45S unit as
# rrn-1.1 and rrn-1.2 (18S), rrn-2.1 (5.8S) and rrn-3.1 (26S), the sixteen rrn-4.*
# 5S genes, ...'
Features in a category may overlap, so count over the union of intervals rather than
summing per-gene counts. In ce11, rrn-3.56 is 851 of 852 bases identical to the 3' end of
rrn-3.1, and the chrI rDNA array is collapsed to about one and a half of the roughly 100
copies the real genome carries, so reads from every copy land on these few features.
The ids are curated and ship inside the package. They are not read from the GTF's own biotype
attribute, which yeast's ensgene_v101 does not carry at all, so deriving the list yourself
from that attribute returns no rRNA genes for yeast and no warning either.
A GeneList carries more than the ids: the assembly and annotation it answers for, and one
GeneListSource per contributing curated list, so a chimera's genes stay attributable to the
component they came from. Both classes are in the API reference.
When an annotation cannot answer¶
There is no empty answer here. An annotation nothing ships a curated list for, and one whose
list does not declare the category asked for, are different facts and each raises its own
error. Neither can be checked ahead of the call, because gene_lists() raises the first of
the two itself:
from genome import GeneCategoryNotDeclaredError, NoGeneCategoriesError
try:
genes = worm.gene_list("tRNA").gene_ids
except NoGeneCategoriesError:
print("no curated list ships for this annotation, so nothing can be asked of it")
except GeneCategoryNotDeclaredError as error:
print("declared here:", ", ".join(error.declared))
# declared here: rRNA
ce11 takes the second branch: a curated list ships for wormbase_ws298 and it declares
rRNA only. Both errors are LookupErrors, so except LookupError catches the pair where
the two need no telling apart. No declared category is ever empty and neither call ever
returns an empty collection.