Repository navigation
seqair 0.2.0
A streaming-window BAM region reader, newtyped record and query indices, a unified
pileup API, and VCF/BCF encoder ergonomics.
Breaking
Pileup API
Readers::pileup(segment, depth)returns aPileupplan; finish it with.run().
pileup_with/pileup_with_reference→.mutate(f)/.with_reference(r)on the plan,
combinable in any order.PileupEngine::newtakes aPileupInput, minted only byRecordStore::prepare_for_pileup()
(which also returnsMateLinkStats). The engine used to silently assume position order and
linked mates.PileupEngine::take_store→reclaim_allocation— it returns an empty store that keeps its
slab capacity, not the records.PileupEngine::set_max_depthtakesNonZeroU32;0used to mean "emit nothing".PileupAlignment::strandremoved — useStrand::from(rec.flags).PileupColumn::pair_indelandPairIndelremoved →PileupColumn::mate_of, so your own
filters decide what the fragment says.
Record store
- Record indices are the
RecordIdxnewtype (bam::record_idx), not bareu32.u32::MAXis
unrepresentable, soOption<RecordIdx>is free and theu32::MAX"no mate" sentinel is gone. - Records are read through
RecordStore::record(idx) -> Option<RecordRef<'_>>, which does not panic.
The by-index readers (try_record,qname(idx),cigar(idx),seq(idx),seq_at,qual(idx),
aux(idx),extra(idx),mate_overlap(idx)) are methods on the handle instead —rec.qname(),
rec.cigar(),rec.base_at(qpos), … — with the record's own fields viaDeref. The handle borrows
the store, soclear, pushes,sort_by_posanddedupare rejected while one is alive.
set_alignment/write_store_recordnow reportNoSuchRecord;extra_mutreturnsOption.
UseRecordStore::indices()instead of0..store.len() as u32.
Coordinates
- Query offsets are the
QPosnewtype, deliberately not interconvertible withPos. Affects
AlignedPair,MatchPosition/MatchedBase/MatchedRef,CigarPosInfo,PileupOp,
PileupAlignment::qpos(),CigarMapping::soft_clip_qpos_at, and
BaseModState::mod_at_qpos/is_unmodified. AlignedPair::Insertion.qpos→first_inserted(also onAlignedPairWithRead/WithRef):
it names the first inserted base, whilePileupOp::Insertion.qposnames the matched base
before the run.
Readers and writers
RegionBufstreams a bounded sliding window instead of loading whole regions: it borrows the
reader (soRegionBuf<'a, _>), no longer implementsUnwindSafe, andloadis gone
(ensure_available/advance_range).- Unmapped-read filtering unified on
filter_raw:IndexedBamReader::keep_unmapped/keeps_unmapped
removed,FilterRawFieldsis type-level enums (publicraw_cigar_bytes,cigar_ops,packed_seq,
basesfields gone;end_posisOption<Pos0>), newRejectUnmappedcustomizer. TargetInfoAccessremoved fromseqair::bam::header.ReaderErrorgained a variant separating "region end past contig" from "exceedsi32::MAX".
VCF/BCF
- VCF headers always declare
VCFv4.5:VcfHeaderBuilder::file_format()removed,
VcfHeader::file_format()returns&'static str, version isVcfHeader::FILE_FORMAT.
A cardinality is versioned, so a header free to declare an older version could promise a grammar
it then violates. EncodeInfoandEncodeFormatremoved;FormatEncodergained three required methods
(format_ints,format_floats,format_string).VcfHeaderError::TooManyFieldsnow carries data.
Added
- Reference hooks on the pileup plan:
Pileup::mutate(f)rewrites the fetchedRecordStore
before pileup (local realignment viaset_alignment) and receives the segment'sRefSeq— the
same onePileupColumn::reference_basereports.with_reference(RefSeq)drives the engine from a
reference you already hold;reference_covers_reads()widens the fetch to the span the records
actually cover. - Mate linking:
RecordStore::link_mates()pairs a template's primary alignments once per store.
Exposed asPileupAlignment::mate_idx()/in_mate_overlap(),AlignmentView::qname_hash(),
PileupColumn::find_record()/mate_of(). - Window queries over a prepared store:
records_overlapping(start, end)onPileupInput,
PileupEngineandPileupColumn— a binary search over a runningend_posmaximum, so one long
read only costs the windows it overlaps. PlusPileupInput::store(). PileupColumn::position_of(record_idx)/alignment_at(index);
AlignmentView::inserted_bases/inserted_quals;PileupAlignment::indel_after;
engine.set_soft_clip_overhang(n)to keepnsoft-clipped bases at alignment fringes.- Indexed FASTQ references: the FAI parser accepts the sixth
qual_offsetcolumn;
FaiEntry::is_fastq()/qual_byte_offset(). htslib-validated. - VCF FORMAT parity:
FormatInts,FormatString, array-of-floats, percent-encoded string values,
htslib-compatible duplicate-field overwrite.Writer::finishreturns a self-serializing
CoordinateIndex. - Byte-aware segment planning to bound per-segment memory.
Fixed
BgzfWriter::virtual_offset()could name a byte inside a record when a write filled a block
exactly, corrupting every co-produced CSI/BAI/TBI offset that landed there — htslib rejected such
files outright. ~0.4 % of blocks fill exactly. Compressed bytes are unchanged; only the recorded
offsets differ.- Region queries stop at the query end instead of inflating BGZF blocks far past it: a 1 kb query
ontests/data/test.bamexamined 2529 records to keep 313; now 313. - SAM and CRAM region queries lost records overlapping by the query's last base (SAM tested a
half-open range against an exclusiveend_pos; CRAI compared a 1-based start to a 0-based query).
All readers now share one convention — 0-based, both ends inclusive. RecordStore::dedupsorts first. It collapses consecutive equal records, so an unsorted store
silently kept the duplicates the method exists to remove.- BCF genotypes carry the phase bit on the first allele. Hardcoded unphased, so htslib rendered a
fully phased0|1as/0|1. SlimRecord::end_posis htslib-compatible on unmapped reads (end_pos == pos).- Region queries no longer error on unplaced reads (
pos = -1/AP = 0). - FASTA: an untrusted
.faican no longer drive a multi-PiB allocation, serve bytes from a wrapped
offset, or panic onlinebases == 0; errors carry the path and distinguishPlainGzipUnsupported
fromGziIndexNotFound. - CRAM: missing rANS Nx16 and allocation-size validation.
- Empty-sample BCF missing-value handling; pileup depth cap always set.
Performance
- Region queries decompress about what htslib's iterator does: 45.8 GB → 22.7 GB on chr12 in 10 kb
tiles, from stopping at the query end. - Pileup columns:
Vec::retaineviction, entries written into one reserved block, pooled scratch
buffers, no cached qname hash,Base::known_indexas a table lookup. Column phase 3.80 s → 3.25 s
on NA12878 chr12 (20 Mb); 1.06x end-to-end in rastair, byte-identical output. - Plain-FASTA fetches use one positional
preadper span (~28 % faster on short slices) and forks
share a handle; fetched spans are stripped and uppercased on a vectorized path. CompactOp16 → 12 bytes;CigarSlicecollapsed to&[CigarOp].- New
pileup_tiledbench andexamples/tiled_pileupmeasure the many-small-queries pattern a
variant caller actually has.