ORFs and evidence¶
The bundled human catalogue is the
GENCODE Ribo-seq ORF
Phase 1 set: 7,264 GRCh38 ORFs that ribosome-profiling studies found translated
but that the reference annotation (GENCODE v35) did not annotate as proteins.
2,186 also have supporting MS peptides. "Noncanonical" is relative to an
annotation and transcript: in Ensembl 115, 34 of the 7,108 ORFs that bind encode
a protein identical to an annotated ENSP, and an ORF can be annotated in one
transcript model but not another. The catalogue is source-backed; it does not
discover every possible ORF. The larger Phase 2 set is planned
(#16). Most users start with a
reference proteome or extra_orfs(genome); this page describes
the objects and evidence underneath.
ORF categories¶
Each ORF keeps its source category verbatim as orf_type. The terms follow
Mudge et al. 2022, Table 2:
orf_type |
Position |
|---|---|
uORF |
Within the 5′ UTR of a protein-coding gene's transcript |
uoORF |
Begins in the 5′ UTR and overlaps the annotated coding sequence in a different frame |
intORF |
Entirely within the annotated coding sequence, in a different frame |
dORF |
Within the 3′ UTR |
doORF |
Begins within the annotated coding sequence and ends in the 3′ UTR, in a different frame |
lncRNA |
On a transcript annotated as a long noncoding RNA |
processed_transcript |
On a transcript annotated as processed_transcript, with no annotated protein |
The category, the host transcript's biotype and the evidence are separate
properties. The category describes the source's annotation; a bound ORF's
transcript.biotype describes the annotation you loaded. In Ensembl 115, for
example, most processed_transcript ORFs are on protein_coding_CDS_not_defined
or retained_intron transcripts, and 48 uORFs are on nonsense-mediated-decay
transcripts. One RNA can carry several ORFs.
Objects and queries¶
A TranscriptORF is one ORF on one PyEnsembl transcript. It covers both
annotated CDSs and the catalogue's extra ORFs, so all the ORFs on an RNA are
handled the same way. ReferenceORF is the same class under its original
name. Each has:
kind:"annotated"for an annotated CDS,"extra_orf"for a catalogue ORF.orf_id: the source ORF ID, or the versioned protein ID of an annotated CDS.startandend: Python slice coordinates into the transcript's spliced sequence, sotranscript.sequence[orf.start:orf.end] == orf.coding_sequence. The coding sequence includes the stop codon when there is one. The genomic path is incoding_blocks, 1-based and inclusive.transcript,transcript_id,gene,gene_idandgene_name: the transcript carrying the ORF and its gene in the annotation you loaded.source_transcript_id,source_gene_idandsource_gene_name: what the ORF's source says, andon_source_transcript: whether this is that transcript.protein_sequence,orf_type,modelandevidence. An annotated CDS has noorf_type,modelor evidence of its own.start_completeandstop_complete, and for an annotated CDSequivalent_ids: other annotated proteins with the identical placement.
An extra ORF is checked strictly: the assembly, available species information,
the full spliced coding path, the protein sequence and the terminal stop must
all match. An annotated CDS is taken as the annotation defines it, with the
annotation's protein. Annotations include selenoproteins and CDSs without
marked start or stop codons, so translation is not re-checked;
start_complete and stop_complete report what the annotation marks.
from orfdata import extra_orfs
orfs = extra_orfs(genome) # Every catalogue ORF, on every transcript carrying it.
tpst1 = genome.transcript_by_id("ENST00000304842")
for orf in orfs.for_transcript(tpst1, include_annotated=True):
print(orf.kind, orf.orf_id, orf.orf_type, orf.start, orf.end, len(orf.equivalent_ids))
extra_orf c7riboseqorf56 uORF 149 269 0
annotated ENSP00000302413.5 None 290 1403 21
Results come in RNA order: the uORF ends 21 bases before the annotated CDS
starts, and 21 other proteins share TPST1's annotated placement exactly.
Another transcript's CDS is listed only when this RNA encodes the same ORF. A
CDS without a marked start or stop codon, such as a fragment on a truncated
transcript, is listed only when this RNA continues identically beyond that end;
otherwise translation would read on. Two such TPST1 fragments are not listed here. annotated_orfs(transcript) gives just the annotated ones;
TranscriptORF.from_annotation(transcript) gives one transcript's own CDS.
orfs.overlapping(contig, start, end, genome_build=...) finds ORFs whose coding
path overlaps a 1-based inclusive genomic interval; introns between coding
blocks don't count. Add include_initiation_context=True to include an ORF's
spliced start flanks, for example for a Kozak-position variant. Flanks come
from the transcript each placement is on (orf.initiation_context), since
transcripts sharing a coding path can differ upstream of the start codon.
Transcripts carrying an ORF¶
An ORF is placed on every transcript that contains its exact spliced coding path
and translates to its protein, not only on the transcript its source names. In
Ensembl 115 the bundled ORFs have 67,529 such placements; only 1,314 ORFs are
carried by a single transcript. on_source_transcript marks the one the source
names. Another carrying transcript is not evidence that the ORF is translated
from it, and the ORF's evidence is the same on every placement.
orfs.source_failures lists ORFs whose source transcript is absent from this
annotation or no longer carries them, with the reason. Most are still placed
elsewhere: of 156 such ORFs in Ensembl 115, only the 25 in orfs.excluded are
carried by no transcript. record.bind(transcript) still binds strictly to
the source's transcript.
Annotated CDSs can also lie in other transcripts. MIURF's CDS sits in the "5′ UTR" of MIEF1's mRNA, for example, and SNURF's in SNRPN's:
mief1 = genome.transcript_by_id("ENST00000325301")
for orf in orfs.for_transcript(mief1, include_annotated=True):
print(orf.kind, orf.orf_id, orf.source_gene_name, orf.start, orf.end, orf.on_source_transcript)
extra_orf c22riboseqorf42 MIEF1 72 132 True
annotated ENSP00000490747.1 MIURF 176 389 False
annotated ENSP00000327124.2 MIEF1 487 1879 True
extra_orf c22norep69 MIEF1 527 677 True
MIURF's protein keeps its own ID and gene while sitting on MIEF1's RNA, so it is not mistaken for a new protein. The examples cover non-AUG starts, recoded stop codons, frameshifts, circular RNAs, precursor products and IRES elements.
Catalogue records¶
load_catalog() loads the bundled human records offline using only the Python
standard library; like PyEnsembl, it defaults to species="human".
catalog.for_transcript(transcript) binds the catalogue ORFs on one
transcript. To inspect source records without a genome:
from orfdata import load_catalog
catalog = load_catalog()
records = catalog.query(gene="TPST1", evidence="ms")
Identifiers¶
Bundled ORF IDs are the source's GENCODE Phase I IDs. They start with the
chromosome: c7riboseqorf56 is on chromosome 7. The 3,085 c…riboseqorf…
ORFs were detected in two or more ribosome-profiling studies, and the 4,179
c…norep… ORFs in one. They are not Ensembl stable IDs.
Each record names one host transcript (transcript_id, an unversioned ENST)
and that transcript's gene (gene_id, an ENSG), as validated against Ensembl
101. For an ORF on an mRNA, this is the gene of the transcript's annotated
protein. Later releases can change both: of the 7,108 ORFs that bind to Ensembl
115, 87 host transcripts now belong to a different ENSG and 796 gene symbols
changed. A bound ORF's gene_id, gene_name and transcript describe the
genome you loaded; orf.model keeps the source's labels. In
extra_orfs(genome), for_gene matches genes in the loaded annotation, or the
source's labels with source=True. for_transcript and for_gene reject a
transcript or gene from another reference, and a versioned Ensembl ID must match
the loaded version.
The host is one of possibly several transcripts that carry the same ORF; see transcripts carrying an ORF.
Evidence¶
Use orf.evidence.has_evidence("ribosome"), "shotgun" (protease-digest MS),
"mhc" (including human HLA), or "ms" (any supporting MS observation).
"hla" selects human HLA observations specifically. Source observations whose
MS assay type is unspecified remain labelled "ms_unclassified".
orf.evidence.has_evidence("ms", require_unique=True, require_reviewed=True)
This requires a supporting spectrum review and a single protein mapping
within that source's search scope. It does not imply uniqueness across every
possible protein. Inspect orf.evidence.peptides for peptide sequences, source
accessions, matching intervals, mapping counts and review quality. Missing counts
remain unknown; low or mixed spectrum reviews do not count as supporting MS.
has_evidence and catalog.query(evidence=...) also take rules that set
Ribo-seq and MS stringency separately, such as
RiboSeq(min_studies=2) & MS(min_peptides=2); see
Build a reference proteome.
Some sources provide only aggregate support counts. EvidenceSummary keeps the
native metric, definition, value, source/release, source checksum and explicitly
scoped entity. For example, OpenProt's native metrics
include an MS score that sums study-specific unique-peptide
counts; it is neither global peptide uniqueness nor protein abundance. Its
translation score counts studies with significant translation detection.
None means unknown and remains distinct from a reported zero.
Positive summaries in orf.evidence.summaries can satisfy general evidence
queries. To require actual peptide records or named ribosome studies, use:
orf.evidence.has_evidence("ms", require_resolved=True)
catalog.query(evidence="ribosome", require_resolved=True)
The CLI uses --evidence ms --require-resolved. The orfdata query command
also takes the Ribo-seq and MS requirements of
orfdata proteome:
orfdata --min-ribo-seq-studies 2 --min-ms-peptides 2 --format tsv
orfdata --gene TPST1 --min-ribo-seq-studies 2 --min-ms-peptides 1 --either
Uniqueness and review filters always require resolved peptide observations. Summaries never create peptide sequences, residue coverage, retained variant peptides or mutant-translation evidence. Their protein checksum and species/reference checks prevent attaching them to a different protein or incompatible source context.
A sequence-level summary may have several transcript assignments. Those records
share the summary's key; catalog.evidence_summaries returns each native metric
once and rejects conflicting facts for the same identity. Native accession
aliases must be resolved explicitly by the source adapter. Counts from different
metrics and sources are not summed, and a later peptide-resolved snapshot is not
an additional independent experiment merely because it has more detail.
The OpenProt mouse catalogue uses these summaries. OpenProt's human and yeast files are not imported yet; see #18.
The distinction between translation, peptide detection and functional protein evidence follows the TransCODE study. Source evidence stays at its declared ORF or protein-sequence scope. It is not automatically evidence for another isoform, a mutant protein, a fusion or another species.
Variants and RNA products¶
Varcode keeps the reference object alongside its modification:
from pyensembl import EnsemblRelease
from orfdata import load_catalog
from varcode import Variant, annotate_orf_variants
genome = EnsemblRelease(95, species="human") # already downloaded and indexed
transcript = genome.transcript_by_id("ENST00000304842")
orf, = load_catalog().for_transcript(transcript)
variant = Variant("7", 66205486, "C", "G", genome=genome)
annotation = annotate_orf_variants([variant], orf)
assert annotation.reference is orf
print(annotation.effect) # P2A in the TPST1 upstream ORF
result = annotation.to_dict() # reference, evidence and altered products
Isovar's annotate_sv_orf_evidence attaches the same reference annotations to
reconstructed RNA products and preserves each candidate's RNA support. For
example, the native TPST1 peptide KIILSKISV is absent from the Sid TPST1–CRCP
alternatives, so its MS evidence does not support those altered products.
The reference integration, including strict reference-context validation, is available in Varcode 10.13.0 and Isovar 1.51.0.
Data and limits¶
The bundled human GRCh38 catalogue, dataset 2026-10-11.1, draws on the
GENCODE Phase I reference,
TransCODE author exports,
and the community microprotein spectrum benchmark.
All 7,264 ORF paths and proteins were checked against Ensembl 101 GTF, coding RNA
and noncoding RNA FASTAs. The 2,186 models with supporting MS observations use
the packaged source-specific filters; this is not a count of proven functional
proteins. Packaged manifests retain source accessions, checksums, licenses,
processing rules and dataset version.
Catalogue models bind as complete ORFs with a terminal stop under NCBI tables
1, 2 or 3 and their initiation codons, or a start codon the model declares with
start_codon; see genetic codes. All bundled models
use table 1 and start at ATG. Transcript-boundary contexts can be partial;
initiation efficiency and effects on the main coding region are not established
by these data.
ORFs that a source defines on an RNA rather than in genomic coordinates use
TranscriptORF.at: non-AUG starts, fragments without a known start, stop codons
read through, and ORFs on circular or other source RNAs (SourceRNA). See the
examples. The bundled catalogue contains none of these. Fusions,
edited RNAs and programmed frameshifts in catalogue models need further model
support, and ORF discovery is outside the current API.
load_uorf_catalog() preserves Isovar's upstream-only snapshot: 3,771 models,
dataset 2026-10-08.2. New code should use the broader load_catalog() API.
Other species¶
ReferenceContext and ScopedTarget keep taxa, strains, assemblies
and annotation versions separate. Custom genomes without species/taxonomy need
an explicit context when binding; source-pinned contexts require an exact match
for record.bind(transcript). With any_transcript=True, as extra_orfs uses
to place ORFs by their exact coding path and protein, a record pinned to
another annotation release of the same species, strain and assembly is also
accepted. ORFs on another assembly can be
lifted over.
load_catalog(species="mouse") returns a species' catalogue: the bundled one,
or else the one downloaded or built through CatalogRegistry,
such as the OpenProt mouse catalogue.
genome_build= chooses between assemblies. It never downloads, and for a
species without a catalogue the error lists the catalogues that exist.
load_catalog(path) reads a custom versioned JSON catalogue. Each catalogue is
scoped to one species and assembly; each model records its NCBI taxon, scientific
name, assembly, translation table and source. Human evidence is never transferred
to a mouse ortholog. Mouse ORFs come from OpenProt, and their bindings are
tested with real OpenProt rows on real Ensembl 106 transcripts.
To build a catalogue, create ORFModel objects from source coordinates and
sequences, attach source observations with ORFRecord.from_model(model, evidence),
and construct ORFCatalog(records, metadata). Its to_dict() produces the
orfdata.catalog.v1 JSON representation. Genomic coding blocks are 1-based,
inclusive, sorted by genomic position, and include the terminal stop codon.
OpenProt supplies the mouse catalogue through a pinned importer that checks every row's coordinates and protein; see Mouse ORFs from OpenProt. Its human and yeast files are not imported yet.
Rebuild and verify¶
From a development checkout with the PyEnsembl dependency installed:
python -m pip install -e '.[dev]'
python -m orfdata.build --source-dir /path/to/sources --prepare-sources \
--all-orfs --output orfdata/data/orf-evidence
./lint.sh
./test.sh
Source preparation downloads pinned public inputs and needs Rscript to extract the author export. Ordinary queries need neither R nor spreadsheet libraries.