Read BAM files and their BAI or CSI indexes in node or the browser.
npm install @gmod/bamimport { BamFile } from '@gmod/bam'
const bam = new BamFile({
bamUrl: 'https://example.com/file.bam',
baiUrl: 'https://example.com/file.bam.bai',
})
const records = await bam.getRecordsForRange('chr1', 10000, 20000)
for (const record of records) {
console.log(record.name, record.start, record.CIGAR)
}URLs work in node and the browser, and local files take bamPath and baiPath
instead.
With bamUrl, bam-js sends a separate HTTP request for each byte range a query
reads (this visualizer draws
them).
@gmod/range-cache-filehandle
fetches only the bytes it does not hold yet, merging nearby reads into one
request, and later queries reuse the bytes it already has:
import { RemoteFileWithRangeCache } from '@gmod/range-cache-filehandle'
const bam = new BamFile({
bamFilehandle: new RemoteFileWithRangeCache(url),
baiFilehandle: new RemoteFileWithRangeCache(`${url}.bai`),
})Coordinates are 0-based half-open, where samtools view takes 1-based closed,
so samtools view file.bam ctgA:1-50000 returns the same records as
getRecordsForRange('ctgA', 0, 50000). A query for a reference the file lacks
returns [].
record.name // read name
record.start // 0-based start
record.CIGAR // '50M2I48M'
record.seq // read sequence
record.getTag('NM') // one auxiliary tag
record.isReverseComplemented() // the usual SAM flagsQueries return records unfiltered, and overlapping queries share them, so treat
them as read-only. getTag decodes one tag instead of all of them, which makes
it the cheap way to filter:
const records = (await bam.getRecordsForRange('chr1', 0, 100000)).filter(
r => r.isProperlyPaired() && !r.isSecondary() && r.getTag('RG') === 'rg1',
)docs/api.md lists every field and method.
record.getMismatches() lists each difference between a read and the reference:
substitutions, insertions, deletions, reference skips and clips.
// a read at 100 with CIGAR 5M1I4M2D3M, SEQ ACGGTCAACGTTA and MD 3A5^GG3
for (const { code, refPos, length, bases } of record.getMismatches()) {
console.log(String.fromCharCode(code), refPos, length, bases)
}
// X 103 1 G substitution to G
// I 105 0 C one base inserted before 105
// D 109 2 two reference bases deleted from 109A substitution needs the read's MD tag or the reference bases. Most aligners
leave MD off, so pass fetchReferenceSequence: (refName, start, end) => ...
to BamFile. bam-js calls it only for a query with a read that lacks MD.
docs/api.md has the fields and options.
A worker pool spreads BGZF decompression across threads, which measured 2.7-4.1x faster on the pool's own fixtures:
import { getSharedWorkerPool } from '@gmod/bgzf-filehandle'
const bam = new BamFile({
bamUrl,
baiUrl,
bgzfWorkerPool: getSharedWorkerPool(),
})The pool is undefined under node and wherever the host forbids workers, and
bam-js then decompresses in-process, so the option is safe to pass everywhere.
bgzf-filehandle's docs
cover pool size and lifecycle.
For an unsorted or name-sorted BAM, streamBamRecords reads the file from start
to end, an array of records at a time:
import { streamBamRecords } from '@gmod/bam'
for await (const records of streamBamRecords({ bamUrl: 'unsorted.bam' })) {
for (const record of records) {
// ...
}
}docs/api.md has the options.
import { HtsgetFile } from '@gmod/bam'
const bam = new HtsgetFile({
baseUrl: 'http://htsnexus.rnd.dnanex.us/v1/reads',
trackId: 'BroadHiSeqX_b37/NA12878',
})
const records = await bam.getRecordsForRange('1', 2000000, 2000001)docs/api.md shows how to add authentication.
- docs/api.md: every option, method and
BamRecordfield - docs/cigar-and-md.md: what
CIGARandMDcontain, and how the mismatch walk reads them - docs/caching.md: sizing the chunk cache
- docs/dataflow.md: a diagram of a query from index to records
- docs/optimizations.md: the measurements behind the read path's design
- agent-docs/architecture-decision-records/: design decisions, with their measurements
- CONTRIBUTING.md: development and release
Written with NHGRI funding as part of JBrowse. If you use this in a publication, please cite the most recent JBrowse paper at jbrowse.org.
MIT © Colin Diesh