RefgetTranscripts (reftx) Reference
RefgetTranscripts (reftx) is a compact binary transcript store for HGVS coordinate mapping. It finds a transcript with a binary search over a sorted hash index, so lookups are O(log n). A store can be read from disk with a memory map, with positioned reads (pread), or from bytes already in memory (which is how it runs in the browser).
The code lives in the gtars-refget crate, in the gtars_refget::transcripts module.
Quick Example
Section titled “Quick Example”use gtars_refget::transcripts::{ReadonlyTxStore, TxBackend, TxStoreBuilder};
// Chromosome digests are the 24 raw bytes behind a refget "SQ." accession.let raw = base64_url::decode("<sha512t24u digest of chr7>").expect("valid base64url");let chr7_digest: [u8; 24] = raw.as_slice().try_into()?;
// Build a store from cdot JSONlet mut builder = TxStoreBuilder::new();builder.add_chrom_mapping("NC_000007.14", chr7_digest);builder.load_mane_summary("MANE.GRCh38.v1.3.summary.txt.gz")?; // optional, load before ingestbuilder.ingest_cdot("cdot.grch38.json.gz")?;builder.build("transcripts.reftx")?;
// Open and querylet store = ReadonlyTxStore::open_with_backend("transcripts.reftx", TxBackend::Pread)?;
if let Some(tx) = store.lookup("NM_004333.6") { println!("{}: {} exons on {:?}", tx.accession, tx.exons.len(), tx.strand);}Rust API
Section titled “Rust API”Installation and features
Section titled “Installation and features”Add gtars-refget with the transcripts feature:
[dependencies]gtars-refget = { git = "https://github.com/databio/gtars", branch = "dev", features = ["transcripts"] }The API is split in two by Cargo feature:
| Feature | What you get |
|---|---|
transcripts | The core, which works on every target including WASM: ReadonlyTxStore (with from_bytes), TranscriptRef, the models, CoordinateMapper, the mature mRNA helpers, and build_reftx_bytes_in_memory |
transcripts + filesystem | Also the file-based parts: TxStore, TxBackend, TxStoreBuilder, and the ReadonlyTxStore::open_* constructors |
filesystem is on by default. For WASM, use default-features = false, features = ["transcripts"].
All public types are re-exported from gtars_refget::transcripts (and from the crate root).
ReadonlyTxStore
Section titled “ReadonlyTxStore”Immutable transcript store for read access. All lookups take &self, so one store can be shared across threads with Arc<ReadonlyTxStore>.
Every constructor checks the header and returns an error if the magic number or format version is wrong. Stores with fewer than 500,000 transcripts are fully decoded into memory when opened.
ReadonlyTxStore::open_with_backend
Section titled “ReadonlyTxStore::open_with_backend”pub fn open_with_backend<P: AsRef<Path>>(path: P, backend: TxBackend) -> Result<Self>Open a .reftx file with the backend you choose. Needs the filesystem feature.
Parameters:
path- Path to a.reftxfilebackend-TxBackend::PreadorTxBackend::Mmap
TxBackend
Section titled “TxBackend”pub enum TxBackend { Mmap, Pread,}Pread- Positioned reads on the file. No memory mapping. The safe default for a single process.Mmap- Memory-map the whole file. Best when many processes read the same large store, since they share the OS page cache. The file must not be changed or truncated while it is mapped. The builder always writes files with an atomic rename, so this is safe for files it produced.
ReadonlyTxStore::open_mmap / open_pread
Section titled “ReadonlyTxStore::open_mmap / open_pread”pub fn open_mmap<P: AsRef<Path>>(path: P) -> Result<Self>pub fn open_pread<P: AsRef<Path>>(path: P) -> Result<Self>Shortcuts for open_with_backend with a fixed backend. Need the filesystem feature.
ReadonlyTxStore::from_bytes
Section titled “ReadonlyTxStore::from_bytes”pub fn from_bytes(bytes: Vec<u8>) -> Result<Self>Build a store from the full contents of a .reftx file held in memory. Uses no file system and no memory map, so it works on every target, including WASM.
Example:
let bytes = std::fs::read("transcripts.reftx")?;let store = ReadonlyTxStore::from_bytes(bytes)?;ReadonlyTxStore::len / is_empty
Section titled “ReadonlyTxStore::len / is_empty”pub fn len(&self) -> u64pub fn is_empty(&self) -> boolNumber of transcripts in the store, and whether it is zero.
ReadonlyTxStore::lookup
Section titled “ReadonlyTxStore::lookup”pub fn lookup(&self, accession: &str) -> Option<TranscriptRef<'_>>Look up a transcript by accession. Uses an O(log n) binary search on the index.
Parameters:
accession- Transcript accession with version (e.g.,"NM_004333.6")
Returns: Option<TranscriptRef<'_>> - A reference to the cached transcript, or a freshly decoded one
Example:
use std::sync::Arc;
let store = ReadonlyTxStore::open_pread("transcripts.reftx")?;let shared = Arc::new(store);
// Multiple threads can call lookup at the same timelet tx = shared.lookup("NM_004333.6");ReadonlyTxStore::lookup_mane
Section titled “ReadonlyTxStore::lookup_mane”pub fn lookup_mane(&self, gene: &str) -> Option<Transcript>Look up the MANE Select transcript for a gene symbol. The match ignores case ("braf" finds "BRAF"). Returns None if the gene has no MANE Select transcript, or if the store was built without MANE data.
Parameters:
gene- Gene symbol (e.g.,"BRAF")
Returns: Option<Transcript> - An owned copy of the MANE Select transcript
ReadonlyTxStore::has_mane_index
Section titled “ReadonlyTxStore::has_mane_index”pub fn has_mane_index(&self) -> boolReturns true if the store has a MANE gene index, meaning lookup_mane can find anything.
TranscriptRef
Section titled “TranscriptRef”pub enum TranscriptRef<'a> { Cached(&'a Transcript), Owned(Transcript),}The value returned by ReadonlyTxStore::lookup. It derefs to Transcript, so you can read fields directly (tx.accession, tx.exons).
TxStore
Section titled “TxStore”Mutable, memory-mapped store for the setup phase. Use it when you want to choose which transcripts are decoded ahead of time, then convert it to a ReadonlyTxStore. Needs the filesystem feature. If you just want to read a store, ReadonlyTxStore::open_with_backend is simpler.
TxStore::open / open_mmap
Section titled “TxStore::open / open_mmap”pub fn open<P: AsRef<Path>>(path: P) -> Result<Self>pub fn open_mmap<P: AsRef<Path>>(path: P) -> Result<Self>Open a store from disk with a memory map. open is the same as open_mmap. Returns an error if the magic number or version is invalid.
TxStore::len / is_empty
Section titled “TxStore::len / is_empty”pub fn len(&self) -> u64pub fn is_empty(&self) -> boolTxStore::lookup
Section titled “TxStore::lookup”pub fn lookup(&self, accession: &str) -> Option<Transcript>Look up a transcript by accession (O(log n) binary search). Returns an owned Transcript.
TxStore::ensure_decoded
Section titled “TxStore::ensure_decoded”pub fn ensure_decoded(&mut self, accession: &str) -> Result<()>Decode one transcript into the internal cache ahead of time. The cache is carried into the ReadonlyTxStore.
TxStore::ensure_decoded_where
Section titled “TxStore::ensure_decoded_where”pub fn ensure_decoded_where<F>(&mut self, predicate: F) -> Result<usize>where F: Fn(&Transcript) -> bool,Decode all transcripts that match a predicate into the cache. Returns how many were cached.
Example:
// Pre-load all BRAF transcriptslet count = store.ensure_decoded_where(|tx| tx.gene == "BRAF")?;TxStore::into_readonly
Section titled “TxStore::into_readonly”pub fn into_readonly(self) -> ReadonlyTxStoreConvert to a memory-mapped ReadonlyTxStore. If the store has fewer than 500,000 transcripts and nothing has been cached yet, this decodes every transcript into memory first.
TxStore::into_readonly_lazy
Section titled “TxStore::into_readonly_lazy”pub fn into_readonly_lazy(self) -> ReadonlyTxStoreConvert without decoding anything extra. Transcripts not already cached are decoded from the file on each lookup. Use this when only a few transcripts will be queried.
TxStoreBuilder
Section titled “TxStoreBuilder”Builds a .reftx file from cdot JSON. Needs the filesystem feature.
TxStoreBuilder::new
Section titled “TxStoreBuilder::new”pub fn new() -> SelfCreate an empty builder. TxStoreBuilder::default() does the same.
TxStoreBuilder::add_chrom_mapping
Section titled “TxStoreBuilder::add_chrom_mapping”pub fn add_chrom_mapping(&mut self, name: &str, digest: [u8; 24])Register a chromosome name and its refget digest. The digest is the 24 raw bytes of the sequence's sha512t24u digest (the part after SQ., base64url-decoded).
Parameters:
name- Chromosome name as it appears in the cdot file (e.g.,"NC_000001.11")digest- 24-byte refget digest
TxStoreBuilder::load_mane_summary
Section titled “TxStoreBuilder::load_mane_summary”pub fn load_mane_summary<P: AsRef<Path>>(&mut self, path: P) -> Result<usize>Load MANE flags from an NCBI MANE summary TSV (plain or .gz). It reads the RefSeq_nuc, Ensembl_nuc, and MANE_status columns, so both RefSeq and Ensembl transcripts get their flags.
Call this before ingest_cdot. Flags are attached to transcripts as they are ingested.
Parameters:
path- Path to the MANE summary file
Returns: Count of accession entries loaded (RefSeq and Ensembl accessions are counted separately)
TxStoreBuilder::has_mane_flags
Section titled “TxStoreBuilder::has_mane_flags”pub fn has_mane_flags(&self) -> boolReturns true if any MANE flags have been loaded.
TxStoreBuilder::ingest_cdot
Section titled “TxStoreBuilder::ingest_cdot”pub fn ingest_cdot<P: AsRef<Path>>(&mut self, path: P) -> Result<usize>Ingest a cdot-style JSON file (plain or .json.gz). Skips transcripts whose chromosome was not registered with add_chrom_mapping, transcripts with a strand other than 1 or -1, and transcripts with no exons.
The reader expects a top-level transcripts object. Each entry needs id, contig, strand, and exons (a list of [start, end] pairs), plus optional gene_name, cds_start, and cds_end.
Parameters:
path- Path to the JSON file
Returns: Count of transcripts ingested
TxStoreBuilder::len / is_empty
Section titled “TxStoreBuilder::len / is_empty”pub fn len(&self) -> usizepub fn is_empty(&self) -> boolNumber of transcripts staged so far. Transcripts can also be added directly through the public transcripts: Vec<Transcript> field.
TxStoreBuilder::build
Section titled “TxStoreBuilder::build”pub fn build<P: AsRef<Path>>(&mut self, output: P) -> Result<()>Write the binary store to disk. Returns an error if no transcripts are staged. The file is written to a temporary file and then renamed into place, so readers never see a half-written file. A <output>.lock file stops two builders from writing the same path at once.
Parameters:
output- Output path for the.reftxfile
Example:
let mut builder = TxStoreBuilder::new();builder.add_chrom_mapping("NC_000007.14", chr7_digest);builder.load_mane_summary("MANE.GRCh38.v1.3.summary.txt.gz")?;let count = builder.ingest_cdot("cdot.grch38.json.gz")?;println!("Ingested {} transcripts", count);builder.build("transcripts.reftx")?;build_reftx_bytes_in_memory
Section titled “build_reftx_bytes_in_memory”pub fn build_reftx_bytes_in_memory(transcripts: &[Transcript]) -> Result<Vec<u8>>Build a complete .reftx image in memory, with no file system. Works on every target. Pair it with ReadonlyTxStore::from_bytes.
Transcript
Section titled “Transcript”Transcript annotation record.
pub struct Transcript { /// Accession with version (e.g., "NM_004333.6") pub accession: String, /// Gene symbol (e.g., "BRAF") pub gene: String, /// Chromosome refget digest (24 bytes, truncated SHA-512) pub chrom_digest: [u8; 24], /// Strand orientation pub strand: Strand, /// CDS start in genomic coordinates (None if non-coding) pub cds_start: Option<u32>, /// CDS end in genomic coordinates (None if non-coding) pub cds_end: Option<u32>, /// Exons in genomic order (5' to 3' on chromosome) pub exons: Vec<Exon>, /// MANE Select / Plus Clinical status pub mane: ManeStatus,}Methods:
| Method | Return Type | Description |
|---|---|---|
transcript_length() | u32 | Total transcript length (sum of exon lengths) |
cds_length() | u32 | CDS length in bases (0 if non-coding) |
is_coding() | bool | Returns true if transcript has CDS |
accession_base() | &str | Accession without version (e.g., "NM_004333") |
A single exon with genomic coordinates.
pub struct Exon { /// Genomic start (0-based, inclusive) pub start: u32, /// Genomic end (0-based, exclusive) pub end: u32,}Methods:
| Method | Return Type | Description |
|---|---|---|
len() | u32 | Length in bases |
is_empty() | bool | Returns true if zero-length |
Strand
Section titled “Strand”Strand orientation.
pub enum Strand { Forward = 1, Reverse = -1,}Strand::from_i8(1) and Strand::from_i8(-1) parse a strand; any other value gives None.
ManeStatus
Section titled “ManeStatus”MANE flags for a transcript. The default is both flags false.
pub struct ManeStatus { /// True if this is the MANE Select transcript for its gene pub mane_select: bool, /// True if this is MANE Plus Clinical pub mane_clinical: bool,}is_mane() returns true if either flag is set.
CoordinateMapper
Section titled “CoordinateMapper”Maps HGVS transcript coordinates to 0-based genomic positions, using a ReadonlyTxStore.
use gtars_refget::transcripts::CoordinateMapper;
let mapper = CoordinateMapper::new(&store);let result = mapper.c_to_g("NM_004333.6", 1799)?;println!("genomic position: {}", result.position);| Method | Description |
|---|---|
new(store: &ReadonlyTxStore) | Create a mapper |
c_to_g(accession, c_pos: i64) | Map c.N to genomic |
n_to_g(accession, n_pos: u64) | Map n.N to genomic |
c_to_g_full(accession, c_pos: i64, offset: i64, is_cds_end: bool) | Full c. form: negative c_pos for 5' UTR (c.-14), offset for introns (c.93+5 is offset = 5), is_cds_end = true for 3' UTR (c.*N) |
n_to_g_full(accession, n_pos: i64, offset: i64) | Full n. form with an intronic offset |
c_to_g_by_gene(gene, c_pos, offset, is_cds_end) | Resolve the gene's MANE Select transcript, then map. Returns (accession_used, MappingResult) |
g_to_transcript_offset(accession, g_pos: u64) | Map a genomic position to a 0-based offset on the spliced mRNA. Returns Ok(None) if the position is not in an exon |
The mapping methods return Result<MappingResult, MappingError>:
pub struct MappingResult { /// Genomic position (0-based) pub position: u64, /// Chromosome refget digest pub chrom_digest: [u8; 24],}MappingError covers cases such as TranscriptNotFound, NoManeTranscript, OutsideCds, InvalidIntronicOffset, the 5' and 3' UTR overflows, and NonCodingTranscript.
CoordinateMapperWriter has the same new, c_to_g, and n_to_g methods, but takes &mut self and reuses its internal buffers between calls. Use it in tight loops.
Mature mRNA helpers
Section titled “Mature mRNA helpers”These build the spliced (mature) mRNA reference sequence for a transcript by reading exon sequences from a ReadonlyRefgetStore. Reverse-strand transcripts are reverse-complemented. The result is the reference sequence; variants are not applied.
pub fn mature_mrna( store: &ReadonlyRefgetStore, tx_store: &ReadonlyTxStore, accession: &str,) -> Result<String>
pub fn mature_mrna_for_transcript( store: &ReadonlyRefgetStore, tx: &Transcript,) -> Result<String>
pub fn concat_regions( store: &ReadonlyRefgetStore, chrom_digest: &[u8; 24], regions: &[(u32, u32)], strand: Strand,) -> Result<String>mature_mrnalooks up the accession, then builds its sequence.mature_mrna_for_transcriptdoes the same for a transcript you already have.concat_regionsis the building block: it joins any list of 0-based, half-open regions (in genomic order) from one chromosome.
They return an error if the transcript is missing, the chromosome is not in the sequence store, or an exon runs past the end of the chromosome (a sign that the transcripts and genome do not match).
Example:
use gtars_refget::store::RefgetStore;use gtars_refget::transcripts::{mature_mrna, ReadonlyTxStore};
let seqs = RefgetStore::open_local("/data/refget")?.into_readonly();let tx_store = ReadonlyTxStore::open_pread("transcripts.reftx")?;let mrna = mature_mrna(&seqs, &tx_store, "NM_004333.6")?;TranscriptProvider and TxProvider
Section titled “TranscriptProvider and TxProvider”HGVS-to-VRS code in gtars-vrs reads transcripts through the TranscriptProvider trait (in gtars_vrs::provider, also re-exported at the gtars_vrs root):
pub trait TranscriptProvider { fn c_to_genomic(&self, accession: &str, c_pos: i64) -> Result<SequenceLocation, ProviderError>; fn n_to_genomic(&self, accession: &str, n_pos: u64) -> Result<SequenceLocation, ProviderError>; fn get_chrom_accession(&self, accession: &str) -> Result<String, ProviderError>; fn get_strand(&self, accession: &str) -> Result<i8, ProviderError>; fn c_to_genomic_full(&self, accession: &str, c_pos: i64, offset: i64, is_cds_end: bool) -> Result<SequenceLocation, ProviderError>; // has a default fn n_to_genomic_full(&self, accession: &str, n_pos: i64, offset: i64) -> Result<SequenceLocation, ProviderError>; // has a default fn gene_to_mane_accession(&self, gene: &str) -> Option<String>; // default: None}With the transcripts feature of gtars-vrs, the trait is implemented for ReadonlyTxStore and for TxProvider, a cheap-to-clone wrapper around Arc<ReadonlyTxStore> for sharing one store across workers. NoTranscriptProvider is a stand-in that rejects all transcript lookups, for when you only expect g. variants.
use std::sync::Arc;use gtars_refget::transcripts::ReadonlyTxStore;use gtars_vrs::TxProvider;
let store = ReadonlyTxStore::open_pread("transcripts.reftx")?;let provider = TxProvider::new(Arc::new(store));let store_again = provider.store(); // &Arc<ReadonlyTxStore>Python API
Section titled “Python API”Installation
Section titled “Installation”The reftx module is part of the gtars Python package (it is in the default build):
pip install gtarsfrom gtars.reftx import ReadonlyTxStore, TxStoreBuilder, CoordinateMapperReadonlyTxStore
Section titled “ReadonlyTxStore”Opening a store
Section titled “Opening a store”store = ReadonlyTxStore.open("transcripts.reftx") # memory-mappedprint(len(store))Looking up transcripts
Section titled “Looking up transcripts”tx = store.lookup("NM_004333.6")if tx: print(f"{tx.accession}: {tx.gene}, {len(tx.exons)} exons") print(f"CDS: {tx.cds_start}-{tx.cds_end}") print(f"Strand: {tx.strand}")MANE lookups
Section titled “MANE lookups”if store.has_mane_index(): tx = store.lookup_mane("BRAF") # case-insensitive if tx: print(f"MANE Select for BRAF: {tx.accession}")Other methods
Section titled “Other methods”| Method | Returns | Description |
|---|---|---|
get_chrom_accession(accession) | str or None | The chromosome's SQ. refget accession |
get_strand(accession) | Strand or None | The transcript's strand |
TxStoreBuilder
Section titled “TxStoreBuilder”Building a store from cdot
Section titled “Building a store from cdot”builder = TxStoreBuilder()
# Map chromosome names to refget accessions ("SQ.<digest>" or the bare digest)builder.add_chrom_mapping("NC_000007.14", "SQ.<sha512t24u digest of chr7>")
# Optionally load MANE flags. Do this before loading cdot.builder.load_mane_summary("MANE.GRCh38.v1.3.summary.txt.gz")
# Ingest cdot JSON (.gz is detected automatically)count = builder.load_cdot("cdot.grch38.json.gz")print(f"Ingested {count} transcripts")
# Build the storebuilder.build("transcripts.reftx")| Method | Description |
|---|---|
add_chrom_mapping(name, accession) | Register a chromosome name and its refget accession |
load_mane_summary(path) | Load MANE flags; returns the count of entries loaded |
load_cdot(path) | Ingest a cdot JSON file; returns the count ingested. load_cdot_gz is an alias |
add_transcript(value) | Add one transcript, given a Transcript or a dict shaped like Transcript.to_dict() |
len(builder) | Number of transcripts staged |
build(out_path) | Write the .reftx file |
CoordinateMapper
Section titled “CoordinateMapper”mapper = CoordinateMapper(store)
pos = mapper.c_to_g("NM_004333.6", 1799) # 0-based genomic position (int)info = mapper.c_to_g_full("NM_004333.6", 1799)# {"chrom": "SQ...", "chrom_accession": "SQ...", "genomic_pos": ..., "strand": Strand.Minus}
by_gene = mapper.c_to_g_by_gene("BRAF", 1799) # same dict plus "accession"| Method | Returns | Description |
|---|---|---|
c_to_g(accession, c_pos, datum=None) | int | Map c. to genomic. Pass datum=1 for a 3' UTR c.*N position |
n_to_g(accession, n_pos) | int | Map n. to genomic |
c_to_g_full(accession, c_pos, datum=None) | dict | Like c_to_g, plus chromosome and strand |
n_to_g_full(accession, n_pos) | dict | Like n_to_g, plus chromosome and strand |
c_to_g_by_gene(gene, c_pos, datum=None) | dict | Map through the gene's MANE Select transcript |
The Python mapper does not take intronic offsets. Mapping failures raise gtars.reftx.MappingError; store and builder failures raise gtars.reftx.TxStoreError.
ReftxProvider
Section titled “ReftxProvider”ReftxProvider(store) wraps a ReadonlyTxStore for the HGVS functions in gtars.vrs.hgvs:
from gtars.reftx import ReadonlyTxStore, ReftxProviderfrom gtars.vrs.hgvs import hgvs_to_vrs_id
provider = ReftxProvider(ReadonlyTxStore.open("transcripts.reftx"))vrs_id = hgvs_to_vrs_id("NM_004333.6:c.1799T>A", provider, refget_store, collection_digest)Transcript object attributes
Section titled “Transcript object attributes”| Attribute | Type | Description |
|---|---|---|
accession | str | Full accession with version |
gene | str or None | Gene symbol (None if empty) |
chrom | str | Chromosome refget accession ("SQ.<digest>") |
strand | Strand | Strand.Plus or Strand.Minus (str() gives "+" or "-") |
cds_start | int or None | CDS start position (0-based) |
cds_end | int or None | CDS end position (0-based, exclusive) |
exons | list[Exon] | Exons, each with start and end |
mane | ManeStatus or None | Has select and plus_clinical flags; None if neither is set |
Transcript.to_dict() returns the same data as a plain dict.
JavaScript (WASM) API
Section titled “JavaScript (WASM) API”The gtars-js WASM package includes a transcript store for the browser. There is no file system in the browser, so you fetch the .reftx bytes yourself and pass them in. Inside, it uses ReadonlyTxStore::from_bytes wrapped in the same TxProvider used on native.
import init, { TranscriptStore, hgvs_to_vrs_id_with_transcripts } from "gtars-js";await init();
const reftxBytes = new Uint8Array(await (await fetch(reftxUrl)).arrayBuffer());const tx = new TranscriptStore(reftxBytes); // throws if the bytes are not a valid .reftxconsole.log(tx.transcriptCount());
const id = hgvs_to_vrs_id_with_transcripts( "NM_004333.6:c.1799T>A", "chr7", chr7Bases, tx);// -> "ga4gh:VA.<digest>"| API | Description |
|---|---|
new TranscriptStore(bytes) | Load a store from a Uint8Array holding an uncompressed .reftx file |
transcriptCount() | Number of transcripts in the store |
hgvs_to_vrs_id_with_transcripts(hgvs, sequenceName, sequenceBases, tx) | HGVS to VRS ID, resolving c./n. and gene symbols. The bases must be for the chromosome the transcript lies on |
RefgetStore.hgvs_to_vrs_id_with_transcripts(hgvs, tx) | Same, but reads bases from a RefgetStore that already holds that chromosome |
Binary Format Specification
Section titled “Binary Format Specification”The .reftx file format is a compact binary format built for O(log n) lookups. It is the same for all three backends (memory map, pread, and in-memory bytes).
File Layout
Section titled “File Layout”+------------------------------------------------------------------------------+| HEADER (40 bytes, fixed) |+------------------------------------------------------------------------------+| RECORDS (variable length, starts at byte 40, sorted by accession hash) |+------------------------------------------------------------------------------+| ACCESSION INDEX (16 bytes per entry, starts at index_offset) |+------------------------------------------------------------------------------+| MANE GENE INDEX (optional, starts at mane_index_offset) |+------------------------------------------------------------------------------+Header (40 bytes)
Section titled “Header (40 bytes)”| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| 0 | 4 | [u8; 4] | magic | b"RFTX" (0x52, 0x46, 0x54, 0x58) |
| 4 | 4 | u32 LE | version | Format version (currently 2) |
| 8 | 8 | u64 LE | record_count | Number of transcript records |
| 16 | 8 | u64 LE | index_offset | Byte offset to the accession index |
| 24 | 8 | u64 LE | mane_index_offset | Byte offset to the MANE gene index, or 0 if there is none |
| 32 | 8 | [u8; 8] | reserved | Zero-filled for future use |
Record Format (variable length)
Section titled “Record Format (variable length)”Each transcript record has the following structure:
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0 | 1 | u8 | accession_len | Length of accession string (max 255) |
| +1 | N | [u8; N] | accession | UTF-8 accession string |
| +N+1 | 1 | u8 | gene_len | Length of gene symbol (max 255) |
| +N+2 | M | [u8; M] | gene | UTF-8 gene symbol |
| ... | 24 | [u8; 24] | chrom_digest | Truncated refget digest |
| ... | 1 | i8 | strand | +1 forward, -1 reverse |
| ... | 1 | u8 | mane_flags | bit 0 = MANE Select, bit 1 = MANE Plus Clinical |
| ... | 4 | u32 LE | cds_start | 0xFFFFFFFF if None |
| ... | 4 | u32 LE | cds_end | 0xFFFFFFFF if None |
| ... | 2 | u16 LE | exon_count | Number of exons |
| ... | 8*exons | [(u32, u32)] | exons | (start, end) pairs, LE |
Accession Index Format (16 bytes per entry)
Section titled “Accession Index Format (16 bytes per entry)”The index is sorted by accession_hash in ascending order to enable binary search.
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0 | 8 | u64 LE | accession_hash | FNV-1a hash of accession bytes |
| +8 | 8 | u64 LE | record_offset | Byte offset of record in file |
MANE Gene Index Format
Section titled “MANE Gene Index Format”Present only if at least one transcript is MANE Select. It starts with a u64 LE entry count, followed by 16-byte entries sorted by hash:
| Offset | Size | Type | Name | Description |
|---|---|---|---|---|
| +0 | 8 | u64 LE | gene_hash | FNV-1a hash of the upper-cased gene symbol |
| +8 | 8 | u64 LE | record_offset | Byte offset of the MANE Select record |
Hash Function
Section titled “Hash Function”Both indexes use FNV-1a 64-bit hashing:
const FNV_OFFSET: u64 = 0xcbf29ce484222325;const FNV_PRIME: u64 = 0x100000001b3;
fn fnv1a_64(data: &[u8]) -> u64 { let mut hash = FNV_OFFSET; for &byte in data { hash ^= byte as u64; hash = hash.wrapping_mul(FNV_PRIME); } hash}Sentinel Values
Section titled “Sentinel Values”0xFFFFFFFF(u32 max) indicates None forcds_startandcds_endmane_index_offset = 0means the file has no MANE gene index
Version History
Section titled “Version History”| Version | Changes |
|---|---|
| 1 | Initial format |
| 2 | Header grows to 40 bytes with mane_index_offset; each record gains a mane_flags byte; optional MANE gene index. Readers accept only version 2 |
Design Rationale
Section titled “Design Rationale”Why binary instead of JSON?
Section titled “Why binary instead of JSON?”cdot JSON files must be fully parsed and held in memory before any lookup. The binary format avoids that:
- Read only what you need: With mmap or pread, only the parts of the file you touch are read
- O(log n) lookup: Binary search on a sorted index
- Few allocations on hot paths: Small stores are decoded once, and
CoordinateMapperWriterreuses its buffers
Why FNV-1a hashing?
Section titled “Why FNV-1a hashing?”FNV-1a is simple, fast, and deterministic. Hash collisions are handled by linear probing with full accession string comparison.
Why 24-byte digests?
Section titled “Why 24-byte digests?”A refget sequence digest (sha512t24u) is the first 24 bytes of a SHA-512 hash. The familiar SQ. form is those 24 bytes written as 32 base64url characters. Storing the raw 24 bytes saves space, and encoding them again gives back the exact refget key.
Why exons inline?
Section titled “Why exons inline?”Storing exons directly in each record avoids pointer chasing and keeps related data contiguous for better cache locality.
Why three backends?
Section titled “Why three backends?”The lookup code is the same for all of them; only the way bytes are read changes. Positioned reads are the safe default. Memory mapping lets many processes share one copy of a large store in the OS page cache. In-memory bytes need no file system, which is what makes the store work in WASM.
See Also
Section titled “See Also”- cdot project - Source of transcript JSON data
- MANE - Matched Annotation from NCBI and EMBL-EBI
- RefgetStore format - Sequence storage format