UNPKG

@gmod/cram

Version:

read CRAM files with pure Javascript

395 lines (335 loc) 10.3 kB
/* eslint-disable no-var */ // @ts-nocheck /* * Copyright (c) 2019 Genome Research Ltd. * Author(s): James Bonfield * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * 3. Neither the names Genome Research Ltd and Wellcome Trust Sanger * Institute nor the names of its contributors may be used to endorse * or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY GENOME RESEARCH LTD AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GENOME RESEARCH * LTD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import RangeCoder from './arith_sh.ts' import ByteModel from './byte_model.ts' import IOStream from './iostream.ts' // ---------------------------------------------------------------------- // Main arithmetic entry function: decodes a compressed src and // returns the uncompressed buffer. function read_array(src, tab, size) { let j = 0 // array value let z = 0 // array index: tab[j] let last = -1 // Remove first level of run-length encoding const R = new Array(1024) // runs while (z < size) { const run = src.ReadByte() R[j++] = run z += run if (run == last) { let copy = src.ReadByte() z += run * copy while (copy--) { R[j++] = run } } last = run } // Now expand runs in R to tab, noting 255 is max run let i = 0 j = 0 z = 0 while (z < size) { let run_len = 0 do { var part = R[j++] run_len += part } while (part == 255) while (run_len--) { tab[z++] = i } i++ } } const QMAX = 256 const FLAG_DEDUP = 2 const FLAG_FLEN = 4 const FLAG_SEL = 8 // whether selector is used in context const FLAG_QMAP = 16 const FLAG_PTAB = 32 const FLAG_DTAB = 64 const FLAG_QTAB = 128 const GFLAG_MULTI_PARAM = 1 const GFLAG_HAVE_STAB = 2 const GFLAG_DO_REV = 4 // Compute a new context from our current state and qual q function fqz_update_ctx(params, state, q) { let last = params.context state.qctx = (state.qctx << params.qshift) + params.qtab[q] // >>> 0 last += (state.qctx & ((1 << params.qbits) - 1)) << params.qloc // >>> 0 if (params.do_pos) { last += params.ptab[Math.min(state.p, 1023)] << params.ploc } if (params.do_delta) { last += params.dtab[Math.min(state.delta, 255)] << params.dloc // Is it better to use q here or qtab[q]? // If qtab[q] we can map eg [a-z0-9A-Z]->0 ,->1 and have // delta being a token number count into comma separated lists? state.delta += state.prevq != q ? 1 : 0 state.prevq = q } if (params.do_sel) { last += state.s << params.sloc } state.p-- return last & 0xffff } function decode_fqz_single_param(src) { const p = {} // params // Load FQZ parameters p.context = src.ReadUint16() p.pflags = src.ReadByte() p.do_dedup = p.pflags & FLAG_DEDUP p.fixed_len = p.pflags & FLAG_FLEN p.do_sel = p.pflags & FLAG_SEL p.do_qmap = p.pflags & FLAG_QMAP p.do_pos = p.pflags & FLAG_PTAB p.do_delta = p.pflags & FLAG_DTAB p.do_qtab = p.pflags & FLAG_QTAB p.max_sym = src.ReadByte() let x = src.ReadByte() p.qbits = x >> 4 p.qshift = x & 15 x = src.ReadByte() p.qloc = x >> 4 p.sloc = x & 15 x = src.ReadByte() p.ploc = x >> 4 p.dloc = x & 15 // Qual map, eg to "unbin" Illumina qualities p.qmap = new Array(256) if (p.pflags & FLAG_QMAP) { for (var i = 0; i < p.max_sym; i++) { p.qmap[i] = src.ReadByte() } } else { // Useful optimisation to speed up main loop for (var i = 0; i < 256; i++) { p.qmap[i] = i } // NOP } // Read tables p.qtab = new Array(1024) if (p.qbits > 0 && p.pflags & FLAG_QTAB) { read_array(src, p.qtab, 256) } else { // Useful optimisation to speed up main loop for (var i = 0; i < 256; i++) { p.qtab[i] = i } // NOP } p.ptab = new Array(1024) if (p.pflags & FLAG_PTAB) { read_array(src, p.ptab, 1024) } p.dtab = new Array(256) if (p.pflags & FLAG_DTAB) { read_array(src, p.dtab, 256) } return p } function decode_fqz_params(src) { const gparams = { max_sym: 0, } // Check fqz format version const vers = src.ReadByte() if (vers != 5) { console.error('Invalid FQZComp version number') return } const gflags = src.ReadByte() const nparam = gflags & GFLAG_MULTI_PARAM ? src.ReadByte() : 1 let max_sel = gflags.nparam > 1 ? gflags.nparam - 1 : 0 // Note max_sel, not num_sel const stab = new Array(256) if (gflags & GFLAG_HAVE_STAB) { max_sel = src.ReadByte() read_array(src, stab, 256) } else { for (var i = 0; i < nparam; i++) { stab[i] = i } for (; i < 256; i++) { stab[i] = nparam - 1 } } gparams.do_rev = gflags & GFLAG_DO_REV gparams.stab = stab gparams.max_sel = max_sel gparams.params = new Array(gparams.nparam) for (let p = 0; p < nparam; p++) { gparams.params[p] = decode_fqz_single_param(src) if (gparams.max_sym < gparams.params[p].max_sym) { gparams.max_sym = gparams.params[p].max_sym } } return gparams } function fqz_create_models(gparams) { const model = {} model.qual = new Array(1 << 16) for (var i = 0; i < 1 << 16; i++) { model.qual[i] = new ByteModel(gparams.max_sym + 1) } // +1 as max value not num. values model.len = new Array(4) for (var i = 0; i < 4; i++) { model.len[i] = new ByteModel(256) } model.rev = new ByteModel(2) model.dup = new ByteModel(2) if (gparams.max_sel > 0) { model.sel = new ByteModel(gparams.max_sel + 1) } // +1 as max value not num. values return model } // Initialise a new record, updating state. // Returns 1 if dup, otherwise 0 function decode_fqz_new_record(src, rc, gparams, model, state, rev) { // Parameter selector state.s = gparams.max_sel > 0 ? model.sel.ModelDecode(src, rc) : 0 state.x = gparams.stab[state.s] const params = gparams.params[state.x] // Reset contexts at the start of each new record if (params.fixed_len >= 0) { // Not fixed or fixed but first record var len = model.len[0].ModelDecode(src, rc) len |= model.len[1].ModelDecode(src, rc) << 8 len |= model.len[2].ModelDecode(src, rc) << 16 len |= model.len[3].ModelDecode(src, rc) << 24 if (params.fixed_len > 0) { params.fixed_len = -len } } else { len = -params.fixed_len } state.len = len if (gparams.do_rev) { rev[state.rec] = model.rev.ModelDecode(src, rc) } state.is_dup = 0 if (params.pflags & FLAG_DEDUP) { if (model.dup.ModelDecode(src, rc)) { state.is_dup = 1 } } state.p = len // number of remaining bytes in this record state.delta = 0 state.qctx = 0 state.prevq = 0 state.rec++ } function decode_fqz(src: IOStream, q_lens: number) { // Decode parameter block const n_out = src.ReadUint7() const gparams = decode_fqz_params(src) if (!gparams) { return } var params = gparams.params const rev = new Array(q_lens.length) // Create initial models const model = fqz_create_models(gparams) // Create our entropy encoder and output buffers const rc = new RangeCoder(src) rc.RangeStartDecode(src) const output = new Uint8Array(n_out) // Internal FQZ state const state = { qctx: 0, // Qual-only sub-context prevq: 0, // Previous quality value delta: 0, // Running delta (q vs prevq) p: 0, // Number of bases left in current record s: 0, // Current parameter selector value (0 if unused) x: 0, // "stab" tabulated copy of s len: 0, // Length of current string is_dup: 0, // This string is a duplicate of last rec: 0, // Record number } // The main decode loop itself let i = 0 // position in output buffer while (i < n_out) { if (state.p == 0) { decode_fqz_new_record(src, rc, gparams, model, state, rev) if (state.is_dup > 0) { if (model.dup.ModelDecode(src, rc)) { // Duplicate of last line for (let x = 0; x < len; x++) { output[i + x] = output[i + x - state.len] } i += state.len state.p = 0 continue } } q_lens.push(state.len) var params = gparams.params[state.x] var last = params.context } // Decode the current quality (possibly mapped via qmap) const Q = model.qual[last].ModelDecode(src, rc) // if (params.do_qmap) // output[i++] = params.qmap[Q]; // else // output[i++] = Q output[i++] = params.qmap[Q] // optimised version of above last = fqz_update_ctx(params, state, Q) } if (gparams.do_rev) { reverse_qualities(output, n_out, rev, q_lens) } return output } function reverse_qualities(qual, qual_len, rev, len) { let rec = 0 let i = 0 while (i < qual_len) { if (rev[rec]) { let j = 0 let k = len[rec] - 1 while (j < k) { const tmp = qual[i + j] qual[i + j] = qual[i + k] qual[i + k] = tmp j++ k-- } } i += len[rec++] } } export function decode(src: Uint8Array, q_lens) { const stream = new IOStream(src) return decode_fqz(stream, q_lens) }