Coordinate frames¶
FiberHMM output uses three coordinate systems. Mixing them up misplaces every call on reverse-strand reads, so it is worth knowing which is which.
| Frame | Origin and direction | Used by |
|---|---|---|
| Molecular (original fiber) | the first base of the molecule as sequenced, 5'→3' of the read | per-read tags: ns/nl, as/al, MA/AQ/AN, and fibertools' Ma/Aq/An |
| Query (SEQ) | the first base of the SEQ field as stored in the BAM |
MM/ML positions after decoding, pysam query_* indexes, posteriors arrays |
| Reference | the reference contig, 0-based half-open | BED/bigBed tracks, consensus tables, SNP masks, --region/--bed inputs |
Molecular versus query¶
For a forward-aligned read the two are identical. For a reverse-aligned read
(flag 0x10) the BAM stores the reverse complement of the molecule, so the
query frame runs backwards along the molecule. An interval [s, s+l) in
query coordinates is written in molecular coordinates as
and the left and right edge bytes (el, er) are swapped, because the
molecule's left edge is the query's right edge. Every list is sorted by
molecular start. This matches the
Molecular-annotation spec
and fibertools.
How FiberHMM tells the frame of a BAM¶
The ns/nl/as/al arrays carry no frame of their own, and two
generations of writers used different frames: FiberHMM 2.12 and earlier
wrote query frame, while FiberHMM 2.13 and later and every fibertools
version write molecular frame. FiberHMM therefore reads the frame from the
header's @PG provenance, with the rule it shares with FiberBrowser
(Footprint-tag coordinate frame):
- The
@PGhistory is followed through itsPPlinks, not header order. Each chain (samtools mergekeeps one per input) votes with the frame of its last footprint writer. fibertools-rs nucleosome commands (ft predict-m6a,m6a,add-nucleosomes,fire,fiber-hmm, the olderpredictandadd) write molecular frame.fiberhmm-call,-apply,-recall-tfsand-recall-nucswrite molecular frame when they declarecoord=molecular, otherwise query frame. Current releases always declare it, both in their@PGDSand as an@CO fiberhmm:coord=molecularline. Other FiberHMM tools pass the tags through and do not vote. - If the votes agree, they decide. With no votes, a
coord=moleculardeclaration anywhere means molecular, otherwise query frame (unmarked FiberHMM 2.12 output). - Merged histories whose votes disagree are molecular when
coord=molecularis declared. Otherwise FiberHMM does not guess:fiberhmm-recall-tfsand-recall-nucsstop at the first read that carriesns/nl/as/aland ask for--input-frame queryor--input-frame molecular. Reads whose only footprints are fibertoolsMatags need no frame.
Tools that copy footprint tags without rewriting them (fiberhmm-dedup,
-pair, -merge, -tag-m5c, -call-m5c, -tag-consensus,
-strand-rescue-annotate) add coord=molecular to their own @PG DS when
the tags they carried are molecular. They record nothing otherwise.
Consensus reads legacy tags only for Hia5 input without MA. It applies
molecular frame by itself only when fibertools is the last writer on every
chain. Reads a FiberHMM caller left without MA are ones it skipped, and
they keep whatever tags they had before. Otherwise it asks you to set its
legacy Hia5 annotation frame. fiberhmm-tag-m5c --input-frame and
fiberhmm-call-m5c --tag-input-frame force the frame of their DAF inputs.
One case cannot be detected from the header. FiberHMM 2.12 or earlier wrote
query-frame tags and no @PG. Run on a BAM that had already been through
ft predict-m6a, it leaves a header that shows only the fibertools command.
Pass --input-frame query for such files.
MA, AQ and AN are written only by FiberHMM, so their frame comes from
the coord=molecular declaration (unmarked MA is query frame, except as
below).
Headers without frame evidence¶
A header has frame evidence when it shows a footprint writer on a @PG chain
or declares coord=molecular anywhere. A pipeline that strips @PG (or keeps
only unrelated programs) leaves none. Only then does FiberHMM look at the
reads:
MA, per annotation group. A group written with the.strand (nuc.QQQ:,msp.:; FiberHMM 2.13.2 and later write every group that way) is molecular. A+/-group (FiberHMM 2.13.1 and earlier) is query frame. One group never decides another, so a.m5C layer appended later (ddda_mcg.,ddda_ucg.) leaves older+footprints in query frame.- Legacy
ns/nl/as/al. They are molecular on a read whoseMAhas a.nuc,msportfgroup, because the same writer wrote both. An m5C group does not count. On every other read they are taken as query frame. Recall,fiberhmm-tag-m5candfiberhmm-call-m5cprint a warning when they fall back to this rule; pass--input-frameto decide it yourself.
A FIBERHMM-CHEMISTRY declaration never decides the frame. Pass-through
producers such as fiberhmm-merge --recall and fiberhmm-pair add one while
carrying older reads unchanged.
fibertools Ma tags¶
fibertools-rs 0.13 and later no longer write ns/nl/as/al. Nucleosomes,
MSPs and FIRE elements go into Ma/Aq/An instead, for example
Ma:Z:8000;nuc.:297-154,...;msp.:451-16,...;fire.Q:804-397,... with one
Aq byte per fire.Q element. These tags follow the same
Molecular-annotation spec as FiberHMM's MA and are always molecular frame.
FiberHMM reads Ma wherever it reads MA or the legacy arrays: in
recall-tfs/recall-nucs, consensus evidence loading and extract. If a read
has both, FiberHMM's own MA is used. Recall writes its calls to
MA/AQ (and ns/nl/as/al) and leaves the fibertools Ma tag in
place. Consensus BAM export copies a read's Ma annotations into the MA
it writes, so the family layers do not hide them. The copy is in the file's
MA frame, which is query frame unless the header declares
coord=molecular. The family layers are molecular, as their
FIBERHMM-CONSENSUS-MA contract declares.
Converting to query or reference coordinates¶
import pysam
from fiberhmm.io.ma_tags import flip_intervals_to_seq, parse_ma_tag
for read in pysam.AlignmentFile("calls.bam"):
if not read.has_tag("MA"):
continue
tf = parse_ma_tag(read.get_tag("MA"))["tf"] # [(start, length)], 0-based, molecular
if not tf:
continue
starts, lengths = flip_intervals_to_seq(*zip(*tf), read) # query frame
q2r = read.get_reference_positions(full_length=True) # query index -> reference
ref_intervals = [(q2r[s], q2r[s + l - 1] + 1) for s, l in zip(starts, lengths)
if q2r[s] is not None and q2r[s + l - 1] is not None]
flip_intervals_to_seq returns forward reads' intervals unchanged and
applies the formula above to reverse reads (the formula is its own inverse).
fiberhmm-extract does this projection for you and writes reference
coordinates.
MA strings are 1-based¶
Inside the MA tag, each interval is written start-length with a
1-based start, as the spec requires. parse_ma_tag returns 0-based
starts. The legacy ns/as arrays are 0-based.
MA:Z:3744;nuc.QQQ:87-146,... # first nucleosome: 1-based start 87
ns:B:I,86,... # the same nucleosome: 0-based start 86
Other frames¶
- Posteriors (
fiberhmm-posteriors): one value per query position; the footprint columns are reference intervals. - Consensus (
fiberhmm-consensus): all tables are reference coordinates, except pooled cross-locus runs, which use the oriented window frame0 … width(a minus-strand window maps base i toend − 1 − i). - Strand rescue edge bytes
q1/q2are molecular-left and molecular-right. - Circular molecules (
fiberhmm-call -r): a feature that crosses the read origin is written as two intervals, one at each end of the read, that share anANname (see Annotations).