UNPKG

@gmod/cram

Version:

read CRAM files with pure Javascript

187 lines (146 loc) 5.71 kB
# @gmod/cram [![NPM version](https://img.shields.io/npm/v/@gmod/cram.svg?style=flat-square)](https://npmjs.org/package/@gmod/cram) ![Build Status](https://img.shields.io/github/actions/workflow/status/GMOD/cram-js/publish.yml?branch=main) Read CRAM files with pure JS, works in node or the browser. Supports CRAM 2.x and 3.x, `.crai` indexes, and all CRAM v3 block codecs (gzip, bzip2, lzma, rANS, arithmetic, fqzcomp, tok3). See [docs/CODEC_SUPPORT.md](docs/CODEC_SUPPORT.md). ## Install ```bash npm install @gmod/cram ``` ## Usage ```js import { IndexedCramFile, CraiIndex } from '@gmod/cram' import { IndexedFasta } from '@gmod/indexedfasta' const fasta = new IndexedFasta({ path: '/path/to/reference.fa', faiPath: '/path/to/reference.fa.fai', }) const idToName = [] const nameToId = {} const indexedFile = new IndexedCramFile({ cramPath: '/path/to/file.cram', // alternatives: cramUrl, cramFilehandle (see generic-filehandle2) index: new CraiIndex({ path: '/path/to/file.cram.crai', // alternatives: url, filehandle }), seqFetch: async (seqId, start, end) => { // seqId is numeric; coordinates are 1-based but IndexedFasta is 0-based return fasta.getSequence(idToName[seqId], start - 1, end) }, checkSequenceMD5: false, }) // Build numeric refId <-> name mappings from the SAM header const samHeader = await indexedFile.cram.getSamHeader() samHeader .filter(l => l.tag === 'SQ') .forEach((sqLine, refId) => { sqLine.data.forEach(item => { if (item.tag === 'SN') { nameToId[item.value] = refId idToName[refId] = item.value } }) }) // Fetch records for a range (1-based, closed coordinates) const records = await indexedFile.getRecordsForRange( nameToId['chr1'], 10000, 20000, ) for (const record of records) { console.log(record.readName, record.alignmentStart, record.mappingQuality) // Extract variants from read features for (const feature of record.readFeatures ?? []) { if (feature.code === 'X') { // SNP: single base substitution console.log(`SNP at ${feature.refPos}: ${feature.ref}->${feature.sub}`) } else if (feature.code === 'I') { // Insertion: full inserted sequence console.log(`Insertion at ${feature.refPos}: ${feature.data}`) } else if (feature.code === 'i') { // Insertion: padding only (no sequence stored) console.log(`Insertion at ${feature.refPos} (no sequence)`) } else if (feature.code === 'D') { // Deletion: bases deleted from reference console.log(`Deletion at ${feature.refPos}: ${feature.data} bases`) } } } ``` See the [example directory](./example) for browser usage with `<script>` tag and the bundled `cram-bundle.js`. For more complex operations like generating CIGAR strings from read features, see the JBrowse [readFeaturesToNumericCIGAR](https://github.com/GMOD/jbrowse-components/blob/main/plugins/alignments/src/CramAdapter/readFeaturesToNumericCIGAR.ts) implementation. ## API ### `IndexedCramFile` ```js new IndexedCramFile({ cramPath, // local path cramUrl, // remote URL cramFilehandle, // generic-filehandle2 compatible handle index, // CraiIndex instance (or any object with getEntriesForRange) seqFetch, // async (seqId, start, end) => string checkSequenceMD5, // default true; set false to avoid large reference fetches cacheSize, // max cached records, default 20000 }) ``` - `getRecordsForRange(seqId, start, end, opts?)``Promise<CramRecord[]>` — 1-based closed coords. `opts`: `{ viewAsPairs, pairAcrossChr, maxInsertSize }` - `hasDataForReferenceSequence(seqId)``Promise<boolean>` ### `CraiIndex` Takes `{ path, url, filehandle }` — one of the three is required. ### `CramRecord` **Properties:** - `readName` — read name - `sequenceId` — numeric reference ID - `alignmentStart` — 1-based start position - `qualityScores``Uint8Array` of per-base quality scores - `readFeatures` — array of read features (see below) - `tags` — auxiliary tags object **Flag methods** (all return `boolean`): - `isPaired()` - `isProperlyPaired()` - `isSegmentUnmapped()` - `isMateUnmapped()` - `isReverseComplemented()` - `isMateReverseComplemented()` - `isRead1()` - `isRead2()` - `isSecondary()` - `isFailedQc()` - `isDuplicate()` - `isSupplementary()` **Methods:** - `getReadBases()``string | null | undefined` — returns the read sequence string. Requires `seqFetch` to be configured and is populated automatically by `getRecordsForRange`. - `getCigarString()``string` — returns the CIGAR string describing the read's alignment (e.g. `"50M2I48M"`), reconstructed from the read features. Substitutions and mismatches are reported as `M` per the plain CIGAR convention; unmapped reads return `"*"`. Does not require `seqFetch`. ### ReadFeatures Each entry in `record.readFeatures`: - `code` — feature type (one of `bqBXIDiQNSPH`, see CRAM spec §8) - `pos` — read position (1-based) - `refPos` — reference position (1-based) - `ref` / `sub` — reference and substituted base (code `X` only) ### Error classes - `CramUnimplementedError` — unimplemented spec feature - `CramMalformedError` — malformed file data - `CramBufferOverrunError` — read past end of data ## Academic Use Written with [NHGRI](http://genome.gov) funding as part of [JBrowse](http://jbrowse.org). If you use this in a publication, please cite the most recent JBrowse paper at [jbrowse.org](http://jbrowse.org). ## License MIT © [Robert Buels](https://github.com/rbuels) ## Publishing [Trusted publishing](https://docs.npmjs.com/about-trusted-publishing) via GitHub Actions. ```bash pnpm version patch # or minor/major ```