UNPKG

@gmod/cram

Version:

read CRAM files with pure Javascript

346 lines 11.7 kB
"use strict"; /* eslint-disable no-var */ // @ts-nocheck var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.decode = decode; /* * 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. */ const arith_sh_ts_1 = __importDefault(require("./arith_sh.js")); const byte_model_ts_1 = __importDefault(require("./byte_model.js")); const iostream_ts_1 = __importDefault(require("./iostream.js")); // ---------------------------------------------------------------------- // 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 byte_model_ts_1.default(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 byte_model_ts_1.default(256); } model.rev = new byte_model_ts_1.default(2); model.dup = new byte_model_ts_1.default(2); if (gparams.max_sel > 0) { model.sel = new byte_model_ts_1.default(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, q_lens) { // 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 arith_sh_ts_1.default(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++]; } } function decode(src, q_lens) { const stream = new iostream_ts_1.default(src); return decode_fqz(stream, q_lens); } //# sourceMappingURL=fqzcomp.js.map