From 9bf898fa10b63cf670c8dfff905514c669a06c50 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 30 Oct 2014 11:33:43 -0400 Subject: [PATCH 01/38] r963: expose mask_level --- fastmap.c | 3 ++- main.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fastmap.c b/fastmap.c index 18f3b0e..25bf822 100644 --- a/fastmap.c +++ b/fastmap.c @@ -55,7 +55,7 @@ int main_mem(int argc, char *argv[]) opt = mem_opt_init(); memset(&opt0, 0, sizeof(mem_opt_t)); - while ((c = getopt(argc, argv, "epaFMCSPHVYjk:c:v:s:r:t:R:A:B:O:E:U:w:L:d:T:Q:D:m:I:N:W:x:G:h:y:K:")) >= 0) { + while ((c = getopt(argc, argv, "epaFMCSPHVYjk:c:v:s:r:t:R:A:B:O:E:U:w:L:d:T:Q:D:m:I:N:W:x:G:h:y:K:X:")) >= 0) { if (c == 'k') opt->min_seed_len = atoi(optarg), opt0.min_seed_len = 1; else if (c == 'x') mode = optarg; else if (c == 'w') opt->w = atoi(optarg), opt0.w = 1; @@ -87,6 +87,7 @@ int main_mem(int argc, char *argv[]) else if (c == 'y') opt->max_mem_intv = atol(optarg), opt0.max_mem_intv = 1; else if (c == 'C') copy_comment = 1; else if (c == 'K') fixed_chunk_size = atoi(optarg); + else if (c == 'X') opt->mask_level = atof(optarg); else if (c == 'h') { opt0.max_XA_hits = opt0.max_XA_hits_alt = 1; opt->max_XA_hits = opt->max_XA_hits_alt = strtol(optarg, &p, 10); diff --git a/main.c b/main.c index f8f6995..a69ffeb 100644 --- a/main.c +++ b/main.c @@ -4,7 +4,7 @@ #include "utils.h" #ifndef PACKAGE_VERSION -#define PACKAGE_VERSION "0.7.10-r960-dirty" +#define PACKAGE_VERSION "0.7.10-r963-dirty" #endif int bwa_fa2pac(int argc, char *argv[]); From b7d0a2d537ae3cffa4e529cb3d85980ed751b70f Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 3 Nov 2014 11:47:23 -0500 Subject: [PATCH 02/38] first HLA typing script --- bwa-typeHLA.js | 171 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 bwa-typeHLA.js diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js new file mode 100644 index 0000000..da9ca28 --- /dev/null +++ b/bwa-typeHLA.js @@ -0,0 +1,171 @@ +var fuzzy = 3, min_qal = 100, drop_thres = 7; + +var file = arguments.length == 0? new File () : new File(arguments[0]); +var buf = new Bytes(); +var re_cigar = /(\d+)([MIDSH])/g; + +var len = {}, list = [], gcnt = []; +while (file.readline(buf) >= 0) { + var m, mm, line = buf.toString(); + var t = line.split("\t"); + if (t[0].charAt(0) == '@') { + if (t[0] == '@SQ' && (m = /LN:(\d+)/.exec(line)) != null && (mm = /SN:(\S+)/.exec(line)) != null) + len[mm[1]] = parseInt(m[1]); + continue; + } + var gene = null, exon = null; + if ((m = /^(HLA-[^\s_]+)_(\d+)/.exec(t[0])) != null) { + gene = m[1], exon = parseInt(m[2]) - 1; + if (gcnt[exon] == null) gcnt[exon] = {}; + gcnt[exon][gene] = true; + } + if (gene == null || exon == null || t[2] == '*') continue; + var x = 0, ts = parseInt(t[3]) - 1, te = ts, clip = [0, 0]; + while ((m = re_cigar.exec(t[5])) != null) { + var l = parseInt(m[1]); + if (m[2] == 'M') x += l, te += l; + else if (m[2] == 'I') x += l; + else if (m[2] == 'D') te += l; + else if (m[2] == 'S' || m[2] == 'H') clip[x==0?0:1] = l; + } + if (x < min_qal && clip[0] + clip[1] > 0) continue; + var tl = len[t[2]]; + var left = ts < clip[0]? ts : clip[0]; + var right = tl - te < clip[1]? tl - te : clip[1]; + var nm = (m = /\tNM:i:(\d+)/.exec(line)) != null? parseInt(m[1]) : 0; + list.push([t[2], gene, exon, ts, te, nm, left + right]); +} + +buf.destroy(); +file.close(); + +// identify the primary exon(s) +var pri_exon = [], n_pri_exons; +{ + var cnt = [], max = 0; + for (var e = 0; e < gcnt.length; ++e) { + if (gcnt[e] != null) { + var c = 0, h = gcnt[e]; + for (var x in h) ++c; + cnt[e] = c; + max = max > c? max : c; + } else cnt[e] = 0; + } + var pri_list = []; + for (var e = 0; e < cnt.length; ++e) { + if (cnt[e] == max) pri_list.push(e + 1); + pri_exon[e] = cnt[e] == max? 1 : 0; + } + warn("List of primary exons: ["+pri_list.join(",")+"]"); + n_pri_exons = pri_list.length; +} + +// convert strings to integers (for performance) +var ghash = {}, glist = [], chash = {}, clist = [], elist = []; +for (var i = 0; i < list.length; ++i) { + var g = glist.length, c = clist.length; + if (ghash[list[i][1]] == null) { + glist.push(list[i][1]); + ghash[list[i][1]] = g; + } + if (chash[list[i][0]] == null) { + clist.push(list[i][0]); + chash[list[i][0]] = c; + } + if (elist[g] == null) elist[g] = {}; + elist[g][list[i][2]] = true; +} + +// change to the per-exon representation +var exons = []; +for (var i = 0; i < list.length; ++i) { + var li = list[i]; + if (exons[li[2]] == null) exons[li[2]] = []; + exons[li[2]].push([chash[li[0]], ghash[li[1]], li[5] + li[6]]); +} + +// initialize genotype scores +var pair = []; +for (var i = 0; i < glist.length; ++i) { + pair[i] = []; + for (var j = 0; j <= i; ++j) + pair[i][j] = 0; +} + +// type each exon +for (var e = 0; e < exons.length; ++e) { +//for (var e = 1; e < 3; ++e) { + if (exons[e] == null) continue; + var ee = exons[e]; + // find good contigs and alleles that have good matches + var ch = {}, gh = {}; + for (var i = 0; i < ee.length; ++i) { + if (elist[ee[i][1]][e] != null) + ch[ee[i][0]] = true, gh[ee[i][1]] = true; + } + var ca = [], ga = []; + for (var c in ch) ca.push(parseInt(c)); + for (var g in gh) ga.push(parseInt(g)); + var named_ca = []; + for (var i = 0; i < ca.length; ++i) named_ca.push(clist[ca[i]]); + warn("Processing exon "+(e+1)+" (" +ga.length+ " genes; " +ca.length+ " contigs: [" +named_ca.join(",")+ "])..."); + // convert representation again + var sc = []; + for (var i = 0; i < ee.length; ++i) { + var c = ee[i][0], g = ee[i][1]; + if (sc[g] == null) sc[g] = []; + if (sc[g][c] == null) sc[g][c] = 0xffff; + sc[g][c] = sc[g][c] < ee[i][2]? sc[g][c] : ee[i][2]; + } + // set unmapped entries to high mismatch + for (var i = 0; i < ga.length; ++i) + for (var j = 0; j < ca.length; ++j) { + var g = ga[i], c = ca[j]; + if (sc[g][c] == null) sc[g][c] = 0xff; + } + // drop mismapped contigs + var dropped = []; + for (var c = 0; c < ca.length; ++c) { + var min = 0x7fffffff, cc = ca[c]; + for (var g = 0; g < ga.length; ++g) { + var gg = ga[g]; + min = min < sc[gg][cc]? min : sc[gg][cc]; + } + dropped[cc] = min > drop_thres? true : false; + if (dropped[cc]) warn("Dropped contig " +clist[cc]+ " due to high divergence to all genes (minNM=" +min+ ")"); + } + // fill the pair array + var min_nm = 0xffff; + for (var i = 0; i < ga.length; ++i) { + var gi = ga[i], g1 = sc[gi]; + for (var j = i; j < ga.length; ++j) { + var gj = ga[j], g2 = sc[gj], m = 0; + for (var k = 0; k < ca.length; ++k) { + c = ca[k]; + if (!dropped[c]) + m += g1[c] < g2[c]? g1[c] : g2[c]; + } + var x = m<<20 | 1<<6 | pri_exon[e]; + if (gi < gj) pair[gj][gi] += x; + else pair[gi][gj] += x; + min_nm = min_nm < m? min_nm : m; + } + } +} + +// genotyping +var min_nm = 0x7fffffff; +for (var i = 0; i < glist.length; ++i) + for (var j = 0; j <= i; ++j) + if ((pair[i][j]&63) == n_pri_exons) + min_nm = min_nm < pair[i][j]>>20? min_nm : pair[i][j]>>20; + +var out = []; +for (var i = 0; i < glist.length; ++i) + for (var j = 0; j <= i; ++j) + if ((pair[i][j]&63) == n_pri_exons && pair[i][j]>>20 <= min_nm + fuzzy) + out.push([pair[i][j]>>20, pair[i][j]>>6&63, i, j]); + +out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : b[1]-a[1]}); +for (var i = 0; i < out.length; ++i) + print(glist[out[i][2]], glist[out[i][3]], out[i][0], out[i][1]); From 4bc01e90c87f77eb835096501bfe2f5102fb4305 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 3 Nov 2014 11:51:25 -0500 Subject: [PATCH 03/38] not used any more --- bwa-helper.js | 373 -------------------------------------------------- 1 file changed, 373 deletions(-) delete mode 100644 bwa-helper.js diff --git a/bwa-helper.js b/bwa-helper.js deleted file mode 100644 index d8477fc..0000000 --- a/bwa-helper.js +++ /dev/null @@ -1,373 +0,0 @@ -/***************************************************************** - * The K8 Javascript interpreter is required to run this script. * - * * - * Source code: https://github.com/attractivechaos/k8 * - * Binary: http://sourceforge.net/projects/lh3/files/k8/ * - * * - * Data file used for generating GRCh38 ALT alignments: * - * * - * http://sourceforge.net/projects/bio-bwa/files/ * - *****************************************************************/ - -/****************** - *** From k8.js *** - ******************/ - -var getopt = function(args, ostr) { - var oli; // option letter list index - if (typeof(getopt.place) == 'undefined') - getopt.ind = 0, getopt.arg = null, getopt.place = -1; - if (getopt.place == -1) { // update scanning pointer - if (getopt.ind >= args.length || args[getopt.ind].charAt(getopt.place = 0) != '-') { - getopt.place = -1; - return null; - } - if (getopt.place + 1 < args[getopt.ind].length && args[getopt.ind].charAt(++getopt.place) == '-') { // found "--" - ++getopt.ind; - getopt.place = -1; - return null; - } - } - var optopt = args[getopt.ind].charAt(getopt.place++); // character checked for validity - if (optopt == ':' || (oli = ostr.indexOf(optopt)) < 0) { - if (optopt == '-') return null; // if the user didn't specify '-' as an option, assume it means null. - if (getopt.place < 0) ++getopt.ind; - return '?'; - } - if (oli+1 >= ostr.length || ostr.charAt(++oli) != ':') { // don't need argument - getopt.arg = null; - if (getopt.place < 0 || getopt.place >= args[getopt.ind].length) ++getopt.ind, getopt.place = -1; - } else { // need an argument - if (getopt.place >= 0 && getopt.place < args[getopt.ind].length) - getopt.arg = args[getopt.ind].substr(getopt.place); - else if (args.length <= ++getopt.ind) { // no arg - getopt.place = -1; - if (ostr.length > 0 && ostr.charAt(0) == ':') return ':'; - return '?'; - } else getopt.arg = args[getopt.ind]; // white space - getopt.place = -1; - ++getopt.ind; - } - return optopt; -} - -/************************ - *** command markovlp *** - ************************/ - -function bwa_markOvlp(args) -{ - var c, min_aln_ratio = .9, min_ext = 50; - while ((c = getopt(args, "r:e:")) != null) { - if (c == 'r') min_aln_ratio = parseFloat(getopt.arg); - else if (c == 'e') min_ext = parseInt(getopt.arg); - } - - var file = args.length == getopt.ind? new File() : new File(args[getopt.ind]); - var buf = new Bytes(); - var dir4 = ['>>', '><', '<>', '<<']; - - while (file.readline(buf) >= 0) { - var t = buf.toString().split("\t") - for (var i = 0; i < t.length; ++i) - if (i != 0 && i != 4) - t[i] = parseInt(t[i]); - var el, a1, a2, e1, e2, r1, r2; // a: aligned length; e: extended length; r: remaining length - e2 = a2 = t[7] - t[6]; - if (t[2] < t[3]) { // forward-forward match - e1 = a1 = t[3] - t[2]; - r1 = t[2] - t[6]; - r2 = (t[5] - t[7]) - (t[1] - t[3]); - el = r1 > 0? t[6] : t[2]; - el += r2 > 0? t[1] - t[3] : t[5] - t[7]; - } else { // reverse-forward match - e1 = a1 = t[2] - t[3]; - r1 = (t[1] - t[2]) - t[6]; - r2 = (t[5] - t[7]) - t[3]; - el = r1 > 0? t[6] : t[1] - t[2]; - el += r2 > 0? t[3] : t[5] - t[7]; - } - e1 += el; e2 += el; - var type; - if (a1 / e1 >= min_aln_ratio && a2 / e2 >= min_aln_ratio) { - if ((r1 >= min_ext && r2 >= min_ext) || (r1 <= -min_ext && r2 <= -min_ext)) { // suffix-prefix match - var d = t[2] < t[3]? 0 : 2; - if (r1 < 0) d ^= 3; // reverse the direction - type = 'O' + dir4[d]; - } else type = 'C' + (e1 / t[1] > e2 / t[5]? 1 : 2); - } else type = 'I'; // internal local match; not a suffix-prefix match - //print(t[1], e1, a1, t[5], e2, a2); - print(type, buf); - } - - buf.destroy(); - file.close(); -} - -/*********************** - *** command pas2bed *** - ***********************/ - -function bwa_pas2reg(args) -{ - var file = args.length? new File(args[0]) : new File(); - var buf = new Bytes(); - - while (file.readline(buf) >= 0) { - var t = buf.toString().split("\t"); - if (t[0] == t[4]) continue; - if (parseInt(t[2]) < parseInt(t[3])) print(t[0], t[1], t[2], t[3], t[8]); - else print(t[0], t[1], t[3], t[2], t[8]); - print(t[4], t[5], t[6], t[7], t[8]); - } - - buf.destroy(); - file.close(); -} - -/******************* - * command sam2pas * - *******************/ - -function bwa_sam2pas(args) -{ - var file = args.length == 0? new File() : new File(args[0]); - var buf = new Bytes(); - var seq_dict = {}; - - while (file.readline(buf) >= 0) { - var line = buf.toString(); - var m; - if (/^@SQ/.test(line)) { - var name = null, len = null; - if ((m = /\tSN:(\S+)/.exec(line)) != null) name = m[1]; - if ((m = /\tLN:(\S+)/.exec(line)) != null) len = parseInt(m[1]); - if (name != null && len != null) seq_dict[name] = len; - } - if (/^@/.test(line)) continue; - var t = line.split("\t"); - var pos = parseInt(t[3]) - 1; - var x = 0, y = 0, i = 0, clip = [0, 0], n_ins = 0, n_del = 0, o_ins = 0, o_del = 0, n_M = 0; - var re = /(\d+)([MIDSH])/g; - while ((m = re.exec(t[5])) != null) { - var l = parseInt(m[1]); - if (m[2] == 'M') x += l, y += l, n_M += l; - else if (m[2] == 'I') y += l, n_ins += l, ++o_ins; - else if (m[2] == 'D') x += l, n_del += l, ++o_del; - else if (m[2] == 'S' || m[2] == 'H') - clip[i == 0? 0 : 1] = l; - ++i; - } - var is_rev = (parseInt(t[1]) & 16)? true : false; - var misc = 'mapQ=' + t[4] + ';'; - var usc = 1; - if ((m = /\tNM:i:(\d+)/.exec(line)) != null) { - var NM = parseInt(m[1]); - var diff = (NM / (n_M + n_ins + n_del)).toFixed(3); - misc += 'diff=' + diff + ';n_mis=' + (NM - n_del - n_ins) + ';'; - } - misc += 'n_del='+n_del+';n_ins='+n_ins+';o_del='+o_del+';o_ins='+o_ins + ';'; - if ((m = /\tAS:i:(\d+)/.exec(line)) != null) { - misc += 'AS='+m[1] + ';'; - usc = (parseInt(m[1]) / (x > y? x : y)).toFixed(3); - } - if ((m = /\tXS:i:(\d+)/.exec(line)) != null) misc += 'XS='+m[1] + ';'; - var len = y + clip[0] + clip[1]; - var z = [t[0], len, clip[0], clip[0] + y, t[2], seq_dict[t[2]], pos, pos + x, usc, misc]; - if (parseInt(t[1]) & 16) z[2] = clip[1] + y, z[3] = clip[1]; - print(z.join("\t")); - } - - buf.destroy(); - file.close(); -} - -/*********************** - *** command reg2cut *** - ***********************/ - -function bwa_reg2cut(args) -{ - var c, min_usc = 0.5, min_ext = 100, min_len = 5000, cut = 250; - while ((c = getopt(args, "s:e:l:c:")) != null) { - if (c == 's') min_usc = parseFloat(getopt.arg); - else if (c == 'e') min_ext = parseInt(getopt.arg); - else if (c == 'l') min_len = parseInt(getopt.arg); - else if (c == 'c') cut = parseInt(getopt.arg); - } - - var file = args.length == getopt.ind? new File () : new File(args[getopt.ind]); - var buf = new Bytes(); - - function print_bed() { - for (var i = 0; i < a.length; ++i) { - var start = a[i][0] - cut > 0? a[i][0] : 0; - var end = a[i][1] + cut < last_len? a[i][1] : last_len; - if (end - start >= min_len) print(last_chr, start, end); - } - } - - var last_chr = null, last_len = null, max_c_usc = 0, start = 0, end = 0; - var a = []; - while (file.readline(buf) >= 0) { - var t = buf.toString().split("\t"); - t[1] = parseInt(t[1]); - t[2] = parseInt(t[2]); - t[3] = parseInt(t[3]); - t[4] = parseFloat(t[4]); - var is_contained = t[2] < min_ext && t[1] - t[3] < min_ext? true : false; - if (t[3] - t[2] < cut<<1) continue; - t[2] += cut; t[3] -= cut; - if (t[0] != last_chr) { - a.push([start, end]); - if (last_chr != null && max_c_usc < min_usc) print_bed(); - last_chr = t[0]; last_len = t[1]; start = t[2]; end = t[3]; - max_c_usc = is_contained? t[4] : 0; - a.length = 0; - } else { - if (is_contained) - max_c_usc = max_c_usc > t[4]? max_c_usc : t[4]; - if (t[4] < min_usc) continue; - if (t[2] > end) { - a.push([start, end]); - start = t[2]; - end = end > t[3]? end : t[3]; - } else end = end > t[3]? end : t[3]; - } - } - a.push([start, end]); - if (max_c_usc < min_usc) print_bed(); // the last sequence - - buf.destroy(); - file.close(); -} - -function bwa_shortname(args) -{ - var file = args.length? new File(args[0]) : new File(); - var buf = new Bytes(); - - var re = /(\S+)\/(\d+)_(\d+)((:\d+-\d+)+)/g; - var re2 = /:(\d+)-(\d+)/g; - while (file.readline(buf) >= 0) { - var match, match2; - var line = buf.toString(); - var x = []; - while ((match = re.exec(line)) != null) { - var start = parseInt(match[2]), len = parseInt(match[3]) - start; - while ((match2 = re2.exec(match[4])) != null) { - var a = parseInt(match2[1]) - 1; - var b = parseInt(match2[2]); - start += a; len = b - a; - } - x.push([match[0], match[1] + '/' + start.toString() + '_' + (start+len).toString()]); - } - for (var i = 0; i < x.length; ++i) - line = line.replace(x[i][0], x[i][1]); - print(line); - } - - buf.destroy(); - file.close(); -} - -/******************* - * Command gff2sam * - *******************/ - -function bwa_gff2sam(args) -{ - if (args.length < 2) { - print("Usage: k8 bwa-helper.js "); - exit(1); - } - - var file = new File(args[1]); - var buf = new Bytes(); - var len = {}; - - while (file.readline(buf) >= 0) { - var t = buf.toString().split(/\s+/); - len[t[0]] = parseInt(t[1]); - } - file.close(); - - file = new File(args[0]); - var re_cigar = /([MID])(\d+)/g; - var lineno = 0; - while (file.readline(buf) >= 0) { - ++lineno; - var t = buf.toString().split("\t"); - var m = /Target=(\S+)\s+(\d+)\s+(\d+)\s+([+-])/.exec(t[8]); - if (m == null) { - warn("WARNING: skipped line "+lineno+" due to the lack of Target."); - continue; - } - var qname = m[1]; - var flag = t[6] == m[4]? 0 : 16; - var qb = parseInt(m[2]) - 1, qe = parseInt(m[3]), qlen = len[qname]; - if (qlen == null) - throw Error("Sequence "+qname+" is not present in the query-length.txt"); - var clip5 = qb, clip3 = qlen - qe; - if (flag&16) clip5 ^= clip3, clip3 ^= clip5, clip5 ^= clip3; // swap - - m = /Gap\s*=\s*(([MID]\d+\s*)+)/.exec(t[8]); - var cigar = clip5? clip5 + 'S' : ''; - var n_ins = 0, n_del = 0, n_match = 0, NM = null; - if (m) { - var mc; - while ((mc = re_cigar.exec(m[1])) != null) { - var l = parseInt(mc[2]); - cigar += mc[2] + mc[1]; - if (mc[1] == 'I') n_ins += l; - else if (mc[1] == 'D') n_del += l; - else if (mc[1] == 'M') n_match += l; - } - if (n_ins + n_match != qe - qb || n_del + n_match != parseInt(t[4]) - parseInt(t[3]) + 1) - throw Error("Inconsistent CIGAR at line "+lineno); - } else { // ungapped alignment - var tb = parseInt(t[3]) - 1, te = parseInt(t[4]); - if (te - tb != qe - qb) { - warn("WARNING: line "+lineno+" should contain gaps, but lacks Gap. Skipped.\n"); - } else cigar = (qe - qb) + 'M'; - } - if (clip3) cigar += clip3 + 'S'; - if ((m = /num_mismatch=(\d+)/.exec(t[8])) != null) - NM = parseInt(m[1]) + n_ins + n_del; - var out = [qname, flag, t[0], t[3], 255, cigar, '*', 0, 0, '*', '*']; - if (NM != null) out.push('NM:i:' + NM); - print(out.join("\t")); - } - buf.destroy(); - file.close(); -} - -/********************* - *** Main function *** - *********************/ - -function main(args) -{ - if (args.length == 0) { - print("\nUsage: k8 bwa-helper.js [arguments]\n"); - print("Commands: sam2pas convert SAM to pairwise alignment summary format (PAS)"); - print(" pas2reg extract covered regions"); - print(" reg2cut regions to extract for the 2nd round bwa-mem"); - print(" markovlp identify bi-directional overlaps"); - print(" gff2sam convert GFF3 alignment to SAM"); - print(" shortname shorten sequence name after subseq (PacBio read names only)"); - print(""); - exit(1); - } - - var cmd = args.shift(); - if (cmd == 'sam2pas') bwa_sam2pas(args); - else if (cmd == 'gff2sam') bwa_gff2sam(args); - else if (cmd == 'markovlp') bwa_markOvlp(args); - else if (cmd == 'pas2reg') bwa_pas2reg(args); - else if (cmd == 'reg2cut') bwa_reg2cut(args); - else if (cmd == 'shortname') bwa_shortname(args); - else warn("Unrecognized command"); -} - -main(arguments); From 40c4a6ffe1d8142f7a09f2764f3add22ed0b5208 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 3 Nov 2014 12:09:29 -0500 Subject: [PATCH 04/38] better CLI; more comments --- bwa-typeHLA.js | 86 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 78 insertions(+), 8 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index da9ca28..5561952 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -1,6 +1,72 @@ -var fuzzy = 3, min_qal = 100, drop_thres = 7; +/***************************************************************** + * The K8 Javascript interpreter is required to run this script. * + * * + * Source code: https://github.com/attractivechaos/k8 * + * Binary: http://sourceforge.net/projects/lh3/files/k8/ * + *****************************************************************/ -var file = arguments.length == 0? new File () : new File(arguments[0]); +var getopt = function(args, ostr) { + var oli; // option letter list index + if (typeof(getopt.place) == 'undefined') + getopt.ind = 0, getopt.arg = null, getopt.place = -1; + if (getopt.place == -1) { // update scanning pointer + if (getopt.ind >= args.length || args[getopt.ind].charAt(getopt.place = 0) != '-') { + getopt.place = -1; + return null; + } + if (getopt.place + 1 < args[getopt.ind].length && args[getopt.ind].charAt(++getopt.place) == '-') { // found "--" + ++getopt.ind; + getopt.place = -1; + return null; + } + } + var optopt = args[getopt.ind].charAt(getopt.place++); // character checked for validity + if (optopt == ':' || (oli = ostr.indexOf(optopt)) < 0) { + if (optopt == '-') return null; // if the user didn't specify '-' as an option, assume it means null. + if (getopt.place < 0) ++getopt.ind; + return '?'; + } + if (oli+1 >= ostr.length || ostr.charAt(++oli) != ':') { // don't need argument + getopt.arg = null; + if (getopt.place < 0 || getopt.place >= args[getopt.ind].length) ++getopt.ind, getopt.place = -1; + } else { // need an argument + if (getopt.place >= 0 && getopt.place < args[getopt.ind].length) + getopt.arg = args[getopt.ind].substr(getopt.place); + else if (args.length <= ++getopt.ind) { // no arg + getopt.place = -1; + if (ostr.length > 0 && ostr.charAt(0) == ':') return ':'; + return '?'; + } else getopt.arg = args[getopt.ind]; // white space + getopt.place = -1; + ++getopt.ind; + } + return optopt; +} + +/********************* + *** Main function *** + *********************/ + +var c, fuzzy = 1, min_qal = 100, drop_thres = 7; + +// parse command line options +while ((c = getopt(arguments, "f:m:d:")) != null) { + if (c == 'f') fuzzy = parseInt(getopt.arg); + else if (c == 'm') min_qal = parseInt(getopt.arg); + else if (c == 'd') drop_thres = parseInt(getopt.arg); +} +if (arguments.length == getopt.ind) { + print(""); + print("Usage: k8 bwa-typeHLA.js [options] \n"); + print("Options: -d INT drop a contig if the edit distance to the closest gene is >INT ["+drop_thres+"]"); + print(" -m INT ignore a hit if the gene sequence in the alignment is = 0) { var m, mm, line = buf.toString(); var t = line.split("\t"); + // SAM header if (t[0].charAt(0) == '@') { if (t[0] == '@SQ' && (m = /LN:(\d+)/.exec(line)) != null && (mm = /SN:(\S+)/.exec(line)) != null) len[mm[1]] = parseInt(m[1]); continue; } + // parse gene name and exon number var gene = null, exon = null; if ((m = /^(HLA-[^\s_]+)_(\d+)/.exec(t[0])) != null) { gene = m[1], exon = parseInt(m[2]) - 1; @@ -20,6 +88,7 @@ while (file.readline(buf) >= 0) { gcnt[exon][gene] = true; } if (gene == null || exon == null || t[2] == '*') continue; + // parse clipping and aligned length var x = 0, ts = parseInt(t[3]) - 1, te = ts, clip = [0, 0]; while ((m = re_cigar.exec(t[5])) != null) { var l = parseInt(m[1]); @@ -33,16 +102,17 @@ while (file.readline(buf) >= 0) { var left = ts < clip[0]? ts : clip[0]; var right = tl - te < clip[1]? tl - te : clip[1]; var nm = (m = /\tNM:i:(\d+)/.exec(line)) != null? parseInt(m[1]) : 0; - list.push([t[2], gene, exon, ts, te, nm, left + right]); + list.push([t[2], gene, exon, ts, te, nm, left + right]); // left+right should be 0 given a prefix-suffix alignment } buf.destroy(); file.close(); -// identify the primary exon(s) +// identify the primary exons, the exons associated with most genes var pri_exon = [], n_pri_exons; { var cnt = [], max = 0; + // count the number of genes per exon and track the max for (var e = 0; e < gcnt.length; ++e) { if (gcnt[e] != null) { var c = 0, h = gcnt[e]; @@ -51,6 +121,7 @@ var pri_exon = [], n_pri_exons; max = max > c? max : c; } else cnt[e] = 0; } + // find primary exons var pri_list = []; for (var e = 0; e < cnt.length; ++e) { if (cnt[e] == max) pri_list.push(e + 1); @@ -76,7 +147,7 @@ for (var i = 0; i < list.length; ++i) { elist[g][list[i][2]] = true; } -// change to the per-exon representation +// reorganize hits to exons var exons = []; for (var i = 0; i < list.length; ++i) { var li = list[i]; @@ -94,10 +165,9 @@ for (var i = 0; i < glist.length; ++i) { // type each exon for (var e = 0; e < exons.length; ++e) { -//for (var e = 1; e < 3; ++e) { if (exons[e] == null) continue; var ee = exons[e]; - // find good contigs and alleles that have good matches + // find contigs and genes associated with the current exon var ch = {}, gh = {}; for (var i = 0; i < ee.length; ++i) { if (elist[ee[i][1]][e] != null) @@ -108,7 +178,7 @@ for (var e = 0; e < exons.length; ++e) { for (var g in gh) ga.push(parseInt(g)); var named_ca = []; for (var i = 0; i < ca.length; ++i) named_ca.push(clist[ca[i]]); - warn("Processing exon "+(e+1)+" (" +ga.length+ " genes; " +ca.length+ " contigs: [" +named_ca.join(",")+ "])..."); + warn("Processing exon "+(e+1)+" (" +ga.length+ " genes; " +ca.length+ " contigs: [" +named_ca.join(", ")+ "])..."); // convert representation again var sc = []; for (var i = 0; i < ee.length; ++i) { From 9dbf152e31f1e15b20faa2ac4b206664e8736c40 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 3 Nov 2014 15:16:14 -0500 Subject: [PATCH 05/38] added the pipeline script --- bwa-typeHLA.js | 16 ++++++++-------- bwa-typeHLA.sh | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) create mode 100755 bwa-typeHLA.sh diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index 5561952..ad47387 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -195,14 +195,14 @@ for (var e = 0; e < exons.length; ++e) { } // drop mismapped contigs var dropped = []; - for (var c = 0; c < ca.length; ++c) { - var min = 0x7fffffff, cc = ca[c]; - for (var g = 0; g < ga.length; ++g) { - var gg = ga[g]; - min = min < sc[gg][cc]? min : sc[gg][cc]; + for (var k = 0; k < ca.length; ++k) { + var min = 0x7fffffff, c = ca[k]; + for (var i = 0; i < ga.length; ++i) { + var g = ga[i]; + min = min < sc[g][c]? min : sc[g][c]; } - dropped[cc] = min > drop_thres? true : false; - if (dropped[cc]) warn("Dropped contig " +clist[cc]+ " due to high divergence to all genes (minNM=" +min+ ")"); + dropped[c] = min > drop_thres? true : false; + if (dropped[c]) warn("Dropped contig " +clist[c]+ " due to high divergence to all genes (minNM=" +min+ ")"); } // fill the pair array var min_nm = 0xffff; @@ -211,7 +211,7 @@ for (var e = 0; e < exons.length; ++e) { for (var j = i; j < ga.length; ++j) { var gj = ga[j], g2 = sc[gj], m = 0; for (var k = 0; k < ca.length; ++k) { - c = ca[k]; + var c = ca[k]; if (!dropped[c]) m += g1[c] < g2[c]? g1[c] : g2[c]; } diff --git a/bwa-typeHLA.sh b/bwa-typeHLA.sh new file mode 100755 index 0000000..cc4bd12 --- /dev/null +++ b/bwa-typeHLA.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +if [ $# -lt 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +root=`dirname $0` +pre=$1.$2 +len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'` + +$root/fermi2.pl unitig -t2 -l$len -p $pre.utg $pre.fq > $pre.utg.mak +make -f $pre.utg.mak +$root/bwa mem -B1 -O1 -E1 $root/HLA-ALT.fa $pre.utg.mag.gz | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.utg.alt.gz +$root/tabix -Bpsam $pre.utg.alt.gz $root/HLA-approx.anno | cut -f1 | sort | uniq | $root/seqtk subseq $pre.utg.mag.gz - | gzip -1 > $pre.utg.fq.gz +rm $pre.utg.{flt,ec,raw,pre}.* $pre.utg.mag* $pre.utg.mak $pre.utg.alt.gz +$root/bwa index -p $pre.utg $pre.utg.fq.gz +$root/bwa mem -aD.1 -t2 $pre.utg $root/hla.fa | egrep "^(@|$2)" > $pre.utg.sam +rm $pre.utg.{ann,amb,bwt,sa,pac} From 4cbb0cb829bed416b2d23b629173c311fb6d7d8d Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 3 Nov 2014 15:29:02 -0500 Subject: [PATCH 06/38] polished the run script --- bwa-typeHLA.sh | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/bwa-typeHLA.sh b/bwa-typeHLA.sh index cc4bd12..2e5c123 100755 --- a/bwa-typeHLA.sh +++ b/bwa-typeHLA.sh @@ -9,11 +9,20 @@ root=`dirname $0` pre=$1.$2 len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'` -$root/fermi2.pl unitig -t2 -l$len -p $pre.utg $pre.fq > $pre.utg.mak -make -f $pre.utg.mak -$root/bwa mem -B1 -O1 -E1 $root/HLA-ALT.fa $pre.utg.mag.gz | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.utg.alt.gz -$root/tabix -Bpsam $pre.utg.alt.gz $root/HLA-approx.anno | cut -f1 | sort | uniq | $root/seqtk subseq $pre.utg.mag.gz - | gzip -1 > $pre.utg.fq.gz -rm $pre.utg.{flt,ec,raw,pre}.* $pre.utg.mag* $pre.utg.mak $pre.utg.alt.gz -$root/bwa index -p $pre.utg $pre.utg.fq.gz -$root/bwa mem -aD.1 -t2 $pre.utg $root/hla.fa | egrep "^(@|$2)" > $pre.utg.sam -rm $pre.utg.{ann,amb,bwt,sa,pac} +# de novo assembly +$root/fermi2.pl unitig -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak +make -f $pre.tmp.mak + +# get contigs overlapping HLA exons +$root/bwa mem -B1 -O1 -E1 $root/HLA-ALT.fa $pre.tmp.mag.gz | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.tmp.alt.gz +$root/tabix -Bpsam $pre.tmp.alt.gz $root/HLA-approx.anno | cut -f1 | sort | uniq | $root/seqtk subseq $pre.tmp.mag.gz - | gzip -1 > $pre.tmp.fq.gz + +# map HLA exons to de novo contigs +$root/bwa index -p $pre.tmp $pre.tmp.fq.gz +$root/bwa mem -aD.1 -t2 $pre.tmp $root/hla.fa | egrep "^(@|$2)" > $pre.tmp.sam + +# type +$root/k8 $root/bwa-typeHLA.js $pre.tmp.sam > $pre.gt + +# delete temporary files +rm -f $pre.tmp.* From ace18171b0a8f47791c48bbdfb4c49772fe8760f Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 4 Nov 2014 10:07:14 -0500 Subject: [PATCH 07/38] backup --- bwa-typeHLA.js | 21 +++++++++++++++++---- bwa-typeHLA.sh | 27 +++++++++++++++++++-------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index ad47387..7a59d60 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -47,7 +47,7 @@ var getopt = function(args, ostr) { *** Main function *** *********************/ -var c, fuzzy = 1, min_qal = 100, drop_thres = 7; +var c, fuzzy = 1, min_qal = 100, drop_thres = 5; // parse command line options while ((c = getopt(arguments, "f:m:d:")) != null) { @@ -212,8 +212,11 @@ for (var e = 0; e < exons.length; ++e) { var gj = ga[j], g2 = sc[gj], m = 0; for (var k = 0; k < ca.length; ++k) { var c = ca[k]; - if (!dropped[c]) + if (!dropped[c]) { m += g1[c] < g2[c]? g1[c] : g2[c]; + if ((gi == 518 && gj == 653) || (gi == 653 && gj == 518)) + print(e+1, clist[c], g1[c], g2[c]); + } } var x = m<<20 | 1<<6 | pri_exon[e]; if (gi < gj) pair[gj][gi] += x; @@ -223,6 +226,16 @@ for (var e = 0; e < exons.length; ++e) { } } +// extract the 3rd and 4th digits +var gsub = [], gsuf = []; +for (var i = 0; i < glist.length; ++i) { + var m = /^HLA-[^*\s]+\*\d+:(\d+).*([A-Z]?)$/.exec(glist[i]); + gsub[i] = parseInt(m[1]); + gsuf[i] = /[A-Z]$/.test(glist[i])? 1 : 0; +} + +warn(ghash["HLA-DQB1*06:04:02"], ghash["HLA-DQB1*06:44"], pair[518][518].toString(16), pair[653][518].toString(16)); + // genotyping var min_nm = 0x7fffffff; for (var i = 0; i < glist.length; ++i) @@ -234,8 +247,8 @@ var out = []; for (var i = 0; i < glist.length; ++i) for (var j = 0; j <= i; ++j) if ((pair[i][j]&63) == n_pri_exons && pair[i][j]>>20 <= min_nm + fuzzy) - out.push([pair[i][j]>>20, pair[i][j]>>6&63, i, j]); + out.push([pair[i][j]>>20, pair[i][j]>>6&63, i, j, (gsuf[i] + gsuf[j])<<16|(gsub[i] + gsub[j])]); -out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : b[1]-a[1]}); +out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : a[1]!=b[1]? b[1]-a[1] : a[4]!=b[4]? a[4]-b[4] : a[2]!=b[2]? a[2]-b[2] : a[3]-b[3]}); for (var i = 0; i < out.length; ++i) print(glist[out[i][2]], glist[out[i][3]], out[i][0], out[i][1]); diff --git a/bwa-typeHLA.sh b/bwa-typeHLA.sh index 2e5c123..e112209 100755 --- a/bwa-typeHLA.sh +++ b/bwa-typeHLA.sh @@ -1,28 +1,39 @@ #!/bin/bash +is_ctg=0 + +if [ $# -gt 1 ] && [ $1 == '-A' ]; then + is_ctg=1 + shift +fi + if [ $# -lt 2 ]; then - echo "Usage: $0 " + echo "Usage: $0 [-A] " exit 1 fi root=`dirname $0` pre=$1.$2 -len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'` # de novo assembly -$root/fermi2.pl unitig -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak -make -f $pre.tmp.mak +if [ $is_ctg -eq 0 ]; then + len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'` + $root/fermi2.pl unitig -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak + make -f $pre.tmp.mak EXE_FERMI2=$root/fermi2 EXE_ROPEBWT2=$root/ropebwt2 +else + ln -s $pre.fq $pre.tmp.mag.gz +fi # get contigs overlapping HLA exons $root/bwa mem -B1 -O1 -E1 $root/HLA-ALT.fa $pre.tmp.mag.gz | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.tmp.alt.gz $root/tabix -Bpsam $pre.tmp.alt.gz $root/HLA-approx.anno | cut -f1 | sort | uniq | $root/seqtk subseq $pre.tmp.mag.gz - | gzip -1 > $pre.tmp.fq.gz # map HLA exons to de novo contigs -$root/bwa index -p $pre.tmp $pre.tmp.fq.gz -$root/bwa mem -aD.1 -t2 $pre.tmp $root/hla.fa | egrep "^(@|$2)" > $pre.tmp.sam +$root/bwa index -p $pre.tmp $pre.tmp.fq.gz 2> /dev/null +$root/bwa mem -aD.1 -t2 $pre.tmp $root/hla.fa | egrep "^(@|$2)" > $pre.sam -# type -$root/k8 $root/bwa-typeHLA.js $pre.tmp.sam > $pre.gt +# type HLA +$root/k8 $root/bwa-typeHLA.js $pre.sam > $pre.gt # delete temporary files rm -f $pre.tmp.* From 96fba0f2f4349e6b011590989227969686bc9b9e Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 4 Nov 2014 11:38:05 -0500 Subject: [PATCH 08/38] proper homozygous typing; fixed a low-level bug --- bwa-typeHLA.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index 7a59d60..f1b4f8a 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -97,7 +97,7 @@ while (file.readline(buf) >= 0) { else if (m[2] == 'D') te += l; else if (m[2] == 'S' || m[2] == 'H') clip[x==0?0:1] = l; } - if (x < min_qal && clip[0] + clip[1] > 0) continue; + //if (x < min_qal && clip[0] + clip[1] > 0) continue; var tl = len[t[2]]; var left = ts < clip[0]? ts : clip[0]; var right = tl - te < clip[1]? tl - te : clip[1]; @@ -134,15 +134,15 @@ var pri_exon = [], n_pri_exons; // convert strings to integers (for performance) var ghash = {}, glist = [], chash = {}, clist = [], elist = []; for (var i = 0; i < list.length; ++i) { - var g = glist.length, c = clist.length; if (ghash[list[i][1]] == null) { + ghash[list[i][1]] = glist.length; glist.push(list[i][1]); - ghash[list[i][1]] = g; } if (chash[list[i][0]] == null) { + chash[list[i][0]] = clist.length; clist.push(list[i][0]); - chash[list[i][0]] = c; } + var g = ghash[list[i][1]]; if (elist[g] == null) elist[g] = {}; elist[g][list[i][2]] = true; } @@ -207,17 +207,24 @@ for (var e = 0; e < exons.length; ++e) { // fill the pair array var min_nm = 0xffff; for (var i = 0; i < ga.length; ++i) { - var gi = ga[i], g1 = sc[gi]; - for (var j = i; j < ga.length; ++j) { - var gj = ga[j], g2 = sc[gj], m = 0; + var m = 0, gi = ga[i], g1 = sc[gi]; + // homozygous + for (var k = 0; k < ca.length; ++k) { + var c = ca[k]; + if (!dropped[c]) m += g1[c]; + } + pair[gi][gi] += m<<20 | 1<<6 | pri_exon[e]; + // heterozygous + for (var j = i + 1; j < ga.length; ++j) { + var gj = ga[j], g2 = sc[gj], m = 0, a = [0, 0]; for (var k = 0; k < ca.length; ++k) { var c = ca[k]; if (!dropped[c]) { m += g1[c] < g2[c]? g1[c] : g2[c]; - if ((gi == 518 && gj == 653) || (gi == 653 && gj == 518)) - print(e+1, clist[c], g1[c], g2[c]); + ++a[g1[c] Date: Tue, 4 Nov 2014 14:30:31 -0500 Subject: [PATCH 09/38] a bit code cleanup --- bwa-typeHLA.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index f1b4f8a..2a9f31d 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -97,7 +97,6 @@ while (file.readline(buf) >= 0) { else if (m[2] == 'D') te += l; else if (m[2] == 'S' || m[2] == 'H') clip[x==0?0:1] = l; } - //if (x < min_qal && clip[0] + clip[1] > 0) continue; var tl = len[t[2]]; var left = ts < clip[0]? ts : clip[0]; var right = tl - te < clip[1]? tl - te : clip[1]; @@ -179,20 +178,19 @@ for (var e = 0; e < exons.length; ++e) { var named_ca = []; for (var i = 0; i < ca.length; ++i) named_ca.push(clist[ca[i]]); warn("Processing exon "+(e+1)+" (" +ga.length+ " genes; " +ca.length+ " contigs: [" +named_ca.join(", ")+ "])..."); - // convert representation again + // set unmapped entries to high mismatch var sc = []; + for (var k = 0; k < ga.length; ++k) { + var g = ga[k]; + if (sc[g] == null) sc[g] = []; + for (var i = 0; i < ca.length; ++i) + sc[g][ca[i]] = 0xff; + } + // convert representation again for (var i = 0; i < ee.length; ++i) { var c = ee[i][0], g = ee[i][1]; - if (sc[g] == null) sc[g] = []; - if (sc[g][c] == null) sc[g][c] = 0xffff; sc[g][c] = sc[g][c] < ee[i][2]? sc[g][c] : ee[i][2]; } - // set unmapped entries to high mismatch - for (var i = 0; i < ga.length; ++i) - for (var j = 0; j < ca.length; ++j) { - var g = ga[i], c = ca[j]; - if (sc[g][c] == null) sc[g][c] = 0xff; - } // drop mismapped contigs var dropped = []; for (var k = 0; k < ca.length; ++k) { From de268247db8ddcd02d4839acdc5c429aed4c54ee Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 4 Nov 2014 15:09:21 -0500 Subject: [PATCH 10/38] type primary exons first and then the rest --- bwa-typeHLA.js | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index 2a9f31d..49dc958 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -47,20 +47,17 @@ var getopt = function(args, ostr) { *** Main function *** *********************/ -var c, fuzzy = 1, min_qal = 100, drop_thres = 5; +var c, min_qal = 100, drop_thres = 5; // parse command line options -while ((c = getopt(arguments, "f:m:d:")) != null) { - if (c == 'f') fuzzy = parseInt(getopt.arg); - else if (c == 'm') min_qal = parseInt(getopt.arg); +while ((c = getopt(arguments, "m:d:")) != null) { + if (c == 'm') min_qal = parseInt(getopt.arg); else if (c == 'd') drop_thres = parseInt(getopt.arg); } if (arguments.length == getopt.ind) { print(""); print("Usage: k8 bwa-typeHLA.js [options] \n"); print("Options: -d INT drop a contig if the edit distance to the closest gene is >INT ["+drop_thres+"]"); - print(" -m INT ignore a hit if the gene sequence in the alignment is >14&0xff) + m < 0xff? (x>>14&0xff) + m : 0xff; + if (is_pri) z = (x>>22) + m < 0xff? (x>>22) + m : 0xff; + else z = x>>22; + return z<<22 | y<<14 | ((x&0x3fff) + (1<<6|is_pri)); +} + // type each exon for (var e = 0; e < exons.length; ++e) { if (exons[e] == null) continue; - var ee = exons[e]; + var ee = exons[e], is_pri = pri_exon[e]? 1 : 0; // find contigs and genes associated with the current exon var ch = {}, gh = {}; for (var i = 0; i < ee.length; ++i) { @@ -203,7 +209,6 @@ for (var e = 0; e < exons.length; ++e) { if (dropped[c]) warn("Dropped contig " +clist[c]+ " due to high divergence to all genes (minNM=" +min+ ")"); } // fill the pair array - var min_nm = 0xffff; for (var i = 0; i < ga.length; ++i) { var m = 0, gi = ga[i], g1 = sc[gi]; // homozygous @@ -211,7 +216,7 @@ for (var e = 0; e < exons.length; ++e) { var c = ca[k]; if (!dropped[c]) m += g1[c]; } - pair[gi][gi] += m<<20 | 1<<6 | pri_exon[e]; + pair[gi][gi] = update_pair(pair[gi][gi], m, is_pri); // heterozygous for (var j = i + 1; j < ga.length; ++j) { var gj = ga[j], g2 = sc[gj], m = 0, a = [0, 0]; @@ -223,10 +228,8 @@ for (var e = 0; e < exons.length; ++e) { } } if (a[0] == 0 || a[1] == 0) m = 0xff; // if all contigs are assigned to one gene, it is not good - var x = m<<20 | 1<<6 | pri_exon[e]; - if (gi < gj) pair[gj][gi] += x; - else pair[gi][gj] += x; - min_nm = min_nm < m? min_nm : m; + if (gi < gj) pair[gj][gi] = update_pair(pair[gj][gi], m, is_pri); + else pair[gi][gj] = update_pair(pair[gi][gj], m, is_pri); } } } @@ -244,14 +247,14 @@ var min_nm = 0x7fffffff; for (var i = 0; i < glist.length; ++i) for (var j = 0; j <= i; ++j) if ((pair[i][j]&63) == n_pri_exons) - min_nm = min_nm < pair[i][j]>>20? min_nm : pair[i][j]>>20; + min_nm = min_nm < pair[i][j]>>14? min_nm : pair[i][j]>>14; var out = []; for (var i = 0; i < glist.length; ++i) for (var j = 0; j <= i; ++j) - if ((pair[i][j]&63) == n_pri_exons && pair[i][j]>>20 <= min_nm + fuzzy) - out.push([pair[i][j]>>20, pair[i][j]>>6&63, i, j, (gsuf[i] + gsuf[j])<<16|(gsub[i] + gsub[j])]); + if ((pair[i][j]&63) == n_pri_exons && pair[i][j]>>14 <= min_nm + 1) + out.push([pair[i][j]>>14, pair[i][j]>>6&0xff, i, j, (gsuf[i] + gsuf[j])<<16|(gsub[i] + gsub[j])]); out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : a[1]!=b[1]? b[1]-a[1] : a[4]!=b[4]? a[4]-b[4] : a[2]!=b[2]? a[2]-b[2] : a[3]-b[3]}); for (var i = 0; i < out.length; ++i) - print(glist[out[i][2]], glist[out[i][3]], out[i][0], out[i][1]); + print(glist[out[i][2]], glist[out[i][3]], out[i][0]>>8&0xff, out[i][0]&0xff, out[i][1]); From ccf03ffd22df208b8af194c60991e5a0a0848c39 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 4 Nov 2014 15:26:10 -0500 Subject: [PATCH 11/38] control min-match-length we still need this to kill false mapping/alignment --- bwa-typeHLA.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index 49dc958..ce10087 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -47,17 +47,18 @@ var getopt = function(args, ostr) { *** Main function *** *********************/ -var c, min_qal = 100, drop_thres = 5; +var c, thres_len = 100, thres_ratio = .8, thres_nm = 5; // parse command line options -while ((c = getopt(arguments, "m:d:")) != null) { - if (c == 'm') min_qal = parseInt(getopt.arg); - else if (c == 'd') drop_thres = parseInt(getopt.arg); +while ((c = getopt(arguments, "l:d:")) != null) { + if (c == 'l') thres_len = parseInt(getopt.arg); + else if (c == 'd') thres_nm = parseInt(getopt.arg); } if (arguments.length == getopt.ind) { print(""); print("Usage: k8 bwa-typeHLA.js [options] \n"); - print("Options: -d INT drop a contig if the edit distance to the closest gene is >INT ["+drop_thres+"]"); + print("Options: -d INT drop a contig if the edit distance to the closest gene is >INT ["+thres_nm+"]"); + print(" -l INT drop a contig if its match too short ["+thres_len+"]"); print(""); exit(1); } @@ -148,7 +149,7 @@ var exons = []; for (var i = 0; i < list.length; ++i) { var li = list[i]; if (exons[li[2]] == null) exons[li[2]] = []; - exons[li[2]].push([chash[li[0]], ghash[li[1]], li[5] + li[6]]); + exons[li[2]].push([chash[li[0]], ghash[li[1]], li[5] + li[6], li[4] - li[3]]); } // initialize genotype scores @@ -193,11 +194,17 @@ for (var e = 0; e < exons.length; ++e) { sc[g][ca[i]] = 0xff; } // convert representation again + var max_len = []; for (var i = 0; i < ee.length; ++i) { var c = ee[i][0], g = ee[i][1]; sc[g][c] = sc[g][c] < ee[i][2]? sc[g][c] : ee[i][2]; + if (max_len[c] == null) max_len[c] = 0; + max_len[c] = max_len[c] > ee[i][3]? max_len[c] : ee[i][3]; } // drop mismapped contigs + var max_max_len = 0; + for (var k = 0; k < ca.length; ++k) + max_max_len = max_max_len > max_len[ca[k]]? max_max_len : max_len[ca[k]]; var dropped = []; for (var k = 0; k < ca.length; ++k) { var min = 0x7fffffff, c = ca[k]; @@ -205,8 +212,9 @@ for (var e = 0; e < exons.length; ++e) { var g = ga[i]; min = min < sc[g][c]? min : sc[g][c]; } - dropped[c] = min > drop_thres? true : false; - if (dropped[c]) warn("Dropped contig " +clist[c]+ " due to high divergence to all genes (minNM=" +min+ ")"); + dropped[c] = min > thres_nm? true : false; + if (max_len[c] < thres_len && max_len[c] < thres_ratio * max_max_len) dropped[c] = true; + if (dropped[c]) warn("Dropped contig " +clist[c]+ " due to high divergence to all genes (minNM=" +min+ "; maxLen=" +max_len[c]+ ")"); } // fill the pair array for (var i = 0; i < ga.length; ++i) { From e5d7f4fc301f85dcf165574cd5704f36c9f9aa4d Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 4 Nov 2014 15:31:33 -0500 Subject: [PATCH 12/38] change the default max_len --- bwa-typeHLA.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index ce10087..fa35199 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -47,7 +47,7 @@ var getopt = function(args, ostr) { *** Main function *** *********************/ -var c, thres_len = 100, thres_ratio = .8, thres_nm = 5; +var c, thres_len = 50, thres_ratio = .8, thres_nm = 5; // parse command line options while ((c = getopt(arguments, "l:d:")) != null) { From 2a41772d0a94e08e2467281cea4cc2242c5c0dc0 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 4 Nov 2014 15:41:18 -0500 Subject: [PATCH 13/38] more command line help --- bwa-typeHLA.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index fa35199..cdcbbd3 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -56,10 +56,13 @@ while ((c = getopt(arguments, "l:d:")) != null) { } if (arguments.length == getopt.ind) { print(""); - print("Usage: k8 bwa-typeHLA.js [options] \n"); + print("Usage: k8 bwa-typeHLA.js [options] \n"); print("Options: -d INT drop a contig if the edit distance to the closest gene is >INT ["+thres_nm+"]"); print(" -l INT drop a contig if its match too short ["+thres_len+"]"); print(""); + print("Note: The output is TAB delimited with each line consisting of allele1, allele2,"); + print(" #mismatches/gaps on primary exons, #mismatches/gaps on other exons and"); + print(" #exons used in typing."); exit(1); } @@ -265,4 +268,4 @@ for (var i = 0; i < glist.length; ++i) out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : a[1]!=b[1]? b[1]-a[1] : a[4]!=b[4]? a[4]-b[4] : a[2]!=b[2]? a[2]-b[2] : a[3]-b[3]}); for (var i = 0; i < out.length; ++i) - print(glist[out[i][2]], glist[out[i][3]], out[i][0]>>8&0xff, out[i][0]&0xff, out[i][1]); + print(glist[out[i][3]], glist[out[i][2]], out[i][0]>>8&0xff, out[i][0]&0xff, out[i][1]); From 0eee4fa1b005d393161b6ef71ebd5736fa3819f3 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 4 Nov 2014 16:00:25 -0500 Subject: [PATCH 14/38] nothing, really --- bwa-typeHLA.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index cdcbbd3..862d7bc 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -62,7 +62,7 @@ if (arguments.length == getopt.ind) { print(""); print("Note: The output is TAB delimited with each line consisting of allele1, allele2,"); print(" #mismatches/gaps on primary exons, #mismatches/gaps on other exons and"); - print(" #exons used in typing."); + print(" #exons used in typing.\n"); exit(1); } From 10de1e0b747194ca6b21eaa056df5ef5fb87b4d7 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Wed, 5 Nov 2014 13:20:33 -0500 Subject: [PATCH 15/38] optionally output debegging info --- bwa-typeHLA.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js index 862d7bc..910073a 100644 --- a/bwa-typeHLA.js +++ b/bwa-typeHLA.js @@ -47,17 +47,18 @@ var getopt = function(args, ostr) { *** Main function *** *********************/ -var c, thres_len = 50, thres_ratio = .8, thres_nm = 5; +var c, thres_len = 50, thres_ratio = .8, thres_nm = 5, dbg = false; // parse command line options -while ((c = getopt(arguments, "l:d:")) != null) { +while ((c = getopt(arguments, "dl:n:")) != null) { if (c == 'l') thres_len = parseInt(getopt.arg); - else if (c == 'd') thres_nm = parseInt(getopt.arg); + else if (c == 'n') thres_nm = parseInt(getopt.arg); + else if (c == 'd') dbg = true; } if (arguments.length == getopt.ind) { print(""); print("Usage: k8 bwa-typeHLA.js [options] \n"); - print("Options: -d INT drop a contig if the edit distance to the closest gene is >INT ["+thres_nm+"]"); + print("Options: -n INT drop a contig if the edit distance to the closest gene is >INT ["+thres_nm+"]"); print(" -l INT drop a contig if its match too short ["+thres_len+"]"); print(""); print("Note: The output is TAB delimited with each line consisting of allele1, allele2,"); @@ -173,7 +174,9 @@ function update_pair(x, m, is_pri) } // type each exon +var score = [], ctg = []; for (var e = 0; e < exons.length; ++e) { + score[e] = []; ctg[e] = []; if (exons[e] == null) continue; var ee = exons[e], is_pri = pri_exon[e]? 1 : 0; // find contigs and genes associated with the current exon @@ -182,14 +185,14 @@ for (var e = 0; e < exons.length; ++e) { if (elist[ee[i][1]][e] != null) ch[ee[i][0]] = true, gh[ee[i][1]] = true; } - var ca = [], ga = []; + var ga = [], ca = ctg[e]; for (var c in ch) ca.push(parseInt(c)); for (var g in gh) ga.push(parseInt(g)); var named_ca = []; for (var i = 0; i < ca.length; ++i) named_ca.push(clist[ca[i]]); warn("Processing exon "+(e+1)+" (" +ga.length+ " genes; " +ca.length+ " contigs: [" +named_ca.join(", ")+ "])..."); // set unmapped entries to high mismatch - var sc = []; + var sc = score[e]; for (var k = 0; k < ga.length; ++k) { var g = ga[k]; if (sc[g] == null) sc[g] = []; @@ -267,5 +270,18 @@ for (var i = 0; i < glist.length; ++i) out.push([pair[i][j]>>14, pair[i][j]>>6&0xff, i, j, (gsuf[i] + gsuf[j])<<16|(gsub[i] + gsub[j])]); out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : a[1]!=b[1]? b[1]-a[1] : a[4]!=b[4]? a[4]-b[4] : a[2]!=b[2]? a[2]-b[2] : a[3]-b[3]}); -for (var i = 0; i < out.length; ++i) - print(glist[out[i][3]], glist[out[i][2]], out[i][0]>>8&0xff, out[i][0]&0xff, out[i][1]); + +for (var i = 0; i < out.length; ++i) { + print("GT", glist[out[i][3]], glist[out[i][2]], out[i][0]>>8&0xff, out[i][0]&0xff, out[i][1]); + if (dbg) { + var a1 = out[i][2], a2 = out[i][3]; + for (var e = 0; e < exons.length; ++e) { + if (exons[e] == null) continue; + if (elist[a1][e] == null || elist[a2][e] == null) continue; + for (var k = 0; k < ctg[e].length; ++k) { + var c = ctg[e][k]; + print("DB", e+1, score[e][a2][c], score[e][a1][c], clist[c]); + } + } + } +} From 1bee29ef4478171e7abcd165672bbaeb6e0829e0 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 6 Nov 2014 00:37:59 -0500 Subject: [PATCH 16/38] map to individual HLA ALT for meaningful mapQ --- bwa-typeHLA.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bwa-typeHLA.sh b/bwa-typeHLA.sh index e112209..b91067b 100755 --- a/bwa-typeHLA.sh +++ b/bwa-typeHLA.sh @@ -21,12 +21,14 @@ if [ $is_ctg -eq 0 ]; then $root/fermi2.pl unitig -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak make -f $pre.tmp.mak EXE_FERMI2=$root/fermi2 EXE_ROPEBWT2=$root/ropebwt2 else - ln -s $pre.fq $pre.tmp.mag.gz + ln -sf $pre.fq $pre.tmp.mag.gz fi # get contigs overlapping HLA exons -$root/bwa mem -B1 -O1 -E1 $root/HLA-ALT.fa $pre.tmp.mag.gz | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.tmp.alt.gz -$root/tabix -Bpsam $pre.tmp.alt.gz $root/HLA-approx.anno | cut -f1 | sort | uniq | $root/seqtk subseq $pre.tmp.mag.gz - | gzip -1 > $pre.tmp.fq.gz +(ls $root/HLA-idx/*.fa | xargs -i $root/bwa mem -t2 -B1 -O1 -E1 {} $pre.tmp.mag.gz 2>/dev/null) | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.tmp.sam.gz +$root/tabix -Bpsam $pre.tmp.sam.gz $root/HLA-approx.anno | cut -f1 | sort | uniq > $pre.tmp.kept +gzip -dc $pre.tmp.sam.gz | perl -ane 'print "$F[0]\n" if /AS:i:(\d+).*XS:i:(\d+)/&&$1==$2' | cut -f1 | sort | uniq > $pre.tmp.dropped +awk -v f=$pre.tmp.dropped 'BEGIN{while((getline0)l[$1]=1}!l[$1]' $pre.tmp.kept | $root/seqtk subseq $pre.tmp.mag.gz - | gzip -1 > $pre.tmp.fq.gz # map HLA exons to de novo contigs $root/bwa index -p $pre.tmp $pre.tmp.fq.gz 2> /dev/null From c2f2cb03ced093ce87a4b3a661ba8c1f8faee4fa Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 6 Nov 2014 09:12:53 -0500 Subject: [PATCH 17/38] to be moved to a separate project --- bwa-typeHLA.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bwa-typeHLA.sh b/bwa-typeHLA.sh index b91067b..1cc74c4 100755 --- a/bwa-typeHLA.sh +++ b/bwa-typeHLA.sh @@ -21,18 +21,19 @@ if [ $is_ctg -eq 0 ]; then $root/fermi2.pl unitig -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak make -f $pre.tmp.mak EXE_FERMI2=$root/fermi2 EXE_ROPEBWT2=$root/ropebwt2 else - ln -sf $pre.fq $pre.tmp.mag.gz + rm -f $pre.tmp.mag.gz + ln -s $pre.fq $pre.tmp.mag.gz fi # get contigs overlapping HLA exons (ls $root/HLA-idx/*.fa | xargs -i $root/bwa mem -t2 -B1 -O1 -E1 {} $pre.tmp.mag.gz 2>/dev/null) | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.tmp.sam.gz $root/tabix -Bpsam $pre.tmp.sam.gz $root/HLA-approx.anno | cut -f1 | sort | uniq > $pre.tmp.kept -gzip -dc $pre.tmp.sam.gz | perl -ane 'print "$F[0]\n" if /AS:i:(\d+).*XS:i:(\d+)/&&$1==$2' | cut -f1 | sort | uniq > $pre.tmp.dropped +gzip -dc $pre.tmp.sam.gz | perl -ane 'print if $F[5]!~/^\d+[SH]/ && $F[5]!~/[SH]$/ && /AS:i:(\d+).*XS:i:(\d+)/ && $1==$2' | cut -f1 | sort | uniq > $pre.tmp.dropped awk -v f=$pre.tmp.dropped 'BEGIN{while((getline0)l[$1]=1}!l[$1]' $pre.tmp.kept | $root/seqtk subseq $pre.tmp.mag.gz - | gzip -1 > $pre.tmp.fq.gz # map HLA exons to de novo contigs -$root/bwa index -p $pre.tmp $pre.tmp.fq.gz 2> /dev/null -$root/bwa mem -aD.1 -t2 $pre.tmp $root/hla.fa | egrep "^(@|$2)" > $pre.sam +$root/bwa index -p $pre.tmp $pre.tmp.fq.gz 2>/dev/null +$root/bwa mem -aD.1 -t2 $pre.tmp $root/hla.fa 2>/dev/null | egrep "^(@|$2)" > $pre.sam # type HLA $root/k8 $root/bwa-typeHLA.js $pre.sam > $pre.gt From 4e97cdfde84a5e06ba035798df1beca93f9bfd5f Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 6 Nov 2014 09:13:15 -0500 Subject: [PATCH 18/38] move to a new project bwa-typeHLA The two scripts cannot be used standalone anyway. --- bwa-typeHLA.js | 287 ------------------------------------------------- bwa-typeHLA.sh | 42 -------- 2 files changed, 329 deletions(-) delete mode 100644 bwa-typeHLA.js delete mode 100755 bwa-typeHLA.sh diff --git a/bwa-typeHLA.js b/bwa-typeHLA.js deleted file mode 100644 index 910073a..0000000 --- a/bwa-typeHLA.js +++ /dev/null @@ -1,287 +0,0 @@ -/***************************************************************** - * The K8 Javascript interpreter is required to run this script. * - * * - * Source code: https://github.com/attractivechaos/k8 * - * Binary: http://sourceforge.net/projects/lh3/files/k8/ * - *****************************************************************/ - -var getopt = function(args, ostr) { - var oli; // option letter list index - if (typeof(getopt.place) == 'undefined') - getopt.ind = 0, getopt.arg = null, getopt.place = -1; - if (getopt.place == -1) { // update scanning pointer - if (getopt.ind >= args.length || args[getopt.ind].charAt(getopt.place = 0) != '-') { - getopt.place = -1; - return null; - } - if (getopt.place + 1 < args[getopt.ind].length && args[getopt.ind].charAt(++getopt.place) == '-') { // found "--" - ++getopt.ind; - getopt.place = -1; - return null; - } - } - var optopt = args[getopt.ind].charAt(getopt.place++); // character checked for validity - if (optopt == ':' || (oli = ostr.indexOf(optopt)) < 0) { - if (optopt == '-') return null; // if the user didn't specify '-' as an option, assume it means null. - if (getopt.place < 0) ++getopt.ind; - return '?'; - } - if (oli+1 >= ostr.length || ostr.charAt(++oli) != ':') { // don't need argument - getopt.arg = null; - if (getopt.place < 0 || getopt.place >= args[getopt.ind].length) ++getopt.ind, getopt.place = -1; - } else { // need an argument - if (getopt.place >= 0 && getopt.place < args[getopt.ind].length) - getopt.arg = args[getopt.ind].substr(getopt.place); - else if (args.length <= ++getopt.ind) { // no arg - getopt.place = -1; - if (ostr.length > 0 && ostr.charAt(0) == ':') return ':'; - return '?'; - } else getopt.arg = args[getopt.ind]; // white space - getopt.place = -1; - ++getopt.ind; - } - return optopt; -} - -/********************* - *** Main function *** - *********************/ - -var c, thres_len = 50, thres_ratio = .8, thres_nm = 5, dbg = false; - -// parse command line options -while ((c = getopt(arguments, "dl:n:")) != null) { - if (c == 'l') thres_len = parseInt(getopt.arg); - else if (c == 'n') thres_nm = parseInt(getopt.arg); - else if (c == 'd') dbg = true; -} -if (arguments.length == getopt.ind) { - print(""); - print("Usage: k8 bwa-typeHLA.js [options] \n"); - print("Options: -n INT drop a contig if the edit distance to the closest gene is >INT ["+thres_nm+"]"); - print(" -l INT drop a contig if its match too short ["+thres_len+"]"); - print(""); - print("Note: The output is TAB delimited with each line consisting of allele1, allele2,"); - print(" #mismatches/gaps on primary exons, #mismatches/gaps on other exons and"); - print(" #exons used in typing.\n"); - exit(1); -} - -// read alignments -var file = new File(arguments[getopt.ind]); -var buf = new Bytes(); -var re_cigar = /(\d+)([MIDSH])/g; - -var len = {}, list = [], gcnt = []; -while (file.readline(buf) >= 0) { - var m, mm, line = buf.toString(); - var t = line.split("\t"); - // SAM header - if (t[0].charAt(0) == '@') { - if (t[0] == '@SQ' && (m = /LN:(\d+)/.exec(line)) != null && (mm = /SN:(\S+)/.exec(line)) != null) - len[mm[1]] = parseInt(m[1]); - continue; - } - // parse gene name and exon number - var gene = null, exon = null; - if ((m = /^(HLA-[^\s_]+)_(\d+)/.exec(t[0])) != null) { - gene = m[1], exon = parseInt(m[2]) - 1; - if (gcnt[exon] == null) gcnt[exon] = {}; - gcnt[exon][gene] = true; - } - if (gene == null || exon == null || t[2] == '*') continue; - // parse clipping and aligned length - var x = 0, ts = parseInt(t[3]) - 1, te = ts, clip = [0, 0]; - while ((m = re_cigar.exec(t[5])) != null) { - var l = parseInt(m[1]); - if (m[2] == 'M') x += l, te += l; - else if (m[2] == 'I') x += l; - else if (m[2] == 'D') te += l; - else if (m[2] == 'S' || m[2] == 'H') clip[x==0?0:1] = l; - } - var tl = len[t[2]]; - var left = ts < clip[0]? ts : clip[0]; - var right = tl - te < clip[1]? tl - te : clip[1]; - var nm = (m = /\tNM:i:(\d+)/.exec(line)) != null? parseInt(m[1]) : 0; - list.push([t[2], gene, exon, ts, te, nm, left + right]); // left+right should be 0 given a prefix-suffix alignment -} - -buf.destroy(); -file.close(); - -// identify the primary exons, the exons associated with most genes -var pri_exon = [], n_pri_exons; -{ - var cnt = [], max = 0; - // count the number of genes per exon and track the max - for (var e = 0; e < gcnt.length; ++e) { - if (gcnt[e] != null) { - var c = 0, h = gcnt[e]; - for (var x in h) ++c; - cnt[e] = c; - max = max > c? max : c; - } else cnt[e] = 0; - } - // find primary exons - var pri_list = []; - for (var e = 0; e < cnt.length; ++e) { - if (cnt[e] == max) pri_list.push(e + 1); - pri_exon[e] = cnt[e] == max? 1 : 0; - } - warn("List of primary exons: ["+pri_list.join(",")+"]"); - n_pri_exons = pri_list.length; -} - -// convert strings to integers (for performance) -var ghash = {}, glist = [], chash = {}, clist = [], elist = []; -for (var i = 0; i < list.length; ++i) { - if (ghash[list[i][1]] == null) { - ghash[list[i][1]] = glist.length; - glist.push(list[i][1]); - } - if (chash[list[i][0]] == null) { - chash[list[i][0]] = clist.length; - clist.push(list[i][0]); - } - var g = ghash[list[i][1]]; - if (elist[g] == null) elist[g] = {}; - elist[g][list[i][2]] = true; -} - -// reorganize hits to exons -var exons = []; -for (var i = 0; i < list.length; ++i) { - var li = list[i]; - if (exons[li[2]] == null) exons[li[2]] = []; - exons[li[2]].push([chash[li[0]], ghash[li[1]], li[5] + li[6], li[4] - li[3]]); -} - -// initialize genotype scores -var pair = []; -for (var i = 0; i < glist.length; ++i) { - pair[i] = []; - for (var j = 0; j <= i; ++j) - pair[i][j] = 0; -} - -function update_pair(x, m, is_pri) -{ - var y, z; - y = (x>>14&0xff) + m < 0xff? (x>>14&0xff) + m : 0xff; - if (is_pri) z = (x>>22) + m < 0xff? (x>>22) + m : 0xff; - else z = x>>22; - return z<<22 | y<<14 | ((x&0x3fff) + (1<<6|is_pri)); -} - -// type each exon -var score = [], ctg = []; -for (var e = 0; e < exons.length; ++e) { - score[e] = []; ctg[e] = []; - if (exons[e] == null) continue; - var ee = exons[e], is_pri = pri_exon[e]? 1 : 0; - // find contigs and genes associated with the current exon - var ch = {}, gh = {}; - for (var i = 0; i < ee.length; ++i) { - if (elist[ee[i][1]][e] != null) - ch[ee[i][0]] = true, gh[ee[i][1]] = true; - } - var ga = [], ca = ctg[e]; - for (var c in ch) ca.push(parseInt(c)); - for (var g in gh) ga.push(parseInt(g)); - var named_ca = []; - for (var i = 0; i < ca.length; ++i) named_ca.push(clist[ca[i]]); - warn("Processing exon "+(e+1)+" (" +ga.length+ " genes; " +ca.length+ " contigs: [" +named_ca.join(", ")+ "])..."); - // set unmapped entries to high mismatch - var sc = score[e]; - for (var k = 0; k < ga.length; ++k) { - var g = ga[k]; - if (sc[g] == null) sc[g] = []; - for (var i = 0; i < ca.length; ++i) - sc[g][ca[i]] = 0xff; - } - // convert representation again - var max_len = []; - for (var i = 0; i < ee.length; ++i) { - var c = ee[i][0], g = ee[i][1]; - sc[g][c] = sc[g][c] < ee[i][2]? sc[g][c] : ee[i][2]; - if (max_len[c] == null) max_len[c] = 0; - max_len[c] = max_len[c] > ee[i][3]? max_len[c] : ee[i][3]; - } - // drop mismapped contigs - var max_max_len = 0; - for (var k = 0; k < ca.length; ++k) - max_max_len = max_max_len > max_len[ca[k]]? max_max_len : max_len[ca[k]]; - var dropped = []; - for (var k = 0; k < ca.length; ++k) { - var min = 0x7fffffff, c = ca[k]; - for (var i = 0; i < ga.length; ++i) { - var g = ga[i]; - min = min < sc[g][c]? min : sc[g][c]; - } - dropped[c] = min > thres_nm? true : false; - if (max_len[c] < thres_len && max_len[c] < thres_ratio * max_max_len) dropped[c] = true; - if (dropped[c]) warn("Dropped contig " +clist[c]+ " due to high divergence to all genes (minNM=" +min+ "; maxLen=" +max_len[c]+ ")"); - } - // fill the pair array - for (var i = 0; i < ga.length; ++i) { - var m = 0, gi = ga[i], g1 = sc[gi]; - // homozygous - for (var k = 0; k < ca.length; ++k) { - var c = ca[k]; - if (!dropped[c]) m += g1[c]; - } - pair[gi][gi] = update_pair(pair[gi][gi], m, is_pri); - // heterozygous - for (var j = i + 1; j < ga.length; ++j) { - var gj = ga[j], g2 = sc[gj], m = 0, a = [0, 0]; - for (var k = 0; k < ca.length; ++k) { - var c = ca[k]; - if (!dropped[c]) { - m += g1[c] < g2[c]? g1[c] : g2[c]; - ++a[g1[c]>14? min_nm : pair[i][j]>>14; - -var out = []; -for (var i = 0; i < glist.length; ++i) - for (var j = 0; j <= i; ++j) - if ((pair[i][j]&63) == n_pri_exons && pair[i][j]>>14 <= min_nm + 1) - out.push([pair[i][j]>>14, pair[i][j]>>6&0xff, i, j, (gsuf[i] + gsuf[j])<<16|(gsub[i] + gsub[j])]); - -out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : a[1]!=b[1]? b[1]-a[1] : a[4]!=b[4]? a[4]-b[4] : a[2]!=b[2]? a[2]-b[2] : a[3]-b[3]}); - -for (var i = 0; i < out.length; ++i) { - print("GT", glist[out[i][3]], glist[out[i][2]], out[i][0]>>8&0xff, out[i][0]&0xff, out[i][1]); - if (dbg) { - var a1 = out[i][2], a2 = out[i][3]; - for (var e = 0; e < exons.length; ++e) { - if (exons[e] == null) continue; - if (elist[a1][e] == null || elist[a2][e] == null) continue; - for (var k = 0; k < ctg[e].length; ++k) { - var c = ctg[e][k]; - print("DB", e+1, score[e][a2][c], score[e][a1][c], clist[c]); - } - } - } -} diff --git a/bwa-typeHLA.sh b/bwa-typeHLA.sh deleted file mode 100755 index 1cc74c4..0000000 --- a/bwa-typeHLA.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -is_ctg=0 - -if [ $# -gt 1 ] && [ $1 == '-A' ]; then - is_ctg=1 - shift -fi - -if [ $# -lt 2 ]; then - echo "Usage: $0 [-A] " - exit 1 -fi - -root=`dirname $0` -pre=$1.$2 - -# de novo assembly -if [ $is_ctg -eq 0 ]; then - len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'` - $root/fermi2.pl unitig -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak - make -f $pre.tmp.mak EXE_FERMI2=$root/fermi2 EXE_ROPEBWT2=$root/ropebwt2 -else - rm -f $pre.tmp.mag.gz - ln -s $pre.fq $pre.tmp.mag.gz -fi - -# get contigs overlapping HLA exons -(ls $root/HLA-idx/*.fa | xargs -i $root/bwa mem -t2 -B1 -O1 -E1 {} $pre.tmp.mag.gz 2>/dev/null) | grep -v ^@ | sort -k3,3 -k4,4n | bgzip > $pre.tmp.sam.gz -$root/tabix -Bpsam $pre.tmp.sam.gz $root/HLA-approx.anno | cut -f1 | sort | uniq > $pre.tmp.kept -gzip -dc $pre.tmp.sam.gz | perl -ane 'print if $F[5]!~/^\d+[SH]/ && $F[5]!~/[SH]$/ && /AS:i:(\d+).*XS:i:(\d+)/ && $1==$2' | cut -f1 | sort | uniq > $pre.tmp.dropped -awk -v f=$pre.tmp.dropped 'BEGIN{while((getline0)l[$1]=1}!l[$1]' $pre.tmp.kept | $root/seqtk subseq $pre.tmp.mag.gz - | gzip -1 > $pre.tmp.fq.gz - -# map HLA exons to de novo contigs -$root/bwa index -p $pre.tmp $pre.tmp.fq.gz 2>/dev/null -$root/bwa mem -aD.1 -t2 $pre.tmp $root/hla.fa 2>/dev/null | egrep "^(@|$2)" > $pre.sam - -# type HLA -$root/k8 $root/bwa-typeHLA.js $pre.sam > $pre.gt - -# delete temporary files -rm -f $pre.tmp.* From b2b42cea7ecab1342dc566cf66168bb9cc69f9c5 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Fri, 7 Nov 2014 12:45:01 -0500 Subject: [PATCH 19/38] removed contig weight & code cleanup Contig weight was calculated based on a false assumption. It is actually not useful. --- bwa-postalt.js | 192 +++++++++---------------------------------------- 1 file changed, 35 insertions(+), 157 deletions(-) diff --git a/bwa-postalt.js b/bwa-postalt.js index 6cf3dc7..96cc6ab 100644 --- a/bwa-postalt.js +++ b/bwa-postalt.js @@ -52,31 +52,6 @@ var getopt = function(args, ostr) { return optopt; } -// print an object in a format similar to JSON. For debugging only. -function obj2str(o) -{ - if (typeof(o) != 'object') { - return o.toString(); - } else if (o == null) { - return "null"; - } else if (Array.isArray(o)) { - var s = "["; - for (var i = 0; i < o.length; ++i) { - if (i) s += ','; - s += obj2str(o[i]); - } - return s + "]"; - } else { - var i = 0, s = "{"; - for (var key in o) { - if (i++) s += ','; - s += key + ":"; - s += obj2str(o[key]); - } - return s + "}"; - } -} - // reverse a string Bytes.prototype.reverse = function() { @@ -219,32 +194,19 @@ function print_buffer(buf2, fp_hla, hla) function bwa_postalt(args) { - var c, opt = { a:1, b:4, o:6, e:1, verbose:3, show_pri:false, update_mapq:true, min_mapq:10, min_sc:90, max_nm_sc:10, show_ev:false, min_pa_ratio:1 }; + var c, opt = { a:1, b:4, o:6, e:1, min_mapq:10, min_sc:90, max_nm_sc:10, min_pa_ratio:1 }; - while ((c = getopt(args, 'Pqev:p:r:')) != null) { - if (c == 'v') opt.verbose = parseInt(getopt.arg); - else if (c == 'p') opt.pre = getopt.arg; - else if (c == 'P') opt.show_pri = true; - else if (c == 'q') opt.recover_maq = false; - else if (c == 'e') opt.show_ev = true; + while ((c = getopt(args, 'p:r:')) != null) { + if (c == 'p') opt.pre = getopt.arg; else if (c == 'r') opt.min_pa_ratio = parseFloat(getopt.arg); } if (opt.min_pa_ratio > 1.) opt.min_pa_ratio = 1.; - if (opt.show_ev && opt.pre == null) { - warn("ERROR: option '-p' must be specified if '-e' is applied."); - exit(1); - } - if (args.length == getopt.ind) { print(""); print("Usage: k8 bwa-postalt.js [options] [aln.sam]\n"); - print("Options: -p STR prefix of file(s) for additional information [null]"); - print(" PREFIX.ctw - weight of each ALT contig"); - print(" PREFIX.evi - reads supporting ALT contigs (effective with -e)"); - print(" -e show reads supporting ALT contigs into file PREFIX.evi"); + print("Options: -p STR prefix of files matching HLA genes [null]"); print(" -r FLOAT reduce mapQ to 0 if not overlapping lifted best and pa= 0) { var line = buf.toString(); if (line.charAt(0) == '@') continue; var t = line.split("\t"); if (t.length < 11) continue; // incomplete lines + is_alt[t[0]] = true; var pos = parseInt(t[3]) - 1; var flag = parseInt(t[1]); - if ((flag&4) || t[2] == '*') { - idx_un[t[0]] = true; - continue; - } + if ((flag&4) || t[2] == '*') continue; var m, cigar = [], l_qaln = 0, l_tlen = 0, l_qclip = 0; if ((m = /^(HLA-[^\s\*]+)\*\d+/.exec(t[0])) != null) { // read HLA contigs if (hla_ctg[m[1]] == null) hla_ctg[m[1]] = 0; @@ -296,10 +255,8 @@ function bwa_postalt(args) } file.close(); var idx_alt = {}, idx_pri = {}; - for (var ctg in intv_alt) - idx_alt[ctg] = intv_ovlp(intv_alt[ctg]); - for (var ctg in intv_pri) - idx_pri[ctg] = intv_ovlp(intv_pri[ctg]); + for (var ctg in intv_alt) idx_alt[ctg] = intv_ovlp(intv_alt[ctg]); + for (var ctg in intv_pri) idx_pri[ctg] = intv_ovlp(intv_pri[ctg]); // initialize the list of HLA contigs var fp_hla = null; @@ -309,18 +266,12 @@ function bwa_postalt(args) fp_hla[h] = new File(opt.pre + '.' + h + '.fq', "w"); } - // initialize the list of ALT contigs - var weight_alt = []; - for (var ctg in idx_alt) - weight_alt[ctg] = [0, 0, 0, 0, 0, 0, intv_alt[ctg][0][3], intv_alt[ctg][0][5], intv_alt[ctg][0][7]]; - for (var ctg in idx_un) - weight_alt[ctg] = [0, 0, 0, 0, 0, 0, '~', 0, 0]; - // process SAM var buf2 = [], hla = {}; file = args.length - getopt.ind >= 2? new File(args[getopt.ind+1]) : new File(); while (file.readline(buf) >= 0) { var m, line = buf.toString(); + if (line.charAt(0) == '@') { // print and then skip the header line print(line); continue; @@ -362,7 +313,7 @@ function bwa_postalt(args) // check if there are ALT hits var has_alt = false; for (var i = 0; i < hits.length; ++i) - if (weight_alt[hits[i].ctg] != null) { + if (is_alt[hits[i].ctg] != null) { has_alt = true; break; } @@ -429,7 +380,7 @@ function bwa_postalt(args) if (hits[i].g == reported_g) ++n_group0; } else { - if (weight_alt[hits[0].ctg] == null) { // no need to go through the following if the single hit is non-ALT + if (is_alt[hits[0].ctg] == null) { // no need to go through the following if the single hit is non-ALT buf2.push(t); continue; } @@ -455,7 +406,8 @@ function bwa_postalt(args) else mapQ = mapQ > ori_mapQ? mapQ : ori_mapQ; } else mapQ = t[4]; - var pri_ofunc = idx_pri[hits[reported_i].pctg], ovlp_alt = []; + // find out whether the read is overlapping HLA genes + var pri_ofunc = idx_pri[hits[reported_i].pctg]; if (pri_ofunc != null) { var rpt_start = 1<<30, rpt_end = 0; for (var i = 0; i < hits.length; ++i) { @@ -465,70 +417,29 @@ function bwa_postalt(args) rpt_end = rpt_end > h.pend ? rpt_end : h.pend; } } - ovlp_alt = pri_ofunc(rpt_start, rpt_end); + var ovlp_alt = pri_ofunc(rpt_start, rpt_end); for (var i = 0; i < ovlp_alt.length; ++i) if ((m = /^(HLA-[^\s\*]+)\*\d+/.exec(ovlp_alt[i][2])) != null) hla[m[1]] = true; } - // ALT genotyping - if (mapQ >= opt.min_mapq && hits[reported_i].score >= opt.min_sc) { - // collect all overlapping ALT contigs - var alts = {}; - for (var i = 0; i < hits.length; ++i) { - var h = hits[i]; - if (h.g == reported_g && weight_alt[h.ctg] != null) - alts[h.ctg] = [h.score, h.NM]; - } - if (ovlp_alt.length > 0) { // add other unreported hits - for (var i = 0; i < ovlp_alt.length; ++i) - if (ovlp_alt[i][0] <= rpt_start && rpt_end <= ovlp_alt[i][1] && alts[ovlp_alt[i][2]] == null) - alts[ovlp_alt[i][2]] = [0, 0]; - } - - // add weight to each ALT contig - var alt_arr = [], max_sc = -1, max_i = -1, sum = 0, min_sc = 1<<30, max_nm = -1; - for (var ctg in alts) - alt_arr.push([ctg, alts[ctg][0], 0, alts[ctg][1]]); - for (var i = 0; i < alt_arr.length; ++i) { - if (max_sc < alt_arr[i][1]) - max_sc = alt_arr[i][1], max_i = i; - min_sc = min_sc < alt_arr[i][1]? min_sc : alt_arr[i][1]; - var nm = alt_arr[i][1] > 0? alt_arr[i][3] : opt.max_nm_sc; - max_nm = max_nm > nm? max_nm : nm; - } - if (max_nm > opt.max_nm_sc) max_nm = opt.max_nm_sc; - if (max_sc > 0 && (alt_arr.length == 1 || min_sc < max_sc)) { - for (var i = 0; i < alt_arr.length; ++i) - sum += (alt_arr[i][2] = Math.pow(10, .6 * (alt_arr[i][1] - max_sc))); - for (var i = 0; i < alt_arr.length; ++i) alt_arr[i][2] /= sum; - for (var i = 0; i < alt_arr.length; ++i) { - var e = [alt_arr[i][0], 1, alt_arr[max_i][2], alt_arr[i][2], max_nm, max_nm * alt_arr[max_i][2], max_nm * alt_arr[i][2]]; - var w = weight_alt[e[0]]; - for (var j = 0; j < 6; ++j) w[j] += e[j+1]; - if (fp_evi) { - e[2] = e[2].toFixed(3); e[3] = e[3].toFixed(3); - e[5] = e[5].toFixed(3); e[6] = e[6].toFixed(3); - fp_evi.write(t[0] + '/' + (t[1]>>6&3) + '\t' + e.join("\t") + '\n'); - } - } - } - } - - // check if the reported hit overlaps a hit to the primary assembly; if so, don't reduce mapping quality - if (opt.update_mapq && mapQ > 0 && n_rpt_lifted <= 1) { + // adjust the mapQ of the primary hits + if (n_rpt_lifted <= 1) { var l = n_rpt_lifted == 1? rpt_lifted : null; for (var i = 0; i < buf2.length; ++i) { var s = buf2[i], is_ovlp = true; if (l != null) { if (l[0] != s[2]) is_ovlp = false; // different chr - if (((s[1]&16) != 0) != l[1]) is_ovlp = false; // different strand - var start = s[3] - 1, end = start; - while ((m = re_cigar.exec(t[5])) != null) - if (m[2] == 'M' || m[2] == 'D' || m[2] == 'N') - end += parseInt(m[1]); - if (!(start < l[3] && l[2] < end)) is_ovlp = false; // no overlap + else if (((s[1]&16) != 0) != l[1]) is_ovlp = false; // different strand + else { + var start = s[3] - 1, end = start; + while ((m = re_cigar.exec(t[5])) != null) + if (m[2] == 'M' || m[2] == 'D' || m[2] == 'N') + end += parseInt(m[1]); + if (!(start < l[3] && l[2] < end)) is_ovlp = false; // no overlap + } } else is_ovlp = false; + // get the "pa" tag if present var om = -1, pa = 10.; for (var j = 11; j < s.length; ++j) if ((m = /^om:i:(\d+)/.exec(s[j])) != null) @@ -555,20 +466,6 @@ function bwa_postalt(args) } } - // generate reversed quality and reverse-complemented sequence if necessary - var rs = null, rq = null; // reversed quality and reverse complement sequence - var need_rev = false; - for (var i = 0; i < hits.length; ++i) { - if (hits[i].g != reported_g || i == reported_i) continue; - if (hits[i].rev != hits[reported_i].rev) - need_rev = true; - } - if (need_rev) { // reverse and reverse complement - aux.length = 0; - aux.set(t[9], 0); aux.revcomp(); rs = aux.toString(); - aux.set(t[10],0); aux.reverse(); rq = aux.toString(); - } - // stage the reported hit t[4] = mapQ; if (n_group0 > 1) t.push("om:i:"+ori_mapQ); @@ -576,18 +473,22 @@ function bwa_postalt(args) buf2.push(t); // stage the hits generated from the XA tag - var cnt = 0; + var cnt = 0, rs = null, rq = null; // rq: reverse quality; rs: reverse complement sequence var rg = (m = /\t(RG:Z:\S+)/.exec(line)) != null? m[1] : null; for (var i = 0; i < hits.length; ++i) { - if (opt.verbose >= 5) print(obj2str(hits[i])); if (hits[i].g != reported_g || i == reported_i) continue; - if (!opt.show_pri && idx_alt[hits[i].ctg] == null) continue; + if (idx_alt[hits[i].ctg] == null) continue; var s = [t[0], 0, hits[i].ctg, hits[i].start+1, mapQ, hits[i].cigar, t[6], t[7], t[8]]; // print sequence/quality and set the rev flag if (hits[i].rev == hits[reported_i].rev) { s.push(t[9], t[10]); s[1] = flag | 0x800; - } else { + } else { // we need to write the reverse sequence + if (rs == null || rq == null) { + aux.length = 0; + aux.set(t[9], 0); aux.revcomp(); rs = aux.toString(); + aux.set(t[10],0); aux.reverse(); rq = aux.toString(); + } s.push(rs, rq); s[1] = (flag ^ 0x10) | 0x800; } @@ -599,35 +500,12 @@ function bwa_postalt(args) } print_buffer(buf2, fp_hla, hla); file.close(); - if (fp_evi != null) fp_evi.close(); if (fp_hla != null) for (var h in fp_hla) fp_hla[h].close(); buf.destroy(); aux.destroy(); - - // print weight of each contig - if (opt.pre != null) { - var fpout = new File(opt.pre + '.ctw', "w"); - var weight_arr = []; - var weight_hla = []; - for (var ctg in weight_alt) { - var w = weight_alt[ctg]; - weight_arr.push([ctg, w[6], w[7], w[8], - w[0], w[1].toFixed(3), w[2].toFixed(3), w[1] > 0? (w[2]/w[1]).toFixed(3) : '0.000', - w[3], w[4].toFixed(3), w[5].toFixed(3), w[4] > 0? (w[5]/w[4]).toFixed(3) : '0.000']); - weight_hla.push([ctg, w[4] > 0? w[5]/w[4] : 0]); - } - weight_arr.sort(function(a,b) { - return a[1] < b[1]? -1 : a[1] > b[1]? 1 : a[2] != b[2]? a[2] - b[2] : a[0] < b[0]? -1 : a[0] > b[0]? 1 : 0; - }); - for (var i = 0; i < weight_arr.length; ++i) { - if (weight_arr[i][1] == '~') weight_arr[i][1] = '*'; - fpout.write(weight_arr[i].join("\t") + '\n'); - } - fpout.close(); - } } bwa_postalt(arguments); From a81603217ed91bacd5bbaa8b51d7c6f22356b9eb Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 10 Nov 2014 10:16:53 -0500 Subject: [PATCH 20/38] mate chr is sometimes wrong --- bwa-postalt.js | 1 + 1 file changed, 1 insertion(+) diff --git a/bwa-postalt.js b/bwa-postalt.js index 96cc6ab..e321c07 100644 --- a/bwa-postalt.js +++ b/bwa-postalt.js @@ -479,6 +479,7 @@ function bwa_postalt(args) if (hits[i].g != reported_g || i == reported_i) continue; if (idx_alt[hits[i].ctg] == null) continue; var s = [t[0], 0, hits[i].ctg, hits[i].start+1, mapQ, hits[i].cigar, t[6], t[7], t[8]]; + if (t[6] == '=' && s[2] != t[2]) s[6] = t[2]; // print sequence/quality and set the rev flag if (hits[i].rev == hits[reported_i].rev) { s.push(t[9], t[10]); From 7eabcf42c4ef102c25b71b20919463ed9767d363 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 10 Nov 2014 13:15:11 -0500 Subject: [PATCH 21/38] r983: bugfix - wrong mate info Picard now gives a clean bill. --- bwamem_pair.c | 9 +++++++-- main.c | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bwamem_pair.c b/bwamem_pair.c index e405423..f8b5d46 100644 --- a/bwamem_pair.c +++ b/bwamem_pair.c @@ -362,8 +362,13 @@ int mem_sam_pe(const mem_opt_t *opt, const bntseq_t *bns, const uint8_t *pac, co no_pairing: for (i = 0; i < 2; ++i) { - if (a[i].n && a[i].a[0].score >= opt->T) - h[i] = mem_reg2aln(opt, bns, pac, s[i].l_seq, s[i].seq, &a[i].a[0]); + int which = -1; + if (a[i].n) { + if (a[i].a[0].score >= opt->T) which = 0; + else if (n_pri[i] < a[i].n && a[i].a[n_pri[i]].score >= opt->T) + which = n_pri[i]; + } + if (which >= 0) h[i] = mem_reg2aln(opt, bns, pac, s[i].l_seq, s[i].seq, &a[i].a[which]); else h[i] = mem_reg2aln(opt, bns, pac, s[i].l_seq, s[i].seq, 0); } if (!(opt->flag & MEM_F_NOPAIRING) && h[0].rid == h[1].rid && h[0].rid >= 0) { // if the top hits from the two ends constitute a proper pair, flag it. diff --git a/main.c b/main.c index a69ffeb..f5448d7 100644 --- a/main.c +++ b/main.c @@ -4,7 +4,7 @@ #include "utils.h" #ifndef PACKAGE_VERSION -#define PACKAGE_VERSION "0.7.10-r963-dirty" +#define PACKAGE_VERSION "0.7.10-r983-dirty" #endif int bwa_fa2pac(int argc, char *argv[]); From c5fe18438a5b82d45cbce909cdd51a1c75fd9c64 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 13 Nov 2014 11:12:23 -0500 Subject: [PATCH 22/38] r984: updated to the latest kseq.h --- kseq.h | 18 +++++++++--------- main.c | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kseq.h b/kseq.h index 642cd33..f3862c6 100644 --- a/kseq.h +++ b/kseq.h @@ -54,9 +54,9 @@ #define __KS_BASIC(type_t, __bufsize) \ static inline kstream_t *ks_init(type_t f) \ { \ - kstream_t *ks = (kstream_t*)calloc(1, sizeof(kstream_t)); \ + kstream_t *ks = (kstream_t*)calloc(1, sizeof(kstream_t)); \ ks->f = f; \ - ks->buf = (unsigned char*)malloc(__bufsize); \ + ks->buf = (unsigned char*)malloc(__bufsize); \ return ks; \ } \ static inline void ks_destroy(kstream_t *ks) \ @@ -74,8 +74,7 @@ if (ks->begin >= ks->end) { \ ks->begin = 0; \ ks->end = __read(ks->f, ks->buf, __bufsize); \ - if (ks->end < __bufsize) ks->is_eof = 1; \ - if (ks->end == 0) return -1; \ + if (ks->end == 0) { ks->is_eof = 1; return -1;} \ } \ return (int)ks->buf[ks->begin++]; \ } @@ -95,17 +94,16 @@ typedef struct __kstring_t { #define __KS_GETUNTIL(__read, __bufsize) \ static int ks_getuntil2(kstream_t *ks, int delimiter, kstring_t *str, int *dret, int append) \ { \ + int gotany = 0; \ if (dret) *dret = 0; \ str->l = append? str->l : 0; \ - if (ks->begin >= ks->end && ks->is_eof) return -1; \ for (;;) { \ int i; \ if (ks->begin >= ks->end) { \ if (!ks->is_eof) { \ ks->begin = 0; \ ks->end = __read(ks->f, ks->buf, __bufsize); \ - if (ks->end < __bufsize) ks->is_eof = 1; \ - if (ks->end == 0) break; \ + if (ks->end == 0) { ks->is_eof = 1; break; } \ } else break; \ } \ if (delimiter == KS_SEP_LINE) { \ @@ -124,8 +122,9 @@ typedef struct __kstring_t { if (str->m - str->l < (size_t)(i - ks->begin + 1)) { \ str->m = str->l + (i - ks->begin) + 1; \ kroundup32(str->m); \ - str->s = (char*)realloc(str->s, str->m); \ + str->s = (char*)realloc(str->s, str->m); \ } \ + gotany = 1; \ memcpy(str->s + str->l, ks->buf + ks->begin, i - ks->begin); \ str->l = str->l + (i - ks->begin); \ ks->begin = i + 1; \ @@ -134,6 +133,7 @@ typedef struct __kstring_t { break; \ } \ } \ + if (!gotany && ks_eof(ks)) return -1; \ if (str->s == 0) { \ str->m = 1; \ str->s = (char*)calloc(1, 1); \ @@ -155,7 +155,7 @@ typedef struct __kstring_t { #define __KSEQ_BASIC(SCOPE, type_t) \ SCOPE kseq_t *kseq_init(type_t fd) \ { \ - kseq_t *s = (kseq_t*)calloc(1, sizeof(kseq_t)); \ + kseq_t *s = (kseq_t*)calloc(1, sizeof(kseq_t)); \ s->f = ks_init(fd); \ return s; \ } \ diff --git a/main.c b/main.c index f5448d7..33e27c2 100644 --- a/main.c +++ b/main.c @@ -4,7 +4,7 @@ #include "utils.h" #ifndef PACKAGE_VERSION -#define PACKAGE_VERSION "0.7.10-r983-dirty" +#define PACKAGE_VERSION "0.7.10-r984-dirty" #endif int bwa_fa2pac(int argc, char *argv[]); From 904afefbc66261496d375e4929c2bf2a276bfffa Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 13 Nov 2014 12:12:41 -0500 Subject: [PATCH 23/38] r985: fix bug due to the lack of _pri contigs d6 --- bwa-postalt.js | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/bwa-postalt.js b/bwa-postalt.js index e321c07..5717045 100644 --- a/bwa-postalt.js +++ b/bwa-postalt.js @@ -178,7 +178,7 @@ function parse_hit(s, opt) return h; } -function print_buffer(buf2, fp_hla, hla) +function print_buffer(buf2, fp_hla, hla) // output alignments { if (buf2.length == 0) return; for (var i = 0; i < buf2.length; ++i) @@ -192,21 +192,34 @@ function print_buffer(buf2, fp_hla, hla) } } +function collect_hla_hits(idx, ctg, start, end, hla) // collect reads hit to HLA genes +{ + var m, ofunc = idx[ctg]; + if (ofunc == null) return; + var ovlp_alt = ofunc(start, end); + for (var i = 0; i < ovlp_alt.length; ++i) + if ((m = /^(HLA-[^\s\*]+)\*\d+/.exec(ovlp_alt[i][2])) != null) + hla[m[1]] = true; +} + function bwa_postalt(args) { + var version = "r985"; var c, opt = { a:1, b:4, o:6, e:1, min_mapq:10, min_sc:90, max_nm_sc:10, min_pa_ratio:1 }; - while ((c = getopt(args, 'p:r:')) != null) { + while ((c = getopt(args, 'vp:r:')) != null) { if (c == 'p') opt.pre = getopt.arg; else if (c == 'r') opt.min_pa_ratio = parseFloat(getopt.arg); + else if (c == 'v') { print(version); exit(0); } } if (opt.min_pa_ratio > 1.) opt.min_pa_ratio = 1.; if (args.length == getopt.ind) { print(""); print("Usage: k8 bwa-postalt.js [options] [aln.sam]\n"); - print("Options: -p STR prefix of files matching HLA genes [null]"); + print("Options: -p STR prefix of output files containting sequences matching HLA genes [null]"); print(" -r FLOAT reduce mapQ to 0 if not overlapping lifted best and pa= 0) { var line = buf.toString(); @@ -237,6 +250,7 @@ function bwa_postalt(args) if ((m = /^(HLA-[^\s\*]+)\*\d+/.exec(t[0])) != null) { // read HLA contigs if (hla_ctg[m[1]] == null) hla_ctg[m[1]] = 0; ++hla_ctg[m[1]]; + hla_chr = t[2]; } while ((m = re_cigar.exec(t[5])) != null) { var l = parseInt(m[1]); @@ -296,6 +310,8 @@ function bwa_postalt(args) var NM = (m = /\tNM:i:(\d+)/.exec(line)) == null? '0' : m[1]; var flag = t[1]; var h = parse_hit([t[2], ((flag&16)?'-':'+') + t[3], t[5], NM], opt); + if (t[2] == hla_chr) collect_hla_hits(idx_pri, h.ctg, h.start, h.end, hla); + if (h.hard) { // the following does not work with hard clipped alignments buf2.push(t); continue; @@ -407,8 +423,7 @@ function bwa_postalt(args) } else mapQ = t[4]; // find out whether the read is overlapping HLA genes - var pri_ofunc = idx_pri[hits[reported_i].pctg]; - if (pri_ofunc != null) { + if (hits[reported_i].pctg == hla_chr) { var rpt_start = 1<<30, rpt_end = 0; for (var i = 0; i < hits.length; ++i) { var h = hits[i]; @@ -417,10 +432,7 @@ function bwa_postalt(args) rpt_end = rpt_end > h.pend ? rpt_end : h.pend; } } - var ovlp_alt = pri_ofunc(rpt_start, rpt_end); - for (var i = 0; i < ovlp_alt.length; ++i) - if ((m = /^(HLA-[^\s\*]+)\*\d+/.exec(ovlp_alt[i][2])) != null) - hla[m[1]] = true; + collect_hla_hits(idx_pri, hla_chr, rpt_start, rpt_end, hla); } // adjust the mapQ of the primary hits From fffe52a45b59fa003313fcdf52ac3da8b7ee9e2b Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 13 Nov 2014 12:50:56 -0500 Subject: [PATCH 24/38] move bwa-typeHLA back to this repo --- extras/run-HLA.sh | 20 ++ extras/typeHLA-selctg.js | 62 +++++ extras/typeHLA.js | 496 +++++++++++++++++++++++++++++++++++++++ extras/typeHLA.sh | 43 ++++ 4 files changed, 621 insertions(+) create mode 100755 extras/run-HLA.sh create mode 100644 extras/typeHLA-selctg.js create mode 100644 extras/typeHLA.js create mode 100755 extras/typeHLA.sh diff --git a/extras/run-HLA.sh b/extras/run-HLA.sh new file mode 100755 index 0000000..4fee16f --- /dev/null +++ b/extras/run-HLA.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +ctg_opt="" +if [ $# -gt 1 ] && [ $1 == '-A' ]; then + ctg_opt="-A" + shift +fi + +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +for f in $1.HLA-*.fq; do + gene=`echo $f | perl -pe 's/^.*(HLA-[A-Z]+[0-9]*).*fq$/$1/'` + echo -e "\n*** Processing gene $gene...\n" >&2 + `dirname $0`/typeHLA.sh $ctg_opt $1 $gene +done + +ls $1.HLA-*.gt | xargs -i echo grep ^GT {} \| head -1 | sh | sed "s,^GT,$1," diff --git a/extras/typeHLA-selctg.js b/extras/typeHLA-selctg.js new file mode 100644 index 0000000..0e02a65 --- /dev/null +++ b/extras/typeHLA-selctg.js @@ -0,0 +1,62 @@ +var min_ovlp = 30; + +if (arguments.length < 3) { + print("Usage: k8 selctg.js [min_ovlp="+min_ovlp+"]"); + exit(1); +} + +if (arguments.length >= 4) min_ovlp = parseInt(arguments[3]); +var gene = arguments[0]; + +var buf = new Bytes(); + +var h = {}; +var file = new File(arguments[1]); +while (file.readline(buf) >= 0) { + var t = buf.toString().split("\t"); + if (t[3] != gene) continue; + if (h[t[0]] == null) h[t[0]] = []; + h[t[0]].push([parseInt(t[1]), parseInt(t[2])]); +} +file.close(); + +var s = {}, re = /(\d+)([MIDSHN])/g; +file = new File(arguments[2]); +while (file.readline(buf) >= 0) { + var line = buf.toString(); + var m, t = line.split("\t"); + var x = h[t[2]]; + if (x == null) continue; + + var start = parseInt(t[3]) - 1, end = start; + while ((m = re.exec(t[5])) != null) // parse CIGAR to get the end position + if (m[2] == 'M' || m[2] == 'D') + end += parseInt(m[1]); + + var max_ovlp = 0; + for (var i = 0; i < x.length; ++i) { + var max_left = x[i][0] > start? x[i][0] : start; + var min_rght = x[i][1] < end ? x[i][1] : end; + max_ovlp = max_ovlp > min_rght - max_left? max_ovlp : min_rght - max_left; + } + + var AS = null, XS = null; + if ((m = /AS:i:(\d+)/.exec(line)) != null) AS = parseInt(m[1]); + if ((m = /XS:i:(\d+)/.exec(line)) != null) XS = parseInt(m[1]); + + if (s[t[0]] == null) s[t[0]] = []; + s[t[0]].push([AS, XS, max_ovlp]); +} +file.close(); + +buf.destroy(); + +for (var x in s) { + var is_rejected = false, y = s[x]; + y.sort(function(a,b) {return b[0]-a[0]}); + for (var i = 0; i < y.length && y[i][0] == y[0][0]; ++i) + if (y[0][2] < min_ovlp || y[i][0] == y[i][1]) + is_rejected = true; + if (is_rejected) continue; + print(x); +} diff --git a/extras/typeHLA.js b/extras/typeHLA.js new file mode 100644 index 0000000..b265d07 --- /dev/null +++ b/extras/typeHLA.js @@ -0,0 +1,496 @@ +/***************************************************************** + * The K8 Javascript interpreter is required to run this script. * + * * + * Source code: https://github.com/attractivechaos/k8 * + * Binary: http://sourceforge.net/projects/lh3/files/k8/ * + *****************************************************************/ + +var getopt = function(args, ostr) { + var oli; // option letter list index + if (typeof(getopt.place) == 'undefined') + getopt.ind = 0, getopt.arg = null, getopt.place = -1; + if (getopt.place == -1) { // update scanning pointer + if (getopt.ind >= args.length || args[getopt.ind].charAt(getopt.place = 0) != '-') { + getopt.place = -1; + return null; + } + if (getopt.place + 1 < args[getopt.ind].length && args[getopt.ind].charAt(++getopt.place) == '-') { // found "--" + ++getopt.ind; + getopt.place = -1; + return null; + } + } + var optopt = args[getopt.ind].charAt(getopt.place++); // character checked for validity + if (optopt == ':' || (oli = ostr.indexOf(optopt)) < 0) { + if (optopt == '-') return null; // if the user didn't specify '-' as an option, assume it means null. + if (getopt.place < 0) ++getopt.ind; + return '?'; + } + if (oli+1 >= ostr.length || ostr.charAt(++oli) != ':') { // don't need argument + getopt.arg = null; + if (getopt.place < 0 || getopt.place >= args[getopt.ind].length) ++getopt.ind, getopt.place = -1; + } else { // need an argument + if (getopt.place >= 0 && getopt.place < args[getopt.ind].length) + getopt.arg = args[getopt.ind].substr(getopt.place); + else if (args.length <= ++getopt.ind) { // no arg + getopt.place = -1; + if (ostr.length > 0 && ostr.charAt(0) == ':') return ':'; + return '?'; + } else getopt.arg = args[getopt.ind]; // white space + getopt.place = -1; + ++getopt.ind; + } + return optopt; +} + +/************************ + * Command line parsing * + ************************/ + +var ver = "r19"; +var c, thres_len = 50, thres_ratio = .8, thres_nm = 5, thres_frac = .33, dbg = false; + +// parse command line options +while ((c = getopt(arguments, "vdl:n:f:")) != null) { + if (c == 'l') thres_len = parseInt(getopt.arg); + else if (c == 'n') thres_nm = parseInt(getopt.arg); + else if (c == 'd') dbg = true; + else if (c == 'f') thres_frac = parseFloat(getopt.arg); + else if (c == 'v') { print(ver); exit(0); } +} +if (arguments.length == getopt.ind) { + print(""); + print("Usage: k8 typeHLA.js [options] \n"); + print("Options: -n INT drop a contig if the edit distance to the closest gene is >INT ["+thres_nm+"]"); + print(" -l INT drop a contig if its match too short ["+thres_len+"]"); + print(" -f FLOAT drop inconsistent contigs if their length = 0) { + var m, mm, line = buf.toString(); + var t = line.split("\t"); + var flag = parseInt(t[1]); + // SAM header + if (t[0].charAt(0) == '@') { + if (t[0] == '@SQ' && (m = /LN:(\d+)/.exec(line)) != null && (mm = /SN:(\S+)/.exec(line)) != null) + len[mm[1]] = parseInt(m[1]); + continue; + } + // parse gene name and exon number + var gene = null, exon = null; + if ((m = /^(HLA-[^\s_]+)_(\d+)/.exec(t[0])) != null) { + gene = m[1], exon = parseInt(m[2]) - 1; + if (gcnt[exon] == null) gcnt[exon] = {}; + gcnt[exon][gene] = true; + } + if (gene == null || exon == null || t[2] == '*') continue; + // parse clipping and aligned length + var x = 0, ts = parseInt(t[3]) - 1, te = ts, clip = [0, 0]; + while ((m = re_cigar.exec(t[5])) != null) { + var l = parseInt(m[1]); + if (m[2] == 'M') x += l, te += l; + else if (m[2] == 'I') x += l; + else if (m[2] == 'D') te += l; + else if (m[2] == 'S' || m[2] == 'H') clip[x==0?0:1] = l; + } + var tl = len[t[2]]; + var left = ts < clip[0]? ts : clip[0]; + var right = tl - te < clip[1]? tl - te : clip[1]; + var qs, qe, ql = clip[0] + x + clip[1]; + if (flag & 16) qs = clip[1], qe = ql - clip[0]; + else qs = clip[0], qe = ql - clip[1]; + var nm = (m = /\tNM:i:(\d+)/.exec(line)) != null? parseInt(m[1]) : 0; + list.push([t[2], gene, exon, ts, te, nm, left + right, qs, qe, ql]); // left+right should be 0 given a prefix-suffix alignment +} + +buf.destroy(); +file.close(); + +/************************************** + * Prepare data structures for typing * + **************************************/ + +// identify the primary exons, the exons associated with most genes +var pri_exon = [], n_pri_exons; +{ + var cnt = [], max = 0; + // count the number of genes per exon and track the max + for (var e = 0; e < gcnt.length; ++e) { + if (gcnt[e] != null) { + var c = 0, h = gcnt[e]; + for (var x in h) ++c; + cnt[e] = c; + max = max > c? max : c; + } else cnt[e] = 0; + } + warn("- Number of genes for each exon: [" +cnt.join(",") + "]"); + // find primary exons + var pri_list = []; + for (var e = 0; e < cnt.length; ++e) { + if (cnt[e] == max) pri_list.push(e + 1); + pri_exon[e] = cnt[e] == max? 1 : 0; + } + warn("- List of primary exon(s): ["+pri_list.join(",")+"]"); + n_pri_exons = pri_list.length; +} + +// convert strings to integers (for performance) +var ghash = {}, glist = [], chash = {}, clist = [], elist = []; +for (var i = 0; i < list.length; ++i) { + if (ghash[list[i][1]] == null) { + ghash[list[i][1]] = glist.length; + glist.push(list[i][1]); + } + if (chash[list[i][0]] == null) { + chash[list[i][0]] = clist.length; + clist.push(list[i][0]); + } + var g = ghash[list[i][1]]; + if (elist[g] == null) elist[g] = {}; + elist[g][list[i][2]] = true; +} + +// extract the 3rd and 4th digits +var gsub = [], gsuf = []; +for (var i = 0; i < glist.length; ++i) { + var m = /^HLA-[^*\s]+\*\d+:(\d+).*([A-Z]?)$/.exec(glist[i]); + gsub[i] = parseInt(m[1]); + gsuf[i] = /[A-Z]$/.test(glist[i])? 1 : 0; +} + +/************************************************* + * Collect genes with perfect matches on primary * + *************************************************/ + +// collect exons with fully covered by perfect match(es) +var perf_exons = []; + +function push_perf_exons(matches, last) +{ + matches.sort(function(a, b) { return a[0]-b[0]; }); + var cov = 0, start = 0, end = 0; + for (var i = 0; i < matches.length; ++i) { + if (matches[i][3] > 0) continue; + if (matches[i][0] <= end) + end = end > matches[i][1]? end : matches[i][1]; + else cov += end - start, start = matches[i][0], end = matches[i][1]; + } + cov += end - start; + if (matches[0][2] == cov) { + if (perf_exons[last[1]] == null) perf_exons[last[1]] = []; + //print(last[0], last[1], ghash[last[0]]); + perf_exons[last[1]].push(ghash[last[0]]); + } +} + +var last = [null, -1], matches = []; +for (var i = 0; i < list.length; ++i) { + var li = list[i]; + if (last[0] != li[1] || last[1] != li[2]) { + if (matches.length) push_perf_exons(matches, last); + matches = []; + last = [li[1], li[2]]; + } + matches.push([li[7], li[8], li[9], li[5]+li[6]]); +} +if (matches.length) push_perf_exons(matches, last); + +// for each gene, count how many primary exons are perfect +var pg_aux_cnt = {}; +for (var e = 0; e < perf_exons.length; ++e) { + if (!pri_exon[e]) continue; + var pe = perf_exons[e]; + var n = pe? pe.length : 0; + for (var i = 0; i < n; ++i) { + var g = pe[i]; + if (pg_aux_cnt[g] == null) pg_aux_cnt[g] = 1; + else ++pg_aux_cnt[g]; + } +} + +// find genes with perfect matches on the primary exons +var perf_genes = []; +for (var g in pg_aux_cnt) + if (pg_aux_cnt[g] == n_pri_exons) + perf_genes.push(parseInt(g)); +warn("- Found " +perf_genes.length+ " genes fully covered by perfect matches on the primary exon(s)"); + +var h_perf_genes = {}; +for (var i = 0; i < perf_genes.length; ++i) { + if (dbg) print("PG", glist[perf_genes[i]]); + h_perf_genes[perf_genes[i]] = true; +} + +/******************* + * Filter hit list * + *******************/ + +// reorganize hits to exons +function list2exons(list, flt_flag, perf_hash) +{ + var exons = []; + for (var i = 0; i < list.length; ++i) { + var li = list[i], c = chash[li[0]], g = ghash[li[1]]; + if (flt_flag != null && flt_flag[c] == 1) continue; + if (perf_hash != null && !perf_hash[g]) continue; + if (exons[li[2]] == null) exons[li[2]] = []; + exons[li[2]].push([c, g, li[5] + li[6], li[4] - li[3]]); + } + return exons; +} + +var exons = list2exons(list), flt_flag = [], ovlp_len = []; +for (var c = 0; c < clist.length; ++c) flt_flag[c] = ovlp_len[c] = 0; +for (var e = 0; e < exons.length; ++e) { + if (!pri_exon[e]) continue; + var ee = exons[e]; + var max_len = []; + for (var c = 0; c < clist.length; ++c) max_len[c] = 0; + for (var i = 0; i < ee.length; ++i) { + var l = ee[i][3] - ee[i][2]; + if (l < 1) l = 1; + if (max_len[ee[i][0]] < l) max_len[ee[i][0]] = l; + } + for (var c = 0; c < clist.length; ++c) ovlp_len[c] += max_len[c]; + for (var i = 0; i < ee.length; ++i) + flt_flag[ee[i][0]] |= (!h_perf_genes[ee[i][1]] || ee[i][2])? 1 : 1<<1; +} + +var l_cons = 0, l_incons = 0; +for (var c = 0; c < clist.length; ++c) + if (flt_flag[c]&2) l_cons += ovlp_len[c]; + else if (flt_flag[c] == 1) l_incons += ovlp_len[c]; + +warn("- Total length of contigs consistent/inconsistent with perfect genes: " +l_cons+ "/" +l_incons); +var attempt_perf = (l_incons/(l_cons+l_incons) < thres_frac); + +/******************************** + * Core function for genotyping * + ********************************/ + +function type_gene(perf_mode) +{ + if (perf_mode) { + var flt_list = []; + for (var c = 0; c < clist.length; ++c) + if (flt_flag[c] == 1) flt_list.push(clist[c]); + warn(" - Filtered " +flt_list.length+ " inconsistent contig(s): [" +flt_list.join(",")+ "]"); + exons = list2exons(list, flt_flag, h_perf_genes); + } else exons = list2exons(list); + + /*********************** + * Score each genotype * + ***********************/ + + // initialize genotype scores + var pair = []; + for (var i = 0; i < glist.length; ++i) { + pair[i] = []; + for (var j = 0; j <= i; ++j) + pair[i][j] = 0; + } + + // these two arrays are used to output debugging information + var score = [], ctg = []; + + function type_exon(e, gt_list) + { + function update_pair(x, m, is_pri) + { + var y, z; + y = (x>>14&0xff) + m < 0xff? (x>>14&0xff) + m : 0xff; + if (is_pri) z = (x>>22) + m < 0xff? (x>>22) + m : 0xff; + else z = x>>22; + return z<<22 | y<<14 | ((x&0x3fff) + (1<<6|is_pri)); + } + + score[e] = []; ctg[e] = []; + if (exons[e] == null) return; + var ee = exons[e], is_pri = pri_exon[e]? 1 : 0; + // find contigs and genes associated with the current exon + var ch = {}, gh = {}; + for (var i = 0; i < ee.length; ++i) + if (elist[ee[i][1]][e] != null) + ch[ee[i][0]] = true, gh[ee[i][1]] = true; + var ga = [], ca = ctg[e]; + for (var c in ch) ca.push(parseInt(c)); + for (var g in gh) ga.push(parseInt(g)); + var named_ca = []; + for (var i = 0; i < ca.length; ++i) named_ca.push(clist[ca[i]]); + warn(" - Processing exon "+(e+1)+" (" +ga.length+ " genes; " +ca.length+ " contigs: [" +named_ca.join(", ")+ "])..."); + // set unmapped entries to high mismatch + var sc = score[e]; + for (var k = 0; k < ga.length; ++k) { + var g = ga[k]; + if (sc[g] == null) sc[g] = []; + for (var i = 0; i < ca.length; ++i) + sc[g][ca[i]] = 0xff; + } + // convert representation again and compute max_len[] + var max_len = []; + for (var i = 0; i < ee.length; ++i) { + var c = ee[i][0], g = ee[i][1]; + if (gh[g] == null || ch[c] == null) continue; + sc[g][c] = sc[g][c] < ee[i][2]? sc[g][c] : ee[i][2]; + if (max_len[c] == null) max_len[c] = 0; + max_len[c] = max_len[c] > ee[i][3]? max_len[c] : ee[i][3]; + } + // drop mismapped contigs + var max_max_len = 0; + for (var k = 0; k < ca.length; ++k) + max_max_len = max_max_len > max_len[ca[k]]? max_max_len : max_len[ca[k]]; + var dropped = []; + for (var k = 0; k < ca.length; ++k) { + var min = 0x7fffffff, c = ca[k]; + for (var i = 0; i < ga.length; ++i) { + var g = ga[i]; + min = min < sc[g][c]? min : sc[g][c]; + } + dropped[c] = min > thres_nm? true : false; + if (max_len[c] < thres_len && max_len[c] < thres_ratio * max_max_len) dropped[c] = true; + if (dropped[c]) warn(" . Dropped low-quality contig " +clist[c]+ " (minNM=" +min+ "; maxLen=" +max_len[c]+ ")"); + } + // fill the pair array + if (gt_list == null) { + for (var i = 0; i < ga.length; ++i) { + var m = 0, gi = ga[i], g1 = sc[gi]; + // homozygous + for (var k = 0; k < ca.length; ++k) { + var c = ca[k]; + if (!dropped[c]) m += g1[c]; + } + pair[gi][gi] = update_pair(pair[gi][gi], m, is_pri); + // heterozygous + for (var j = i + 1; j < ga.length; ++j) { + var gj = ga[j], g2 = sc[gj], m = 0, a = [0, 0]; + for (var k = 0; k < ca.length; ++k) { + var c = ca[k]; + if (!dropped[c]) { + m += g1[c] < g2[c]? g1[c] : g2[c]; + ++a[g1[c]>22? min_nm_pri : pair[i][j]>>22; + + var gt_list = []; + for (var i = 0; i < glist.length; ++i) + for (var j = 0; j <= i; ++j) + if ((pair[i][j]&63) == n_pri_exons && pair[i][j]>>22 == min_nm_pri) + gt_list.push([i, j]); + + warn(" - Collected " +gt_list.length+ " top genotypes on the primary exon(s); minimal edit distance: " +min_nm_pri); + + // type other exons + warn(" - Processing other exon(s)..."); + for (var e = 0; e < exons.length; ++e) + if (!pri_exon[e]) type_exon(e, gt_list); + + /***************************** + * Choose the best genotypes * + *****************************/ + + // genotyping + var min_nm = 0x7fffffff; + for (var i = 0; i < glist.length; ++i) + for (var j = 0; j <= i; ++j) + if ((pair[i][j]&63) == n_pri_exons) + min_nm = min_nm < pair[i][j]>>14? min_nm : pair[i][j]>>14; + + var out = []; + for (var i = 0; i < glist.length; ++i) + for (var j = 0; j <= i; ++j) + if ((pair[i][j]&63) == n_pri_exons && pair[i][j]>>14 <= min_nm + 1) + out.push([pair[i][j]>>14, pair[i][j]>>6&0xff, i, j, (gsuf[i] + gsuf[j])<<16|(gsub[i] + gsub[j])]); + + out.sort(function(a, b) { return a[0]!=b[0]? a[0]-b[0] : a[1]!=b[1]? b[1]-a[1] : a[4]!=b[4]? a[4]-b[4] : a[2]!=b[2]? a[2]-b[2] : a[3]-b[3]}); + + return out; +} + +/********************** + * Perform genotyping * + **********************/ + +warn("- Typing in the imperfect mode..."); +var rst = type_gene(false); +if (attempt_perf) { + warn("- Typing in the perfect mode..."); + var rst_perf = type_gene(true); + warn("- Imperfect vs perfect mode: [" +(rst[0][0]>>8&0xff)+ "," +(rst[0][0]&0xff)+ "] vs [" +(rst_perf[0][0]>>8&0xff)+ "," +(rst_perf[0][0]&0xff)+ "]"); + if (rst_perf[0][0] < rst[0][0]) { + warn("- Chose the result from the perfect mode"); + rst = rst_perf; + } else warn("- Chose the result from the imperfect mode"); +} else warn("- Perfect mode is not attempted"); + +/********** + * Output * + **********/ + +for (var i = 0; i < rst.length; ++i) + print("GT", glist[rst[i][3]], glist[rst[i][2]], rst[i][0]>>8&0xff, rst[i][0]&0xff, rst[i][1]); diff --git a/extras/typeHLA.sh b/extras/typeHLA.sh new file mode 100755 index 0000000..88a3728 --- /dev/null +++ b/extras/typeHLA.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +is_ctg=0 + +if [ $# -gt 1 ] && [ $1 == '-A' ]; then + is_ctg=1 + shift +fi + +if [ $# -lt 2 ]; then + echo "Usage: $0 [-A] " + exit 1 +fi + +preres="resource-human-HLA" +root=`dirname $0` +pre=$1.$2 + +if [ $is_ctg -eq 0 ]; then + echo "** De novo assembling..." >&2 + len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'` + $root/fermi2.pl unitig -f $root/fermi2 -r $root/ropebwt2 -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak + make -f $pre.tmp.mak + cp $pre.tmp.mag.gz $pre.mag.gz +else + rm -f $pre.tmp.mag.gz + ln -s $pre.fq $pre.tmp.mag.gz +fi + +echo "** Selecting contigs overlapping target exons..." >&2 +(ls $root/$preres/HLA-ALT-idx/*.fa.bwt | sed s,.bwt,, | xargs -i $root/bwa mem -t2 -B1 -O1 -E1 {} $pre.tmp.mag.gz 2>/dev/null) | grep -v ^@ | sort -k3,3 -k4,4n | gzip > $pre.tmp.ALT.sam.gz +$root/k8 $root/typeHLA-selctg.js $2 $root/$preres/HLA-ALT-exons.bed $pre.tmp.ALT.sam.gz | $root/seqtk subseq $pre.tmp.mag.gz - | gzip -1 > $pre.tmp.fq.gz + +echo "** Mapping exons to de novo contigs..." >&2 +$root/bwa index -p $pre.tmp $pre.tmp.fq.gz 2>/dev/null +$root/seqtk comp $root/$preres/HLA-CDS.fa | cut -f1 | grep ^$2 | $root/seqtk subseq $root/$preres/HLA-CDS.fa - | $root/bwa mem -aD.1 -t2 $pre.tmp - 2>/dev/null | gzip -1 > $pre.sam.gz + +echo "** Typing..." >&2 +$root/k8 $root/typeHLA.js $pre.sam.gz > $pre.gt + +# delete temporary files +rm -f $pre.tmp.* +[ $is_ctg -eq 1 ] && rm -f $pre.mag.gz From a5c144db4d23eb2190ef84b60b063918d6026712 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 13 Nov 2014 12:53:29 -0500 Subject: [PATCH 25/38] added scripts to generate hs38a.fa and hs38d6.fa --- extras/{run-HLA.sh => run-HLA} | 0 extras/run-gen-hs38a | 9 +++++++++ extras/run-gen-hs38d6 | 9 +++++++++ 3 files changed, 18 insertions(+) rename extras/{run-HLA.sh => run-HLA} (100%) create mode 100755 extras/run-gen-hs38a create mode 100755 extras/run-gen-hs38d6 diff --git a/extras/run-HLA.sh b/extras/run-HLA similarity index 100% rename from extras/run-HLA.sh rename to extras/run-HLA diff --git a/extras/run-gen-hs38a b/extras/run-gen-hs38a new file mode 100755 index 0000000..cd119ad --- /dev/null +++ b/extras/run-gen-hs38a @@ -0,0 +1,9 @@ +#!/bin/bash + +root=`dirname $0` + +wget -O- ftp://ftp.ncbi.nlm.nih.gov/genbank/genomes/Eukaryotes/vertebrates_mammals/Homo_sapiens/GRCh38/seqs_for_alignment_pipelines/GCA_000001405.15_GRCh38_full_analysis_set.fna.gz \ + | gzip -dc > hs38a.fa + +[ ! -f hs38a.fa.alt ] && grep _alt $root/resource-GRCh38/hs38d6.fa.alt > hs38a.fa.alt +[ ! -f hs38a.fa.bwt ] && echo -e "\nPlease run 'bwa index hs38a.fa'...\n" diff --git a/extras/run-gen-hs38d6 b/extras/run-gen-hs38d6 new file mode 100755 index 0000000..86d6fa2 --- /dev/null +++ b/extras/run-gen-hs38d6 @@ -0,0 +1,9 @@ +#!/bin/bash + +root=`dirname $0` + +(wget -O- ftp://ftp.ncbi.nlm.nih.gov/genbank/genomes/Eukaryotes/vertebrates_mammals/Homo_sapiens/GRCh38/seqs_for_alignment_pipelines/GCA_000001405.15_GRCh38_full_analysis_set.fna.gz \ + | gzip -dc; cat $root/data/hs38d6-extra.fa) > hs38d6.fa + +[ ! -f hs38d6.fa.alt ] && cp $root/resource-GRCh38/hs38d6.fa.alt . +[ ! -f hs38d6.fa.bwt ] && echo -e "\nPlease run 'bwa index hs38d6.fa'...\n" From 4373a077d5fdff770281ddf7d9b3e350bdcdc402 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 13 Nov 2014 15:51:00 -0500 Subject: [PATCH 26/38] wrapper for bwa-mem --- extras/run-bwamem | 122 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100755 extras/run-bwamem diff --git a/extras/run-bwamem b/extras/run-bwamem new file mode 100755 index 0000000..b011399 --- /dev/null +++ b/extras/run-bwamem @@ -0,0 +1,122 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use Getopt::Std; + +my %opts = (t=>1, n=>64); +getopts("SPADsp:R:x:t:", \%opts); + +die(' +Usage: run-bwamem [options] [file2] + +Options: -p STR prefix for output files [inferred from file1] + -R STR read group header line such as \'@RG\tID:foo\tSM:bar\' [null] + -x STR read type: pacbio, ont2d or intractg [default] + -t INT number of threads [1] + -P input are paired-end reads if file2 absent + + -A skip HiSeq2000/2500 PE resequencing adapter trimming (via trimadap) + -D skip duplicate marking (via samblaster) + -S for SAM/BAM input, don\'t shuffle + -s sort the output alignment (more RAM) + +Note: File type is determined by the file extension of the first input sequence file. + Recognized file extensions: fasta, fa, fastq, fq, fasta.gz, fa.gz, fastq.gz, + fq.gz, sam, sam.gz and bam. SAM/BAM input will be converted to FASTQ. + +') if @ARGV < 2; + +warn("WARNING: many programs require read groups. Please specify with -R if you can.\n") unless defined($opts{R}); + +my $idx = $ARGV[0]; + +my $exepath = $0 =~/^\S+\/[^\/\s]+/? $0 : &which($0); +my $root = $0 =~/^(\S+)\/[^\/\s]+/? $1 : undef; +die "ERROR: failed to locate the 'bwa.kit' directory\n" if !defined($root); + +my $prefix; +if (defined $opts{p}) { + $prefix = $opts{p}; +} elsif ($ARGV[1] =~ /^(\S+)\.(fastq|fq|fasta|fa|mag|sam|sam\.gz|mag\.gz|fasta\.gz|fa\.gz|fastq\.gz|fq\.gz|bam)$/) { + $prefix = $1; +} else { + die("ERROR: failed to identify the prefix for output. Please specify -p.\n") +} + +my $prefix_dir = $prefix =~ /^(\S+)\/[^\/\s]+$/? $1 : "."; +die("ERROR: directory $prefix_dir is not writable. Please specify a new output prefix with -p.\n") unless (-w $prefix_dir); + +die("ERROR: failed to locate the BWA index. Please run '$root/bwa index -p $idx ref.fa'.\n") + unless (-f "$idx.bwt" && -f "$idx.pac" && -f "$idx.sa" && -f "$idx.ann" && -f "$idx.amb"); + +if (@ARGV >= 3 && $ARGV[1] =~ /\.(bam|sam|sam\.gz)$/) { + warn("WARNING: for SAM/BAM input, only the first sequence file is used.\n"); + @ARGV = 2; +} + +if (defined($opts{P}) && @ARGV >= 3) { + warn("WARNING: option -P is ignored as there are two input sequence files.\n"); + delete $opts{P}; +} + +my $size = 0; +my $comp_ratio = 3.; +for my $f (@ARGV[1..$#ARGV]) { + my @a = stat($f); + my $s = $a[7]; + $s *= $comp_ratio if $f =~ /\.(gz|bam)$/; + $size += int($s) + 1; +} + +my $is_pe = (defined($opts{P}) || @ARGV >= 3)? 1 : 0; +my $is_sam = $ARGV[1] =~ /\.(sam|sam\.gz)$/? 1 : 0; +my $is_bam = $ARGV[1] =~ /\.bam$/? 1 : 0; + +my $cmd = ''; +if ($is_sam || $is_bam) { + my $cmd_sam2bam = $is_sam? "$root/htsbox samview -uS $ARGV[1] \\\n" : "cat $ARGV[1] \\\n"; + my $ntmps = int($size / 4e9) + 1; + my $cmd_shuf = ($is_bam || $is_sam) && !defined($opts{S})? " | $root/htsbox bamshuf -uOn$ntmps - $prefix.shuf \\\n" : ""; + my $cmd_bam2fq = ""; + $cmd_bam2fq = " | $root/htsbox bam2fq -O " . (defined($opts{P})? "-s $prefix.out.se.fq.gz " : "") . "- \\\n"; + $cmd = $cmd_sam2bam . $cmd_shuf . $cmd_bam2fq; +} elsif (@ARGV >= 3) { + $cmd = " | $root/seqtk mergepe $ARGV[1] $ARGV[2] \\\n"; +} else { + $cmd = "cat $ARGV[1] \\\n"; +} + +$cmd .= " | $root/trimadap \\\n" unless defined($opts{A}); +$cmd .= " | $root/bwa mem -t$opts{t} " . (defined($opts{x})? "-x $opts{x} " : "") . ($is_pe? "-P " : "") . (defined($opts{R})? "-R'$opts{R}' " : "") . "$ARGV[0] - 2> $prefix.log.bwamem \\\n"; +$cmd .= " | $root/samblaster 2> $prefix.log.dedup \\\n" unless defined($opts{D}); + +my $has_hla = 0; +if (-f "$ARGV[0].alt") { + my $fh; + open($fh, "$ARGV[0].alt") || die; + while (<$fh>) { + $has_hla = 1 if /^HLA-[^\s\*]+\*\d+/; + } + close($fh); + my $hla_pre = $has_hla? "-p $prefix.out " : ""; + $cmd .= " | $root/k8 $root/bwa-postalt.js $hla_pre$ARGV[0].alt \\\n"; +} + +my $t_sort = $opts{t} < 4? $opts{t} : 4; +$cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.out;\n" : " | gzip -1 > $prefix.out.sam.gz;\n"; +$cmd .= "$root/run-HLA $prefix.out;\n" if ($has_hla); + +print $cmd; + +sub which +{ + my $file = shift; + my $path = (@_)? shift : $ENV{PATH}; + return if (!defined($path)); + foreach my $x (split(":", $path)) { + $x =~ s/\/$//; + return "$x/$file" if (-x "$x/$file"); + } + return; +} From 65c637b03650f6b7176618fe9e706556cd6816f8 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 13 Nov 2014 16:16:21 -0500 Subject: [PATCH 27/38] smarter prefix finder --- README-alt.md | 98 ++++++++++++++--------------------------------- extras/run-bwamem | 42 +++++++++++--------- 2 files changed, 51 insertions(+), 89 deletions(-) diff --git a/README-alt.md b/README-alt.md index 1ee2e9f..cccad4f 100644 --- a/README-alt.md +++ b/README-alt.md @@ -4,24 +4,16 @@ Since version 0.7.11, BWA-MEM supports read mapping against a reference genome with long alternative haplotypes present in separate ALT contigs. To use the ALT-aware mode, users need to provide pairwise ALT-to-reference alignment in the SAM format and rename the file to "*idxbase*.alt". For GRCh38, this alignment -is available from the [BWA resource bundle for GRCh38][res]. +is available from the [binary package of BWA][res]. -#### Option 1: Mapping to the official GRCh38 with ALT contigs - -Construct the index: ```sh -wget ftp://ftp.ncbi.nlm.nih.gov/genbank/genomes/Eukaryotes/vertebrates_mammals/Homo_sapiens/GRCh38/seqs_for_alignment_pipelines/GCA_000001405.15_GRCh38_full_analysis_set.fna.gz -gzip -d GCA_000001405.15_GRCh38_full_analysis_set.fna.gz -mv GCA_000001405.15_GRCh38_full_analysis_set.fna hs38a.fa -bwa index hs38a.fa -cp bwa-hs38-bundle/hs38d4.fa.alt hs38a.fa.alt -``` - -Perform mapping: -```sh -bwa mem hs38a.fa read1.fq read2.fq \ - | bwa-hs38-bundle/k8-linux bwa-postalt.js hs38a.fa.alt \ - | samtools view -bS - > aln.unsrt.bam +# Generate the GRCh38+ALT+decoy+HLA and create the BWA index +wget -O- http://sourceforge.net/projects/bio-bwa/files/bwakit-0.7.11_x64-linux.tar.bz2/download \ + | gzip -dc | tar xf - +bwa.kit/run-gen-hs38d6 # download GRCh38 and write hs38d6.fa +bwa.kit/bwa index hs38d6.fa # create BWA index +# mapping +bwa.kit/run-bwamem hs38d6.fa read1.fq read2.fq ``` In the final alignment, a read may be placed on the [primary assembly][grcdef] @@ -30,26 +22,6 @@ Mapping quality (mapQ) is properly adjusted by the postprocessing script `bwa-postalt.js` using the ALT-to-reference alignment `hs38a.fa.alt`. For details, see the [Methods section](#methods). -#### Option 2: Mapping to the collection of GRCh38, decoy and HLA genes - -Construct the index: -```sh -cat hs38a.fa bwa-hs38-bundle/hs38d4-extra.fa > hs38d4.fa -bwa index hs38d4.fa -cp bwa-hs38-bundle/hs38d4.fa.alt . -``` -Perform mapping: -```sh -bwa mem hs38d4.fa read1.fq read2.fq \ - | bwa-hs38-bundle/k8-linux bwa-postalt.js -p postinfo hs38d4.fa.alt \ - | samtools view -bS - > aln.unsrt.bam -``` -The benefit of this option is to have a more complete reference sequence and -to facilitate HLA typing with a 3rd-party tool (see below). - -***If you are not interested in the way BWA-MEM performs ALT mapping, you can -skip the rest of this documentation.*** - ## Background GRCh38 consists of several components: chromosomal assembly, unlocalized contigs @@ -60,8 +32,8 @@ definitions from the [GRC website][grcdef]. GRCh38 ALT contigs are totaled 109Mb in length, spanning 60Mbp genomic regions. However, sequences that are highly diverged from the primary assembly only -contribute a few million bp. Most subsequences of ALT contigs are highly similar -or identical to the primary assembly. If we align sequence reads to GRCh38+ALT +contribute a few million bp. Most subsequences of ALT contigs are nearly +identical to the primary assembly. If we align sequence reads to GRCh38+ALT treating ALT equal to the primary assembly, we will get many reads with zero mapping quality and lose variants on them. It is crucial to make the mapper aware of ALTs. @@ -88,8 +60,8 @@ the ALT-to-ref alignment, and labels a potential hit as *ALT* or *non-ALT*, depending on whether the hit lands on an ALT contig or not. BWA-MEM then reports alignments and assigns mapQ following these two rules: -1. The original mapQ of a non-ALT hit is computed across non-ALT hits only. - The reported mapQ of an ALT hit is computed across all hits. +1. The mapQ of a non-ALT hit is computed across non-ALT hits only. The mapQ of + an ALT hit is computed across all hits. 2. If there are no non-ALT hits, the best ALT hit is outputted as the primary alignment. If there are both ALT and non-ALT hits, non-ALT hits will be @@ -100,7 +72,7 @@ In theory, non-ALT alignments from step 1 should be identical to alignments against a reference genome with ALT contigs. In practice, the two types of alignments may differ in rare cases due to seeding heuristics. When an ALT hit is significantly better than non-ALT hits, BWA-MEM may miss seeds on the -non-ALT hits. This happens more often for contig mapping. +non-ALT hits. If we don't care about ALT hits, we may skip postprocessing (step 2). Nonetheless, postprocessing is recommended as it improves mapQ and gives more @@ -110,18 +82,12 @@ information about ALT hits. Postprocessing is done with a separate script `bwa-postalt.js`. It reads all potential hits reported in the XA tag, lifts ALT hits to the chromosomal -positions using the ALT-to-ref alignment, groups them after lifting and then -reassigns mapQ based on the best scoring hit in each group with all the hits in -a group get the same mapQ. Being aware of the ALT-to-ref alignment, this script -can greatly improve mapQ of ALT hits and occasionally improve mapQ of non-ALT -hits. - -The script also measures the presence of each ALT contig. For a group of -overlapping ALT contigs c_1, ..., c_m, the weight for c_k equals `\frac{\sum_j -P(c_k|r_j)}{\sum_j\max_i P(c_i|r_j)}`, where `P(c_k|r)=\frac{pow(4,s_k)}{\sum_i -pow(4,s_i)}` is the posterior of c_k given a read r mapped to it with a -Smith-Waterman score s_k. This weight is reported in `postinfo.ctw` in the -option 2 above. +positions using the ALT-to-ref alignment, groups them based on overlaps between +their lifted positions, and then re-estimates mapQ across the best scoring hit +in each group. Being aware of the ALT-to-ref alignment, this script can greatly +improve mapQ of ALT hits and occasionally improve mapQ of non-ALT hits. It also +writes each hit overlapping the reported hit into a separate SAM line. This +enables variant calling on each ALT contig independent of others. ### On the completeness of GRCh38+ALT @@ -129,10 +95,10 @@ While GRCh38 is much more complete than GRCh37, it is still missing some true human sequences. To make sure every piece of sequence in the reference assembly is correct, the [Genome Reference Consortium][grc] (GRC) require each ALT contig to have enough support from multiple sources before considering to add it to the -reference assembly. This careful procedure has left out some sequences, one of -which is [this example][novel], a 10kb contig assembled from CHM1 short -reads and present also in NA12878. You can try [BLAT][blat] or [BLAST][blast] to -see where it maps. +reference assembly. This careful and sophisticated procedure has left out some +sequences, one of which is [this example][novel], a 10kb contig assembled from +CHM1 short reads and present also in NA12878. You can try [BLAT][blat] or +[BLAST][blast] to see where it maps. For a more complete reference genome, we compiled a new set of decoy sequences from GenBank clones and the de novo assembly of 254 public [SGDP][sgdp] samples. @@ -146,12 +112,11 @@ not to high resolution for now. ### More on HLA typing -It is [well known][hlalink] that HLA genes are associated with many autoimmune -diseases as well as some others not directly related to the immune system. -However, many HLA alleles are highly diverged from the reference genome. If we -map whole-genome shotgun (WGS) reads to the reference only, many -allele-informative will get lost. As a result, the vast majority of WGS projects -have ignored these important genes. +It is [well known][hlalink] that HLA genes are associated with many autoimmunity +infectious diseases and drug responses. However, many HLA alleles are highly +diverged from the reference genome. If we map whole-genome shotgun (WGS) reads +to the reference only, many allele-informative will get lost. As a result, the +vast majority of WGS projects have ignored these important genes. We recommend to include the genomic regions of classical HLA genes in the BWA index. This way we will be able to get a more complete collection of reads @@ -160,13 +125,6 @@ and type HLA genes with another program, such as [Warren et al (2012)][hla4], [Liu et al (2013)][hla2], [Bai et al (2014)][hla3], [Dilthey et al (2014)][hla1] or others from [this list][hlatools]. -If the postprocessing script `bwa-postalt.js` is invoked with `-p prefix`, it -will also write the top three alleles to file `prefix.hla`. However, as most HLA -alleles from IMGT/HLA don't have intronic sequences and thus are not included in -the BWA index from option 2, we are unable to type HLA genes to high resolution -with the BWA-MEM mapping alone. A dedicated tool is recommended for accurate -typing. - ### Evaluating ALT Mapping (Coming soon...) diff --git a/extras/run-bwamem b/extras/run-bwamem index b011399..c01fc5d 100755 --- a/extras/run-bwamem +++ b/extras/run-bwamem @@ -5,16 +5,16 @@ use warnings; use Getopt::Std; my %opts = (t=>1, n=>64); -getopts("SPADsp:R:x:t:", \%opts); +getopts("SpADso:R:x:t:", \%opts); die(' Usage: run-bwamem [options] [file2] -Options: -p STR prefix for output files [inferred from file1] +Options: -o STR prefix for output files [inferred from input] -R STR read group header line such as \'@RG\tID:foo\tSM:bar\' [null] -x STR read type: pacbio, ont2d or intractg [default] -t INT number of threads [1] - -P input are paired-end reads if file2 absent + -p input are paired-end reads if file2 absent -A skip HiSeq2000/2500 PE resequencing adapter trimming (via trimadap) -D skip duplicate marking (via samblaster) @@ -35,18 +35,6 @@ my $exepath = $0 =~/^\S+\/[^\/\s]+/? $0 : &which($0); my $root = $0 =~/^(\S+)\/[^\/\s]+/? $1 : undef; die "ERROR: failed to locate the 'bwa.kit' directory\n" if !defined($root); -my $prefix; -if (defined $opts{p}) { - $prefix = $opts{p}; -} elsif ($ARGV[1] =~ /^(\S+)\.(fastq|fq|fasta|fa|mag|sam|sam\.gz|mag\.gz|fasta\.gz|fa\.gz|fastq\.gz|fq\.gz|bam)$/) { - $prefix = $1; -} else { - die("ERROR: failed to identify the prefix for output. Please specify -p.\n") -} - -my $prefix_dir = $prefix =~ /^(\S+)\/[^\/\s]+$/? $1 : "."; -die("ERROR: directory $prefix_dir is not writable. Please specify a new output prefix with -p.\n") unless (-w $prefix_dir); - die("ERROR: failed to locate the BWA index. Please run '$root/bwa index -p $idx ref.fa'.\n") unless (-f "$idx.bwt" && -f "$idx.pac" && -f "$idx.sa" && -f "$idx.ann" && -f "$idx.amb"); @@ -55,21 +43,37 @@ if (@ARGV >= 3 && $ARGV[1] =~ /\.(bam|sam|sam\.gz)$/) { @ARGV = 2; } -if (defined($opts{P}) && @ARGV >= 3) { +if (defined($opts{p}) && @ARGV >= 3) { warn("WARNING: option -P is ignored as there are two input sequence files.\n"); - delete $opts{P}; + delete $opts{p}; } +my $prefix; +if (defined $opts{o}) { + $prefix = $opts{o}; +} elsif (@ARGV >= 3) { + my $len = length($ARGV[1]) < length($ARGV[2])? length($ARGV[1]) : length($ARGV[2]); + my $i; + for ($i = 0; $i < $len; ++$i) { + last if substr($ARGV[1], $i, 1) ne substr($ARGV[2], $i, 1) + } + $prefix = substr($ARGV[1], 0, $i) if $i > 0; +} elsif ($ARGV[1] =~ /^(\S+)\.(fastq|fq|fasta|fa|mag|sam|sam\.gz|mag\.gz|fasta\.gz|fa\.gz|fastq\.gz|fq\.gz|bam)$/) { + $prefix = $1; +} +die("ERROR: failed to identify the prefix for output. Please specify -p.\n") unless defined($prefix); + my $size = 0; my $comp_ratio = 3.; for my $f (@ARGV[1..$#ARGV]) { my @a = stat($f); my $s = $a[7]; + die("ERROR: failed to read file $f\n") if !defined($s); $s *= $comp_ratio if $f =~ /\.(gz|bam)$/; $size += int($s) + 1; } -my $is_pe = (defined($opts{P}) || @ARGV >= 3)? 1 : 0; +my $is_pe = (defined($opts{p}) || @ARGV >= 3)? 1 : 0; my $is_sam = $ARGV[1] =~ /\.(sam|sam\.gz)$/? 1 : 0; my $is_bam = $ARGV[1] =~ /\.bam$/? 1 : 0; @@ -79,7 +83,7 @@ if ($is_sam || $is_bam) { my $ntmps = int($size / 4e9) + 1; my $cmd_shuf = ($is_bam || $is_sam) && !defined($opts{S})? " | $root/htsbox bamshuf -uOn$ntmps - $prefix.shuf \\\n" : ""; my $cmd_bam2fq = ""; - $cmd_bam2fq = " | $root/htsbox bam2fq -O " . (defined($opts{P})? "-s $prefix.out.se.fq.gz " : "") . "- \\\n"; + $cmd_bam2fq = " | $root/htsbox bam2fq -O " . (defined($opts{p})? "-s $prefix.out.se.fq.gz " : "") . "- \\\n"; $cmd = $cmd_sam2bam . $cmd_shuf . $cmd_bam2fq; } elsif (@ARGV >= 3) { $cmd = " | $root/seqtk mergepe $ARGV[1] $ARGV[2] \\\n"; From 050482ef778fed56411b945f7836a689a2ba5209 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Thu, 13 Nov 2014 19:52:47 -0500 Subject: [PATCH 28/38] fine tune --- README-alt.md | 2 +- extras/run-bwamem | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/README-alt.md b/README-alt.md index cccad4f..40fb9d1 100644 --- a/README-alt.md +++ b/README-alt.md @@ -13,7 +13,7 @@ wget -O- http://sourceforge.net/projects/bio-bwa/files/bwakit-0.7.11_x64-linux.t bwa.kit/run-gen-hs38d6 # download GRCh38 and write hs38d6.fa bwa.kit/bwa index hs38d6.fa # create BWA index # mapping -bwa.kit/run-bwamem hs38d6.fa read1.fq read2.fq +bwa.kit/run-bwamem hs38d6.fa read1.fq read2.fq | sh ``` In the final alignment, a read may be placed on the [primary assembly][grcdef] diff --git a/extras/run-bwamem b/extras/run-bwamem index c01fc5d..30e682d 100755 --- a/extras/run-bwamem +++ b/extras/run-bwamem @@ -5,7 +5,7 @@ use warnings; use Getopt::Std; my %opts = (t=>1, n=>64); -getopts("SpADso:R:x:t:", \%opts); +getopts("SpADsko:R:x:t:", \%opts); die(' Usage: run-bwamem [options] [file2] @@ -13,6 +13,9 @@ Usage: run-bwamem [options] [file2] Options: -o STR prefix for output files [inferred from input] -R STR read group header line such as \'@RG\tID:foo\tSM:bar\' [null] -x STR read type: pacbio, ont2d or intractg [default] + intractg: intra-species contig (kb query, highly similar) + pacbio: pacbio subreads (~10kb query, high error rate) + ont2d: Oxford Nanopore reads (~10kb query, higher error rate) -t INT number of threads [1] -p input are paired-end reads if file2 absent @@ -20,6 +23,7 @@ Options: -o STR prefix for output files [inferred from -D skip duplicate marking (via samblaster) -S for SAM/BAM input, don\'t shuffle -s sort the output alignment (more RAM) + -k keep temporary files generated by typeHLA Note: File type is determined by the file extension of the first input sequence file. Recognized file extensions: fasta, fa, fastq, fq, fasta.gz, fa.gz, fastq.gz, @@ -77,6 +81,11 @@ my $is_pe = (defined($opts{p}) || @ARGV >= 3)? 1 : 0; my $is_sam = $ARGV[1] =~ /\.(sam|sam\.gz)$/? 1 : 0; my $is_bam = $ARGV[1] =~ /\.bam$/? 1 : 0; +if (defined($opts{x})) { + $opts{A} = $opts{D} = 1; + delete $opts{p}; +} + my $cmd = ''; if ($is_sam || $is_bam) { my $cmd_sam2bam = $is_sam? "$root/htsbox samview -uS $ARGV[1] \\\n" : "cat $ARGV[1] \\\n"; @@ -86,13 +95,13 @@ if ($is_sam || $is_bam) { $cmd_bam2fq = " | $root/htsbox bam2fq -O " . (defined($opts{p})? "-s $prefix.out.se.fq.gz " : "") . "- \\\n"; $cmd = $cmd_sam2bam . $cmd_shuf . $cmd_bam2fq; } elsif (@ARGV >= 3) { - $cmd = " | $root/seqtk mergepe $ARGV[1] $ARGV[2] \\\n"; + $cmd = "$root/seqtk mergepe $ARGV[1] $ARGV[2] \\\n"; } else { $cmd = "cat $ARGV[1] \\\n"; } $cmd .= " | $root/trimadap \\\n" unless defined($opts{A}); -$cmd .= " | $root/bwa mem -t$opts{t} " . (defined($opts{x})? "-x $opts{x} " : "") . ($is_pe? "-P " : "") . (defined($opts{R})? "-R'$opts{R}' " : "") . "$ARGV[0] - 2> $prefix.log.bwamem \\\n"; +$cmd .= " | $root/bwa mem -t$opts{t} " . (defined($opts{x})? "-x $opts{x} " : "") . ($is_pe? "-p " : "") . (defined($opts{R})? "-R'$opts{R}' " : "") . "$ARGV[0] - 2> $prefix.log.bwamem \\\n"; $cmd .= " | $root/samblaster 2> $prefix.log.dedup \\\n" unless defined($opts{D}); my $has_hla = 0; @@ -109,7 +118,12 @@ if (-f "$ARGV[0].alt") { my $t_sort = $opts{t} < 4? $opts{t} : 4; $cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.out;\n" : " | gzip -1 > $prefix.out.sam.gz;\n"; -$cmd .= "$root/run-HLA $prefix.out;\n" if ($has_hla); + +if ($has_hla && (!defined($opts{x}) || $opts{x} eq 'intractg')) { + $cmd .= "$root/run-HLA ". ($opts{x} eq 'intractg'? "-A " : "") . "$prefix.out > $prefix.hla.top 2> $prefix.log.hla;\n"; + $cmd .= "cat $prefix.out.HLA*.gt | grep ^GT | cut -f2- > $prefix.hla.all;\n"; + $cmd .= "rm -f $prefix.out.HLA*;\n" unless defined($opts{k}); +} print $cmd; From 792be78a8096eb111a090b573d64520a615bc760 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Sun, 16 Nov 2014 00:46:02 -0500 Subject: [PATCH 29/38] replace run-gen-hs38* with run-gen-ref --- extras/run-gen-hs38a | 9 --------- extras/run-gen-hs38d6 | 9 --------- extras/run-gen-ref | 31 +++++++++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 18 deletions(-) delete mode 100755 extras/run-gen-hs38a delete mode 100755 extras/run-gen-hs38d6 create mode 100755 extras/run-gen-ref diff --git a/extras/run-gen-hs38a b/extras/run-gen-hs38a deleted file mode 100755 index cd119ad..0000000 --- a/extras/run-gen-hs38a +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -root=`dirname $0` - -wget -O- ftp://ftp.ncbi.nlm.nih.gov/genbank/genomes/Eukaryotes/vertebrates_mammals/Homo_sapiens/GRCh38/seqs_for_alignment_pipelines/GCA_000001405.15_GRCh38_full_analysis_set.fna.gz \ - | gzip -dc > hs38a.fa - -[ ! -f hs38a.fa.alt ] && grep _alt $root/resource-GRCh38/hs38d6.fa.alt > hs38a.fa.alt -[ ! -f hs38a.fa.bwt ] && echo -e "\nPlease run 'bwa index hs38a.fa'...\n" diff --git a/extras/run-gen-hs38d6 b/extras/run-gen-hs38d6 deleted file mode 100755 index 86d6fa2..0000000 --- a/extras/run-gen-hs38d6 +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -root=`dirname $0` - -(wget -O- ftp://ftp.ncbi.nlm.nih.gov/genbank/genomes/Eukaryotes/vertebrates_mammals/Homo_sapiens/GRCh38/seqs_for_alignment_pipelines/GCA_000001405.15_GRCh38_full_analysis_set.fna.gz \ - | gzip -dc; cat $root/data/hs38d6-extra.fa) > hs38d6.fa - -[ ! -f hs38d6.fa.alt ] && cp $root/resource-GRCh38/hs38d6.fa.alt . -[ ! -f hs38d6.fa.bwt ] && echo -e "\nPlease run 'bwa index hs38d6.fa'...\n" diff --git a/extras/run-gen-ref b/extras/run-gen-ref new file mode 100755 index 0000000..86317c3 --- /dev/null +++ b/extras/run-gen-ref @@ -0,0 +1,31 @@ +#!/bin/bash + +root=`dirname $0` + +url38="ftp://ftp.ncbi.nlm.nih.gov/genbank/genomes/Eukaryotes/vertebrates_mammals/Homo_sapiens/GRCh38/seqs_for_alignment_pipelines/GCA_000001405.15_GRCh38_full_analysis_set.fna.gz" +url37d5="ftp://ftp.ncbi.nlm.nih.gov/1000genomes/ftp/technical/reference/phase2_reference_assembly_sequence/hs37d5.fa.gz" + +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1; +fi + +if [ $1 == "hs38d6" ]; then + (wget -O- $url38 | gzip -dc; cat $root/resource-GRCh38/hs38d6-extra.fa) > $1.fa + [ ! -f $1.fa.alt ] && cp $root/resource-GRCh38/hs38d6.fa.alt $1.fa.alt +elif [ $1 == "hs38a" ]; then + wget -O- $url38 | gzip -dc > $1.fa + [ ! -f $1.fa.alt ] && grep _alt $root/resource-GRCh38/hs38d6.fa.alt > $1.fa.alt +elif [ $1 == "hs38" ]; then + # we don't use GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.gz because it lacks EBV + wget -O- $url38 | gzip -dc | awk '/^>/{f=/_alt/?0:1}f' > $1.fa +elif [ $1 == "hs37d5" ]; then + wget -O- $url37d5 | gzip -dc > $1.fa 2>/dev/null +elif [ $1 == "hs37" ]; then + wget -O- $url37d5 | gzip -dc 2>/dev/null | awk '/^>/{f=/>hs37d5/?0:1}f' > $1.fa +else + echo "ERROR: unknown genome build" +fi + +[ ! -f $1.fa.bwt ] && echo -e "\nPlease run 'bwa index $1.fa'...\n" + From 0071c4ab22f3a95c04386df7cf03079dd06f1478 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 17 Nov 2014 10:44:54 -0500 Subject: [PATCH 30/38] polish the run script --- extras/run-bwamem | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/extras/run-bwamem b/extras/run-bwamem index 30e682d..3231a6e 100755 --- a/extras/run-bwamem +++ b/extras/run-bwamem @@ -5,7 +5,7 @@ use warnings; use Getopt::Std; my %opts = (t=>1, n=>64); -getopts("SpADsko:R:x:t:", \%opts); +getopts("SpAdsko:R:x:t:", \%opts); die(' Usage: run-bwamem [options] [file2] @@ -20,7 +20,7 @@ Options: -o STR prefix for output files [inferred from -p input are paired-end reads if file2 absent -A skip HiSeq2000/2500 PE resequencing adapter trimming (via trimadap) - -D skip duplicate marking (via samblaster) + -d mark duplicate (via samblaster) -S for SAM/BAM input, don\'t shuffle -s sort the output alignment (more RAM) -k keep temporary files generated by typeHLA @@ -29,6 +29,19 @@ Note: File type is determined by the file extension of the first input sequence Recognized file extensions: fasta, fa, fastq, fq, fasta.gz, fa.gz, fastq.gz, fq.gz, sam, sam.gz and bam. SAM/BAM input will be converted to FASTQ. + When option -d is in use, all the input reads are required to come from the same + library and all the reads from the library shall be included in the input. In + addition, samblaster does not distinguish optical and PCR duplicates. + + The output may consist of the following files: + + {-o}.aln.sam.gz - unsorted alignment (unless -s is specified) + {-o}.aln.bam - sorted alignment (if -s is specified) + {-o}.oph.sam.gz - orphan single-end reads mapping (if input is paired SAM/BAM) + {-o}.hla.top - best HLA typing for the six classical HLA genes + {-o}.hla.all - additional HLA typing + {-o}.log.* - log files + ') if @ARGV < 2; warn("WARNING: many programs require read groups. Please specify with -R if you can.\n") unless defined($opts{R}); @@ -82,7 +95,7 @@ my $is_sam = $ARGV[1] =~ /\.(sam|sam\.gz)$/? 1 : 0; my $is_bam = $ARGV[1] =~ /\.bam$/? 1 : 0; if (defined($opts{x})) { - $opts{A} = $opts{D} = 1; + $opts{A} = 1; delete($opts{d}); delete $opts{p}; } @@ -92,7 +105,7 @@ if ($is_sam || $is_bam) { my $ntmps = int($size / 4e9) + 1; my $cmd_shuf = ($is_bam || $is_sam) && !defined($opts{S})? " | $root/htsbox bamshuf -uOn$ntmps - $prefix.shuf \\\n" : ""; my $cmd_bam2fq = ""; - $cmd_bam2fq = " | $root/htsbox bam2fq -O " . (defined($opts{p})? "-s $prefix.out.se.fq.gz " : "") . "- \\\n"; + $cmd_bam2fq = " | $root/htsbox bam2fq -O " . (defined($opts{p})? "-s $prefix.oph.fq.gz " : "") . "- \\\n"; $cmd = $cmd_sam2bam . $cmd_shuf . $cmd_bam2fq; } elsif (@ARGV >= 3) { $cmd = "$root/seqtk mergepe $ARGV[1] $ARGV[2] \\\n"; @@ -100,9 +113,11 @@ if ($is_sam || $is_bam) { $cmd = "cat $ARGV[1] \\\n"; } +my $bwa_opts = ($opts{t} > 1? "-t$opts{t} " : "") . (defined($opts{x})? "-x $opts{x} " : "") . (defined($opts{R})? "-R'$opts{R}' " : ""); + $cmd .= " | $root/trimadap \\\n" unless defined($opts{A}); -$cmd .= " | $root/bwa mem -t$opts{t} " . (defined($opts{x})? "-x $opts{x} " : "") . ($is_pe? "-p " : "") . (defined($opts{R})? "-R'$opts{R}' " : "") . "$ARGV[0] - 2> $prefix.log.bwamem \\\n"; -$cmd .= " | $root/samblaster 2> $prefix.log.dedup \\\n" unless defined($opts{D}); +$cmd .= " | $root/bwa mem $bwa_opts" . ($is_pe? "-p " : "") . "$ARGV[0] - 2> $prefix.log.bwamem \\\n"; +$cmd .= " | $root/samblaster 2> $prefix.log.dedup \\\n" if defined($opts{d}); my $has_hla = 0; if (-f "$ARGV[0].alt") { @@ -112,17 +127,23 @@ if (-f "$ARGV[0].alt") { $has_hla = 1 if /^HLA-[^\s\*]+\*\d+/; } close($fh); - my $hla_pre = $has_hla? "-p $prefix.out " : ""; + my $hla_pre = $has_hla? "-p $prefix.hla " : ""; $cmd .= " | $root/k8 $root/bwa-postalt.js $hla_pre$ARGV[0].alt \\\n"; } my $t_sort = $opts{t} < 4? $opts{t} : 4; -$cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.out;\n" : " | gzip -1 > $prefix.out.sam.gz;\n"; +$cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.aln;\n" : " | gzip -1 > $prefix.aln.sam.gz;\n"; + +# map single end reads +if ($cmd =~ /$prefix.oph.fq.gz/) { + $cmd .= "[ -s $prefix.oph.fq.gz ] && "; + $cmd .= "$root/bwa mem $bwa_opts$ARGV[0] $prefix.oph.fq.gz 2>> $prefix.log.bwamem | gzip -1 > $prefix.oph.sam.gz; rm -f $prefix.oph.fq.gz;\n"; +} if ($has_hla && (!defined($opts{x}) || $opts{x} eq 'intractg')) { - $cmd .= "$root/run-HLA ". ($opts{x} eq 'intractg'? "-A " : "") . "$prefix.out > $prefix.hla.top 2> $prefix.log.hla;\n"; - $cmd .= "cat $prefix.out.HLA*.gt | grep ^GT | cut -f2- > $prefix.hla.all;\n"; - $cmd .= "rm -f $prefix.out.HLA*;\n" unless defined($opts{k}); + $cmd .= "$root/run-HLA ". ($opts{x} eq 'intractg'? "-A " : "") . "$prefix.hla > $prefix.hla.top 2> $prefix.log.hla;\n"; + $cmd .= "cat $prefix.hla.HLA*.gt | grep ^GT | cut -f2- > $prefix.hla.all;\n"; + $cmd .= "rm -f $prefix.hla.HLA*;\n" unless defined($opts{k}); } print $cmd; From bf053c779b4882974cfd33bebfb10df1f4ed1d4f Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 17 Nov 2014 10:54:16 -0500 Subject: [PATCH 31/38] polish --- extras/run-bwamem | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/extras/run-bwamem b/extras/run-bwamem index 3231a6e..40724bb 100755 --- a/extras/run-bwamem +++ b/extras/run-bwamem @@ -5,7 +5,7 @@ use warnings; use Getopt::Std; my %opts = (t=>1, n=>64); -getopts("SpAdsko:R:x:t:", \%opts); +getopts("Spadsko:R:x:t:", \%opts); die(' Usage: run-bwamem [options] [file2] @@ -19,10 +19,10 @@ Options: -o STR prefix for output files [inferred from -t INT number of threads [1] -p input are paired-end reads if file2 absent - -A skip HiSeq2000/2500 PE resequencing adapter trimming (via trimadap) + -a trim HiSeq2000/2500 PE resequencing adapters (via trimadap) -d mark duplicate (via samblaster) -S for SAM/BAM input, don\'t shuffle - -s sort the output alignment (more RAM) + -s sort the output alignment (higher RAM footprint) -k keep temporary files generated by typeHLA Note: File type is determined by the file extension of the first input sequence file. @@ -95,8 +95,7 @@ my $is_sam = $ARGV[1] =~ /\.(sam|sam\.gz)$/? 1 : 0; my $is_bam = $ARGV[1] =~ /\.bam$/? 1 : 0; if (defined($opts{x})) { - $opts{A} = 1; delete($opts{d}); - delete $opts{p}; + delete($opts{d}); delete($opts{a}); delete $opts{p}; } my $cmd = ''; @@ -115,7 +114,7 @@ if ($is_sam || $is_bam) { my $bwa_opts = ($opts{t} > 1? "-t$opts{t} " : "") . (defined($opts{x})? "-x $opts{x} " : "") . (defined($opts{R})? "-R'$opts{R}' " : ""); -$cmd .= " | $root/trimadap \\\n" unless defined($opts{A}); +$cmd .= " | $root/trimadap \\\n" if defined($opts{a}); $cmd .= " | $root/bwa mem $bwa_opts" . ($is_pe? "-p " : "") . "$ARGV[0] - 2> $prefix.log.bwamem \\\n"; $cmd .= " | $root/samblaster 2> $prefix.log.dedup \\\n" if defined($opts{d}); @@ -136,8 +135,10 @@ $cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.al # map single end reads if ($cmd =~ /$prefix.oph.fq.gz/) { - $cmd .= "[ -s $prefix.oph.fq.gz ] && "; - $cmd .= "$root/bwa mem $bwa_opts$ARGV[0] $prefix.oph.fq.gz 2>> $prefix.log.bwamem | gzip -1 > $prefix.oph.sam.gz; rm -f $prefix.oph.fq.gz;\n"; + $cmd .= "[ -s $prefix.oph.fq.gz ] && zcat $prefix.oph.fq.gz \\\n"; + $cmd .= " | $root/trimadap \\\n" if defined($opts{a}); + $cmd .= " | $root/bwa mem $bwa_opts$ARGV[0] - 2>> $prefix.log.bwamem | gzip -1 > $prefix.oph.sam.gz;\n"; + $cmd .= "rm -f $prefix.oph.fq.gz;\n"; } if ($has_hla && (!defined($opts{x}) || $opts{x} eq 'intractg')) { From ad9d12c04d4728206179f39785dfc528d667cf3a Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 17 Nov 2014 11:08:07 -0500 Subject: [PATCH 32/38] Removed a comment on hs38 lacking EBV as is pointed out by Martin Pollard --- extras/run-gen-ref | 1 - 1 file changed, 1 deletion(-) diff --git a/extras/run-gen-ref b/extras/run-gen-ref index 86317c3..3be3325 100755 --- a/extras/run-gen-ref +++ b/extras/run-gen-ref @@ -17,7 +17,6 @@ elif [ $1 == "hs38a" ]; then wget -O- $url38 | gzip -dc > $1.fa [ ! -f $1.fa.alt ] && grep _alt $root/resource-GRCh38/hs38d6.fa.alt > $1.fa.alt elif [ $1 == "hs38" ]; then - # we don't use GCA_000001405.15_GRCh38_no_alt_analysis_set.fna.gz because it lacks EBV wget -O- $url38 | gzip -dc | awk '/^>/{f=/_alt/?0:1}f' > $1.fa elif [ $1 == "hs37d5" ]; then wget -O- $url37d5 | gzip -dc > $1.fa 2>/dev/null From cee8149b12be9166a73547f9639297839b1ec05a Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 17 Nov 2014 13:20:23 -0500 Subject: [PATCH 33/38] Updated README for ALT mapping --- README-alt.md | 114 +++++++------- extras/alt-demo.graffle | 328 ++++++++++++++++++++++++++++++++++++++++ extras/alt-demo.png | Bin 0 -> 45962 bytes 3 files changed, 388 insertions(+), 54 deletions(-) create mode 100644 extras/alt-demo.graffle create mode 100644 extras/alt-demo.png diff --git a/README-alt.md b/README-alt.md index 40fb9d1..e05a225 100644 --- a/README-alt.md +++ b/README-alt.md @@ -1,57 +1,63 @@ ## Getting Started -Since version 0.7.11, BWA-MEM supports read mapping against a reference genome -with long alternative haplotypes present in separate ALT contigs. To use the -ALT-aware mode, users need to provide pairwise ALT-to-reference alignment in the -SAM format and rename the file to "*idxbase*.alt". For GRCh38, this alignment -is available from the [binary package of BWA][res]. - ```sh -# Generate the GRCh38+ALT+decoy+HLA and create the BWA index +# Download the bwa-0.7.11 binary package wget -O- http://sourceforge.net/projects/bio-bwa/files/bwakit-0.7.11_x64-linux.tar.bz2/download \ | gzip -dc | tar xf - -bwa.kit/run-gen-hs38d6 # download GRCh38 and write hs38d6.fa +# Generate the GRCh38+ALT+decoy+HLA and create the BWA index +bwa.kit/run-gen-ref hs38d6 # download GRCh38 and write hs38d6.fa bwa.kit/bwa index hs38d6.fa # create BWA index # mapping -bwa.kit/run-bwamem hs38d6.fa read1.fq read2.fq | sh +bwa.kit/run-bwamem -o out hs38d6.fa read1.fq read2.fq | sh # skip "|sh" to show command lines ``` -In the final alignment, a read may be placed on the [primary assembly][grcdef] -and multiple overlapping ALT contigs at the same time (on multiple SAM lines). -Mapping quality (mapQ) is properly adjusted by the postprocessing script -`bwa-postalt.js` using the ALT-to-reference alignment `hs38a.fa.alt`. For -details, see the [Methods section](#methods). +This will generate the following files: + +* `out.aln.sam.gz`: unsorted alignments with ALT-aware mapping quality. In this + file, one read may be placed on multiple overlapping ALT contigs at the same + time even if the read is mapped better to some contigs than others. This makes + it possible to analyze each contig independent of others. + +* `out.hla.top`: best genotypes for HLA-A, -B, -C, -DQA1, -DQB1 and -DRB1 genes. + +* `out.hla.all`: other possible genotypes on the six HLA genes. + +* `out.log.*`: bwa-mem, samblaster and HLA typing log files. + +Note that `run-bwamem` only prints command lines but doesn't execute them. It +is advised to have a look at the command lines before passing them to `sh` for +actual execution. ## Background GRCh38 consists of several components: chromosomal assembly, unlocalized contigs (chromosome known but location unknown), unplaced contigs (chromosome unknown) and ALT contigs (long clustered variations). The combination of the first three -components is called the *primary assembly*. You can find the more exact -definitions from the [GRC website][grcdef]. +components is called the *primary assembly*. It is recommended to use the +complete primary assembly for all analyses. Using ALT contigs in read mapping is +tricky. -GRCh38 ALT contigs are totaled 109Mb in length, spanning 60Mbp genomic regions. -However, sequences that are highly diverged from the primary assembly only -contribute a few million bp. Most subsequences of ALT contigs are nearly +GRCh38 ALT contigs are totaled 109Mb in length, spanning 60Mbp of the primary +assembly. However, sequences that are highly diverged from the primary assembly +only contribute a few million bp. Most subsequences of ALT contigs are nearly identical to the primary assembly. If we align sequence reads to GRCh38+ALT -treating ALT equal to the primary assembly, we will get many reads with zero -mapping quality and lose variants on them. It is crucial to make the mapper -aware of ALTs. +blindly, we will get many additional reads with zero mapping quality and miss +variants on them. It is crucial to make mappers aware of ALTs. -BWA-MEM is designed to minimize the interference of ALT contigs such that on the -primary assembly, the ALT-aware alignment is highly similar to the alignment -without using ALT contigs in the index. This design choice makes it almost -always safe to map reads to GRCh38+ALT. Although we don't know yet how much -variations on ALT contigs contribute to phenotypes, we would not get the answer -without mapping large cohorts to these extra sequences. We hope our current -implementation encourages researchers to use ALT contigs soon and often. +BWA-MEM is ALT-aware. It essentially computes mapping quality across the +non-redundant content of the primary assembly plus the ALT contigs and is free +of the problem above. ## Methods ### Sequence alignment As of now, ALT mapping is done in two separate steps: BWA-MEM mapping and -postprocessing. +postprocessing. The `bwa.kit/run-bwamem` script performs the two steps when ALT +contigs are present. The following picture shows an example about how BWA-MEM +infers mapping quality and reports alignment after step 2: + +![](https://raw.githubusercontent.com/lh3/bwa/dev/extras/alt-demo.png) #### Step 1: BWA-MEM mapping @@ -65,11 +71,11 @@ alignments and assigns mapQ following these two rules: 2. If there are no non-ALT hits, the best ALT hit is outputted as the primary alignment. If there are both ALT and non-ALT hits, non-ALT hits will be - primary. ALT hits are reported as supplementary alignments (flag 0x800) only - if they are better than all overlapping non-ALT hits. + primary and ALT hits be supplementary (SAM flag 0x800) if ALT hits are better + than the best overlapping non-ALT hits. In theory, non-ALT alignments from step 1 should be identical to alignments -against a reference genome with ALT contigs. In practice, the two types of +against the reference genome with ALT contigs. In practice, the two types of alignments may differ in rare cases due to seeding heuristics. When an ALT hit is significantly better than non-ALT hits, BWA-MEM may miss seeds on the non-ALT hits. @@ -102,32 +108,32 @@ CHM1 short reads and present also in NA12878. You can try [BLAT][blat] or For a more complete reference genome, we compiled a new set of decoy sequences from GenBank clones and the de novo assembly of 254 public [SGDP][sgdp] samples. -The sequences are included in `hs38d4-extra.fa` from the [BWA resource bundle -for GRCh38][res]. +The sequences are included in `hs38d6-extra.fa` from the [BWA binary +package][res]. In addition to decoy, we also put multiple alleles of HLA genes in -`hs38d4-extra.fa`. These genomic sequences were acquired from [IMGT/HLA][hladb], -version 3.18.0. Script `bwa-postalt.js` also helps to genotype HLA genes, though -not to high resolution for now. +`hs38d6-extra.fa`. These genomic sequences were acquired from [IMGT/HLA][hladb], +version 3.18.0 and are used to collect reads sequenced from these genes. -### More on HLA typing +### HLA typing -It is [well known][hlalink] that HLA genes are associated with many autoimmunity -infectious diseases and drug responses. However, many HLA alleles are highly -diverged from the reference genome. If we map whole-genome shotgun (WGS) reads -to the reference only, many allele-informative will get lost. As a result, the -vast majority of WGS projects have ignored these important genes. +HLA genes are known to be associated with many autoimmune diseases, infectious +diseases and drug responses. They are among the most important genes but are +rarely studied by WGS projects due to the high sequence divergence between +HLA genes and the reference genome in these regions. -We recommend to include the genomic regions of classical HLA genes in the BWA -index. This way we will be able to get a more complete collection of reads -mapped to HLA. We can then isolate these reads with little computational cost -and type HLA genes with another program, such as [Warren et al (2012)][hla4], -[Liu et al (2013)][hla2], [Bai et al (2014)][hla3], [Dilthey et al (2014)][hla1] -or others from [this list][hlatools]. - -### Evaluating ALT Mapping - -(Coming soon...) +By including the HLA gene regions in the reference assembly as ALT contigs, we +are able to effectively identify reads coming from these genes. We also provide +a pipeline, which is included in the [BWA binary package][res], to type the +several classic HLA genes. The pipeline is conceptually simple. It de novo +assembles sequence reads mapped to each gene, aligns exon sequences of each +allele to the assembled contigs and then finds the pairs of alleles that best +explain the contigs. In practice, however, the completeness of IMGT/HLA and +copy-number changes related to these genes are not so straightforward to +resolve. HLA typing may not always be successful. Users may also consider to use +other programs for typing such as [Warren et al (2012)][hla4], [Liu et al +(2013)][hla2], [Bai et al (2014)][hla3] and [Dilthey et al (2014)][hla1], though +most of them are distributed under restrictive licenses. ## Problems and Future Development diff --git a/extras/alt-demo.graffle b/extras/alt-demo.graffle new file mode 100644 index 0000000..ff47a30 --- /dev/null +++ b/extras/alt-demo.graffle @@ -0,0 +1,328 @@ + + + + + ActiveLayerIndex + 0 + ApplicationVersion + + com.omnigroup.OmniGraffle + 139.18.0.187838 + + AutoAdjust + + BackgroundGraphic + + Bounds + {{0, 0}, {576, 733}} + Class + SolidGraphic + ID + 2 + Style + + shadow + + Draws + NO + + stroke + + Draws + NO + + + + BaseZoom + 0 + CanvasOrigin + {0, 0} + ColumnAlign + 1 + ColumnSpacing + 36 + CreationDate + 2014-11-17 16:51:42 +0000 + Creator + Heng Li + DisplayScale + 1 0/72 in = 1 0/72 in + GraphDocumentVersion + 8 + GraphicsList + + + Bounds + {{35.699992179870605, 151.89999580383301}, {476, 224}} + Class + ShapedGraphic + FitText + YES + Flow + Resize + FontInfo + + Font + AndaleMono + Size + 12 + + ID + 28 + Shape + Rectangle + Style + + fill + + Draws + NO + + shadow + + Draws + NO + + stroke + + Draws + NO + + + Text + + Align + 0 + Pad + 0 + Text + {\rtf1\ansi\ansicpg1252\cocoartf1265\cocoasubrtf210 +\cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 Consolas;\f1\fnil\fcharset0 Consolas-Bold;} +{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red127\green127\blue127;\red255\green0\blue0; +\red204\green204\blue204;\red0\green0\blue255;\red0\green128\blue0;\red255\green128\blue0;} +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural + +\f0\fs24 \cf2 Read: A\cf0 TCAGCATC\ +\cf2 \ + ALT ctg 1: \cf3 TGA\cf3 AA---CGAATGCAAATCA +\f1\b \cf4 ATCAGCATC +\f0\b0 \cf3 GAACTAGTCACAT\cf2 \ + \cf3 |||||\cf5 (high div) \cf3 |||\cf5 (novel ins)\cf3 ||||||||||\cf2 \ +Chromosome:\cf3 GCGTACATGATACGA +\f1\b \cf6 ATCgGCATC +\f0\b0 \cf3 ATC-------------CTAGTCACATCGTAATCGA\ +\cf2 \cf3 |||||||||||| |||||||\cf5 (novel ins) \cf3 ||||||||||\ +\cf2 ALT ctg 2:\cf3 TGATACGA +\f1\b \cf7 ATCgcCATC +\f0\b0 \cf3 ATCA +\f1\b \cf8 ATCgcCAgC +\f0\b0 \cf3 GAACTAGTCACAT\ +\ +\cf2 4 potential hits: +\f1\b \cf4 ATCAGCATC +\f0\b0 \cf0 > +\f1\b \cf6 ATCgGCATC +\f0\b0 \cf0 > +\f1\b \cf7 ATCgcCATC +\f0\b0 \cf2 > +\f1\b \cf8 ATCgcCAgC\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural + +\f0\b0 \cf0 2 hit groups: \{ +\f1\b \cf4 ATCAGCATC +\f0\b0 \cf0 , +\f1\b \cf8 ATCgcCAgC +\f0\b0 \cf2 \} and\cf0 \{ +\f1\b \cf6 ATCgGCATC +\f0\b0 \cf2 , +\f1\b \cf7 ATCgcCATC +\f0\b0 \cf2 \}\ +\cf0 Hits considered in mapQ: +\f1\b \cf4 ATCAGCATC +\f0\b0 \cf0 and +\f1\b \cf6 ATCgGCATC\ +\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural + +\f0\b0 \cf3 \ +\cf2 In the output SAM: +\f1\b \cf6 ATCgGCATC +\f0\b0 \cf2 as the primary SAM line with mapQ=0\ + +\f1\b \cf4 ATCAGCATC +\f0\b0 \cf2 as a supplementary line with mapQ>0\ + +\f1\b \cf8 ATCgcCAgC +\f0\b0 \cf2 as a supplementary line with mapQ>0\ + +\f1\b \cf7 ATCgcCATC +\f0\b0 \cf2 in an XA tag, not as a separate line} + VerticalPad + 0 + + Wrap + NO + + + GridInfo + + GridSpacing + 7.1999998092651367 + MajorGridSpacing + 10 + SnapsToGrid + YES + + GuidesLocked + NO + GuidesVisible + YES + HPages + 1 + ImageCounter + 1 + KeepToScale + + Layers + + + Lock + NO + Name + Layer 1 + Print + YES + View + YES + + + LayoutInfo + + Animate + NO + circoMinDist + 18 + circoSeparation + 0.0 + layoutEngine + dot + neatoSeparation + 0.0 + twopiSeparation + 0.0 + + LinksVisible + NO + MagnetsVisible + NO + MasterSheets + + ModificationDate + 2014-11-17 18:01:49 +0000 + Modifier + Heng Li + NotesVisible + NO + Orientation + 2 + OriginVisible + NO + PageBreaks + YES + PrintInfo + + NSBottomMargin + + float + 41 + + NSHorizonalPagination + + coded + BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG + + NSLeftMargin + + float + 18 + + NSPaperSize + + size + {612, 792} + + NSPrintReverseOrientation + + int + 0 + + NSRightMargin + + float + 18 + + NSTopMargin + + float + 18 + + + PrintOnePage + + ReadOnly + NO + RowAlign + 1 + RowSpacing + 36 + SheetTitle + Canvas 1 + SmartAlignmentGuidesActive + YES + SmartDistanceGuidesActive + YES + UniqueID + 1 + UseEntirePage + + VPages + 1 + WindowInfo + + CurrentSheet + 0 + ExpandedCanvases + + + name + Canvas 1 + + + Frame + {{367, 6}, {710, 872}} + ListView + + OutlineWidth + 142 + RightSidebar + + ShowRuler + + Sidebar + + SidebarWidth + 120 + VisibleRegion + {{0, 0}, {575, 733}} + Zoom + 1 + ZoomValues + + + Canvas 1 + 1 + 1 + + + + + diff --git a/extras/alt-demo.png b/extras/alt-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..efd247c0a7120739aa0ca81b692c3d65d994861d GIT binary patch literal 45962 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 Date: Mon, 17 Nov 2014 14:54:24 -0500 Subject: [PATCH 34/38] added an evaluation section --- README-alt.md | 25 +++++++++++++++++++++++++ extras/alt-demo.graffle | 16 +++++++++------- extras/alt-demo.png | Bin 45962 -> 47673 bytes 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/README-alt.md b/README-alt.md index e05a225..14c5d58 100644 --- a/README-alt.md +++ b/README-alt.md @@ -135,6 +135,31 @@ other programs for typing such as [Warren et al (2012)][hla4], [Liu et al (2013)][hla2], [Bai et al (2014)][hla3] and [Dilthey et al (2014)][hla1], though most of them are distributed under restrictive licenses. +## Preliminary Evaluation + +To check whether GRCh38 is better than GRCh37, we mapped the CHM1 and NA12878 +unitigs to GRCh37 primary (hs37), GRCh38 primary (hs38) and GRCh38+ALT+decoy +(hs38d6), and called small variants from the alignment. CHM1 is haploid. +Ideally, heterozygous calls are false positives (FP). NA12878 is diploid. The +true positive (TP) heterozygous calls from NA12878 are approximately equal +to the difference between NA12878 and CHM1 heterozygous calls. A better assembly +should yield higher TP and lower FP. The following table shows the numbers for +these assemblies: + +|Assembly|hs37 |hs38 |hs38d6|CHM1_1.1| huref| +|:------:|------:|------:|------:|------:|------:| +|FP | 255706| 168068| 142516|307172 | 575634| +|TP |2142260|2163113|2150844|2167235|2137053| + +With this measurement, hs38 is clearly better than hs37. Genome hs38d6 reduces +FP by ~25k but also reduces TP by ~12k. We manually inspected variants called +from hs38 only and found the majority of them are associated with excessive read +depth, clustered variants or weak alignment. We believe most hs38-only calls are +problematic. In addition, if we compare two NA12878 replicates from HiSeq X10 +with nearly identical library construction, the difference is ~140k, an order +of magnitude higher than the difference between hs38 and hs38d6. ALT contigs, +decoy and HLA genes in hs38d6 improve variant calling at little cost. + ## Problems and Future Development There are some uncertainties about ALT mappings - we are not sure whether they diff --git a/extras/alt-demo.graffle b/extras/alt-demo.graffle index ff47a30..32a8f5f 100644 --- a/extras/alt-demo.graffle +++ b/extras/alt-demo.graffle @@ -104,17 +104,17 @@ \f0\fs24 \cf2 Read: A\cf0 TCAGCATC\ \cf2 \ - ALT ctg 1: \cf3 TGA\cf3 AA---CGAATGCAAATCA + ALT ctg 1: \cf3 TGA\cf3 AA---CGAATGCAAATGGTCA \f1\b \cf4 ATCAGCATC \f0\b0 \cf3 GAACTAGTCACAT\cf2 \ - \cf3 |||||\cf5 (high div) \cf3 |||\cf5 (novel ins)\cf3 ||||||||||\cf2 \ + \cf3 |||||\cf5 (high div) \cf3 ||||||\cf5 (novel ins)\cf3 ||||||||||\cf2 \ Chromosome:\cf3 GCGTACATGATACGA \f1\b \cf6 ATCgGCATC -\f0\b0 \cf3 ATC-------------CTAGTCACATCGTAATCGA\ -\cf2 \cf3 |||||||||||| |||||||\cf5 (novel ins) \cf3 ||||||||||\ +\f0\b0 \cf3 ATGGTC-------------CTAGTCACATCGTAATC\ +\cf2 \cf3 |||||||||||| ||||||||||\cf5 (novel ins) \cf3 ||||||||||\ \cf2 ALT ctg 2:\cf3 TGATACGA \f1\b \cf7 ATCgcCATC -\f0\b0 \cf3 ATCA +\f0\b0 \cf3 ATGGTCA \f1\b \cf8 ATCgcCAgC \f0\b0 \cf3 GAACTAGTCACAT\ \ @@ -140,7 +140,9 @@ Chromosome:\cf3 GCGTACATGATACGA \cf0 Hits considered in mapQ: \f1\b \cf4 ATCAGCATC \f0\b0 \cf0 and -\f1\b \cf6 ATCgGCATC\ +\f1\b \cf6 ATCgGCATC +\f0\b0 \cf2 (best from each group) +\f1\b \cf6 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b0 \cf3 \ @@ -217,7 +219,7 @@ Chromosome:\cf3 GCGTACATGATACGA MasterSheets ModificationDate - 2014-11-17 18:01:49 +0000 + 2014-11-17 18:28:10 +0000 Modifier Heng Li NotesVisible diff --git a/extras/alt-demo.png b/extras/alt-demo.png index efd247c0a7120739aa0ca81b692c3d65d994861d..71f497637168a0ba22f37c71c9422ce26fb91578 100644 GIT binary patch delta 44837 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 delta 43123 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 G;Qs+UQzl~o From 6f9bf9a4ae9bcaf42ce31c3382626afba4ea2707 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Mon, 17 Nov 2014 15:23:38 -0500 Subject: [PATCH 35/38] minor change --- README-alt.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README-alt.md b/README-alt.md index 14c5d58..6476c63 100644 --- a/README-alt.md +++ b/README-alt.md @@ -158,7 +158,8 @@ depth, clustered variants or weak alignment. We believe most hs38-only calls are problematic. In addition, if we compare two NA12878 replicates from HiSeq X10 with nearly identical library construction, the difference is ~140k, an order of magnitude higher than the difference between hs38 and hs38d6. ALT contigs, -decoy and HLA genes in hs38d6 improve variant calling at little cost. +decoy and HLA genes in hs38d6 improve variant calling and enable the analyses of +ALT contigs and HLA typing at little cost. ## Problems and Future Development From 80e4ecfa79cbc433420729096ad3595bd596266b Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 18 Nov 2014 14:30:22 -0500 Subject: [PATCH 36/38] r998: smart pairing; allow mixture of SE/PE reads --- NEWS.md | 38 ++++++++++++-------------------- bwa.c | 21 ++++++++++++++++++ bwa.h | 3 ++- bwamem.h | 1 + extras/run-bwamem | 55 ++++++++++++++++++++++------------------------- fastmap.c | 33 ++++++++++++++++++++-------- main.c | 2 +- 7 files changed, 89 insertions(+), 64 deletions(-) diff --git a/NEWS.md b/NEWS.md index 3d8dec9..c624481 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,29 +1,17 @@ -Release 0.7.11 (XX September, 2014) +Release 0.7.11 (XX November, 2014) ----------------------------------- -A major change to BWA-MEM is the support of mapping to ALT contigs. To use this -feature, users need to manually create a file `indexbase.alt` with each line -giving the name of an ALT contig. During alignment, BWA-MEM will be able to -classify potential hits to ALT and non-ALT hits. It reports alignments and -assigns mapping quality (mapQ) loosely following these rules: +A major change to BWA-MEM is the support of mapping to ALT contigs in addition +to the primary assembly. Part of the ALT mapping strategy is implemented in +BWA-MEM and the rest in a postprocessing script for now. Due to the extra +layer of complexity on generating the reference genome and on the two-step +mapping, we start to provide a wrapper script and precompiled binaries since +this release. Please check README-alt.md for details. - 1. The original mapQ of a non-ALT hit is computed across non-ALT hits only. - The reported mapQ of an ALT hit is always computed across all hits. - - 2. An ALT hit is only reported if its score is strictly better than all - overlapping non-ALT hits. A reported ALT hit is flagged with 0x800 - (supplementary) unless there are no non-ALT hits. - - 3. The mapQ of a non-ALT hit is reduced to zero if its score is less than 80% - (controlled by option `-g`) of the score of an overlapping ALT hit. In this - case, the original mapQ is moved to the `om` tag. - -This way, non-ALT alignments are only affected by ALT contigs if there are -significantly better ALT alignments. BWA-MEM is carefully engineered such that -ALT contigs do not interfere with the alignments to the primary assembly. - -Users may consider to use ALT contigs from GRCh38. I am also constructing a -non-redundant and more complete set of sequences missing from GRCh38. +Another major addition to BWA-MEM is HLA typing, which made possible with the +new ALT mapping strategy. Necessary data and programs are also included in +the binary release. The wrapper script also performs HLA typing when HLA genes +are also included in the reference genome as additional ALT contigs. Other notable changes to BWA-MEM: @@ -42,7 +30,9 @@ Other notable changes to BWA-MEM: * Added new pre-setting for Oxford Nanopore 2D reads. For small genomes, though, LAST is still more sensitive. -(0.7.11: XX September 2014, rXXX) + * Added LAST-like seeding. This improves the accuracy for longer reads. + +(0.7.11: XX November 2014, rXXX) diff --git a/bwa.c b/bwa.c index 80a4b8f..053025e 100644 --- a/bwa.c +++ b/bwa.c @@ -7,6 +7,7 @@ #include "ksw.h" #include "utils.h" #include "kstring.h" +#include "kvec.h" #ifdef USE_MALLOC_WRAPPERS # include "malloc_wrap.h" @@ -55,10 +56,12 @@ bseq1_t *bseq_read(int chunk_size, int *n_, void *ks1_, void *ks2_) } trim_readno(&ks->name); kseq2bseq1(ks, &seqs[n]); + seqs[n].id = n; size += seqs[n++].l_seq; if (ks2) { trim_readno(&ks2->name); kseq2bseq1(ks2, &seqs[n]); + seqs[n].id = n; size += seqs[n++].l_seq; } if (size >= chunk_size && (n&1) == 0) break; @@ -71,6 +74,24 @@ bseq1_t *bseq_read(int chunk_size, int *n_, void *ks1_, void *ks2_) return seqs; } +void bseq_classify(int n, bseq1_t *seqs, int m[2], bseq1_t *sep[2]) +{ + int i, has_last; + kvec_t(bseq1_t) a[2] = {{0,0,0}, {0,0,0}}; + for (i = 1, has_last = 1; i < n; ++i) { + if (has_last) { + if (strcmp(seqs[i].name, seqs[i-1].name) == 0) { + kv_push(bseq1_t, a[1], seqs[i-1]); + kv_push(bseq1_t, a[1], seqs[i]); + has_last = 0; + } else kv_push(bseq1_t, a[0], seqs[i-1]); + } else has_last = 1; + } + if (has_last) kv_push(bseq1_t, a[0], seqs[i-1]); + sep[0] = a[0].a, m[0] = a[0].n; + sep[1] = a[1].a, m[1] = a[1].n; +} + /***************** * CIGAR related * *****************/ diff --git a/bwa.h b/bwa.h index 6fcc82e..830bc3c 100644 --- a/bwa.h +++ b/bwa.h @@ -23,7 +23,7 @@ typedef struct { } bwaidx_t; typedef struct { - int l_seq; + int l_seq, id; char *name, *comment, *seq, *qual, *sam; } bseq1_t; @@ -35,6 +35,7 @@ extern "C" { #endif bseq1_t *bseq_read(int chunk_size, int *n_, void *ks1_, void *ks2_); + void bseq_classify(int n, bseq1_t *seqs, int m[2], bseq1_t *sep[2]); void bwa_fill_scmat(int a, int b, int8_t mat[25]); uint32_t *bwa_gen_cigar(const int8_t mat[25], int q, int r, int w_, int64_t l_pac, const uint8_t *pac, int l_query, uint8_t *query, int64_t rb, int64_t re, int *score, int *n_cigar, int *NM); diff --git a/bwamem.h b/bwamem.h index 1746ab5..8ffe729 100644 --- a/bwamem.h +++ b/bwamem.h @@ -20,6 +20,7 @@ typedef struct __smem_i smem_i; #define MEM_F_ALN_REG 0x80 #define MEM_F_REF_HDR 0x100 #define MEM_F_SOFTCLIP 0x200 +#define MEM_F_SMARTPE 0x400 typedef struct { int a, b; // match score and mismatch penalty diff --git a/extras/run-bwamem b/extras/run-bwamem index 40724bb..dba8f72 100755 --- a/extras/run-bwamem +++ b/extras/run-bwamem @@ -5,7 +5,7 @@ use warnings; use Getopt::Std; my %opts = (t=>1, n=>64); -getopts("Spadsko:R:x:t:", \%opts); +getopts("Sadsko:R:x:t:", \%opts); die(' Usage: run-bwamem [options] [file2] @@ -17,30 +17,36 @@ Options: -o STR prefix for output files [inferred from pacbio: pacbio subreads (~10kb query, high error rate) ont2d: Oxford Nanopore reads (~10kb query, higher error rate) -t INT number of threads [1] - -p input are paired-end reads if file2 absent -a trim HiSeq2000/2500 PE resequencing adapters (via trimadap) -d mark duplicate (via samblaster) -S for SAM/BAM input, don\'t shuffle - -s sort the output alignment (higher RAM footprint) + -s sort the output alignment (requring more RAM) -k keep temporary files generated by typeHLA -Note: File type is determined by the file extension of the first input sequence file. - Recognized file extensions: fasta, fa, fastq, fq, fasta.gz, fa.gz, fastq.gz, - fq.gz, sam, sam.gz and bam. SAM/BAM input will be converted to FASTQ. +Examples: - When option -d is in use, all the input reads are required to come from the same - library and all the reads from the library shall be included in the input. In - addition, samblaster does not distinguish optical and PCR duplicates. + * Map paired-end reads to GRCh38+ALT+decoy+HLA and perform HLA typing: - The output may consist of the following files: + run-bwamem -o prefix -t8 -R"@RG\tID:foo\tSM:bar" hs38d6.fa read1.fq.gz read2.fq.gz - {-o}.aln.sam.gz - unsorted alignment (unless -s is specified) - {-o}.aln.bam - sorted alignment (if -s is specified) - {-o}.oph.sam.gz - orphan single-end reads mapping (if input is paired SAM/BAM) - {-o}.hla.top - best HLA typing for the six classical HLA genes - {-o}.hla.all - additional HLA typing - {-o}.log.* - log files + * Remap coordinate-sorted BAM, trim Illumina PE adapters and sort the output. The BAM + may contain single-end or paired-end reads, or a mixture of the two types. The read + groups are not transferred to the output BAM. + + run-bwamem -sao prefix hs38d6.fa old-srt.bam + + * Remap name-grouped BAM and mark duplicates. Note that in this case, all reads from + a single library should be aligned at the same time. Paired-end only. + + run-bwamem -Sdo prefix hs38d6.fa old-unsrt.bam + +Output files: + + {-o}.aln.bam - final alignment + {-o}.hla.top - best genotypes for the 6 classical HLA genes (if there are HLA-* contigs) + {-o}.hla.all - additional HLA genotypes consistent with data + {-o}.log.* - log files ') if @ARGV < 2; @@ -103,8 +109,7 @@ if ($is_sam || $is_bam) { my $cmd_sam2bam = $is_sam? "$root/htsbox samview -uS $ARGV[1] \\\n" : "cat $ARGV[1] \\\n"; my $ntmps = int($size / 4e9) + 1; my $cmd_shuf = ($is_bam || $is_sam) && !defined($opts{S})? " | $root/htsbox bamshuf -uOn$ntmps - $prefix.shuf \\\n" : ""; - my $cmd_bam2fq = ""; - $cmd_bam2fq = " | $root/htsbox bam2fq -O " . (defined($opts{p})? "-s $prefix.oph.fq.gz " : "") . "- \\\n"; + my $cmd_bam2fq = " | $root/htsbox bam2fq -O - \\\n"; $cmd = $cmd_sam2bam . $cmd_shuf . $cmd_bam2fq; } elsif (@ARGV >= 3) { $cmd = "$root/seqtk mergepe $ARGV[1] $ARGV[2] \\\n"; @@ -112,10 +117,10 @@ if ($is_sam || $is_bam) { $cmd = "cat $ARGV[1] \\\n"; } -my $bwa_opts = ($opts{t} > 1? "-t$opts{t} " : "") . (defined($opts{x})? "-x $opts{x} " : "") . (defined($opts{R})? "-R'$opts{R}' " : ""); +my $bwa_opts = "-p " . ($opts{t} > 1? "-t$opts{t} " : "") . (defined($opts{x})? "-x $opts{x} " : "") . (defined($opts{R})? "-R'$opts{R}' " : ""); $cmd .= " | $root/trimadap \\\n" if defined($opts{a}); -$cmd .= " | $root/bwa mem $bwa_opts" . ($is_pe? "-p " : "") . "$ARGV[0] - 2> $prefix.log.bwamem \\\n"; +$cmd .= " | $root/bwa mem $bwa_opts$ARGV[0] - 2> $prefix.log.bwamem \\\n"; $cmd .= " | $root/samblaster 2> $prefix.log.dedup \\\n" if defined($opts{d}); my $has_hla = 0; @@ -131,15 +136,7 @@ if (-f "$ARGV[0].alt") { } my $t_sort = $opts{t} < 4? $opts{t} : 4; -$cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.aln;\n" : " | gzip -1 > $prefix.aln.sam.gz;\n"; - -# map single end reads -if ($cmd =~ /$prefix.oph.fq.gz/) { - $cmd .= "[ -s $prefix.oph.fq.gz ] && zcat $prefix.oph.fq.gz \\\n"; - $cmd .= " | $root/trimadap \\\n" if defined($opts{a}); - $cmd .= " | $root/bwa mem $bwa_opts$ARGV[0] - 2>> $prefix.log.bwamem | gzip -1 > $prefix.oph.sam.gz;\n"; - $cmd .= "rm -f $prefix.oph.fq.gz;\n"; -} +$cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.aln;\n" : " | $root/samtools view -1 - > $prefix.aln.bam;\n"; if ($has_hla && (!defined($opts{x}) || $opts{x} eq 'intractg')) { $cmd .= "$root/run-HLA ". ($opts{x} eq 'intractg'? "-A " : "") . "$prefix.hla > $prefix.hla.top 2> $prefix.log.hla;\n"; diff --git a/fastmap.c b/fastmap.c index 25bf822..988b6ed 100644 --- a/fastmap.c +++ b/fastmap.c @@ -66,7 +66,7 @@ int main_mem(int argc, char *argv[]) else if (c == 't') opt->n_threads = atoi(optarg), opt->n_threads = opt->n_threads > 1? opt->n_threads : 1; else if (c == 'P') opt->flag |= MEM_F_NOPAIRING; else if (c == 'a') opt->flag |= MEM_F_ALL; - else if (c == 'p') opt->flag |= MEM_F_PE; + else if (c == 'p') opt->flag |= MEM_F_PE | MEM_F_SMARTPE; else if (c == 'M') opt->flag |= MEM_F_NO_MULTI; else if (c == 'S') opt->flag |= MEM_F_NO_RESCUE; else if (c == 'e') opt->flag |= MEM_F_SELF_OVLP; @@ -167,7 +167,7 @@ int main_mem(int argc, char *argv[]) fprintf(stderr, " intractg: -B9 -O16 -L5 (intra-species contigs to ref)\n"); // fprintf(stderr, " pbread: -k13 -W40 -c1000 -r10 -A1 -B1 -O1 -E1 -N25 -FeaD.001\n"); fprintf(stderr, "\nInput/output options:\n\n"); - fprintf(stderr, " -p first query file consists of interleaved paired-end sequences\n"); + fprintf(stderr, " -p smart pairing (ignoring in2.fq)\n"); fprintf(stderr, " -R STR read group header line such as '@RG\\tID:foo\\tSM:bar' [null]\n"); fprintf(stderr, " -j ignore ALT contigs\n"); fprintf(stderr, "\n"); @@ -248,7 +248,7 @@ int main_mem(int argc, char *argv[]) if (optind + 2 < argc) { if (opt->flag&MEM_F_PE) { if (bwa_verbose >= 2) - fprintf(stderr, "[W::%s] when '-p' is in use, the second query file will be ignored.\n", __func__); + fprintf(stderr, "[W::%s] when '-p' is in use, the second query file is ignored.\n", __func__); } else { ko2 = kopen(argv[optind + 2], &fd2); if (ko2 == 0) { @@ -265,11 +265,6 @@ int main_mem(int argc, char *argv[]) actual_chunk_size = fixed_chunk_size > 0? fixed_chunk_size : opt->chunk_size * opt->n_threads; while ((seqs = bseq_read(actual_chunk_size, &n, ks, ks2)) != 0) { int64_t size = 0; - if ((opt->flag & MEM_F_PE) && (n&1) == 1) { - if (bwa_verbose >= 2) - fprintf(stderr, "[W::%s] odd number of reads in the PE mode; last read dropped\n", __func__); - n = n>>1<<1; - } if (!copy_comment) for (i = 0; i < n; ++i) { free(seqs[i].comment); seqs[i].comment = 0; @@ -277,7 +272,27 @@ int main_mem(int argc, char *argv[]) for (i = 0; i < n; ++i) size += seqs[i].l_seq; if (bwa_verbose >= 3) fprintf(stderr, "[M::%s] read %d sequences (%ld bp)...\n", __func__, n, (long)size); - mem_process_seqs(opt, idx->bwt, idx->bns, idx->pac, n_processed, n, seqs, pes0); + if (opt->flag & MEM_F_SMARTPE) { + bseq1_t *sep[2]; + int i, n_sep[2]; + mem_opt_t tmp_opt = *opt; + bseq_classify(n, seqs, n_sep, sep); + if (bwa_verbose >= 3) + fprintf(stderr, "[M::%s] %d single-end sequences; %d paired-end sequences\n", __func__, n_sep[0], n_sep[1]); + if (n_sep[0]) { + tmp_opt.flag &= ~MEM_F_PE; + mem_process_seqs(&tmp_opt, idx->bwt, idx->bns, idx->pac, n_processed, n_sep[0], sep[0], 0); + for (i = 0; i < n_sep[0]; ++i) + seqs[sep[0][i].id].sam = sep[0][i].sam; + } + if (n_sep[1]) { + tmp_opt.flag |= MEM_F_PE; + mem_process_seqs(&tmp_opt, idx->bwt, idx->bns, idx->pac, n_processed + n_sep[0], n_sep[1], sep[1], pes0); + for (i = 0; i < n_sep[1]; ++i) + seqs[sep[1][i].id].sam = sep[1][i].sam; + } + free(sep[0]); free(sep[1]); + } else mem_process_seqs(opt, idx->bwt, idx->bns, idx->pac, n_processed, n, seqs, pes0); n_processed += n; for (i = 0; i < n; ++i) { if (seqs[i].sam) err_fputs(seqs[i].sam, stdout); diff --git a/main.c b/main.c index 33e27c2..5a2de61 100644 --- a/main.c +++ b/main.c @@ -4,7 +4,7 @@ #include "utils.h" #ifndef PACKAGE_VERSION -#define PACKAGE_VERSION "0.7.10-r984-dirty" +#define PACKAGE_VERSION "0.7.10-r998-dirty" #endif int bwa_fa2pac(int argc, char *argv[]); From 61ab3296394612246ecf7866c4c50f87605b9415 Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 18 Nov 2014 15:40:58 -0500 Subject: [PATCH 37/38] fine tune the wrapper scripts --- NEWS.md | 6 +++--- bwa.1 | 7 ++++--- extras/run-bwamem | 17 +++++++++-------- extras/typeHLA.sh | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/NEWS.md b/NEWS.md index c624481..4cf69ab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,9 +9,9 @@ mapping, we start to provide a wrapper script and precompiled binaries since this release. Please check README-alt.md for details. Another major addition to BWA-MEM is HLA typing, which made possible with the -new ALT mapping strategy. Necessary data and programs are also included in -the binary release. The wrapper script also performs HLA typing when HLA genes -are also included in the reference genome as additional ALT contigs. +new ALT mapping strategy. Necessary data and programs are included in the +binary release. The wrapper script also performs HLA typing when HLA genes are +also included in the reference genome as additional ALT contigs. Other notable changes to BWA-MEM: diff --git a/bwa.1 b/bwa.1 index ba8bc9e..0d556be 100644 --- a/bwa.1 +++ b/bwa.1 @@ -1,4 +1,4 @@ -.TH bwa 1 "19 May 2014" "bwa-0.7.9-r783" "Bioinformatics tools" +.TH bwa 1 "18 November 2014" "bwa-0.7.11-r999" "Bioinformatics tools" .SH NAME .PP bwa - Burrows-Wheeler Alignment Tool @@ -233,8 +233,9 @@ more aggressive read pair. [17] .B INPUT/OUTPUT OPTIONS: .TP .B -p -Assume the first input query file is interleaved paired-end FASTA/Q. See the -command description for details. +Smart pairing. If two adjacent reads have the same name, they are considered +to form a read pair. This way, paired-end and single-end reads can be mixed +in a single FASTA/Q stream. .TP .BI -R \ STR Complete read group header line. '\\t' can be used in diff --git a/extras/run-bwamem b/extras/run-bwamem index dba8f72..9d5fa19 100755 --- a/extras/run-bwamem +++ b/extras/run-bwamem @@ -4,8 +4,8 @@ use strict; use warnings; use Getopt::Std; -my %opts = (t=>1, n=>64); -getopts("Sadsko:R:x:t:", \%opts); +my %opts = (t=>1); +getopts("SadskHo:R:x:t:", \%opts); die(' Usage: run-bwamem [options] [file2] @@ -22,6 +22,7 @@ Options: -o STR prefix for output files [inferred from -d mark duplicate (via samblaster) -S for SAM/BAM input, don\'t shuffle -s sort the output alignment (requring more RAM) + -H skip HLA typing -k keep temporary files generated by typeHLA Examples: @@ -31,15 +32,15 @@ Examples: run-bwamem -o prefix -t8 -R"@RG\tID:foo\tSM:bar" hs38d6.fa read1.fq.gz read2.fq.gz * Remap coordinate-sorted BAM, trim Illumina PE adapters and sort the output. The BAM - may contain single-end or paired-end reads, or a mixture of the two types. The read - groups are not transferred to the output BAM. + may contain single-end or paired-end reads, or a mixture of the two types. Read groups + are not transferred to the output BAM, though. run-bwamem -sao prefix hs38d6.fa old-srt.bam * Remap name-grouped BAM and mark duplicates. Note that in this case, all reads from a single library should be aligned at the same time. Paired-end only. - run-bwamem -Sdo prefix hs38d6.fa old-unsrt.bam + run-bwamem -Sdo prefix hs38d6.fa old-unsrt.bam Output files: @@ -119,7 +120,7 @@ if ($is_sam || $is_bam) { my $bwa_opts = "-p " . ($opts{t} > 1? "-t$opts{t} " : "") . (defined($opts{x})? "-x $opts{x} " : "") . (defined($opts{R})? "-R'$opts{R}' " : ""); -$cmd .= " | $root/trimadap \\\n" if defined($opts{a}); +$cmd .= " | $root/trimadap 2> $prefix.log.trim \\\n" if defined($opts{a}); $cmd .= " | $root/bwa mem $bwa_opts$ARGV[0] - 2> $prefix.log.bwamem \\\n"; $cmd .= " | $root/samblaster 2> $prefix.log.dedup \\\n" if defined($opts{d}); @@ -138,8 +139,8 @@ if (-f "$ARGV[0].alt") { my $t_sort = $opts{t} < 4? $opts{t} : 4; $cmd .= defined($opts{s})? " | $root/samtools sort -@ $t_sort -m1G - $prefix.aln;\n" : " | $root/samtools view -1 - > $prefix.aln.bam;\n"; -if ($has_hla && (!defined($opts{x}) || $opts{x} eq 'intractg')) { - $cmd .= "$root/run-HLA ". ($opts{x} eq 'intractg'? "-A " : "") . "$prefix.hla > $prefix.hla.top 2> $prefix.log.hla;\n"; +if ($has_hla && !defined($opts{H}) && (!defined($opts{x}) || $opts{x} eq 'intractg')) { + $cmd .= "$root/run-HLA ". (defined($opts{x}) && $opts{x} eq 'intractg'? "-A " : "") . "$prefix.hla > $prefix.hla.top 2> $prefix.log.hla;\n"; $cmd .= "cat $prefix.hla.HLA*.gt | grep ^GT | cut -f2- > $prefix.hla.all;\n"; $cmd .= "rm -f $prefix.hla.HLA*;\n" unless defined($opts{k}); } diff --git a/extras/typeHLA.sh b/extras/typeHLA.sh index 88a3728..8b40d85 100755 --- a/extras/typeHLA.sh +++ b/extras/typeHLA.sh @@ -20,7 +20,7 @@ if [ $is_ctg -eq 0 ]; then echo "** De novo assembling..." >&2 len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'` $root/fermi2.pl unitig -f $root/fermi2 -r $root/ropebwt2 -t2 -l$len -p $pre.tmp $pre.fq > $pre.tmp.mak - make -f $pre.tmp.mak + make -f $pre.tmp.mak >&2 cp $pre.tmp.mag.gz $pre.mag.gz else rm -f $pre.tmp.mag.gz From 56f1056b493fd60e1a30e2b1bfff76a1c5a5969f Mon Sep 17 00:00:00 2001 From: Heng Li Date: Tue, 18 Nov 2014 23:28:46 -0500 Subject: [PATCH 38/38] don't type empty files This causes errors. --- README-alt.md | 2 +- extras/typeHLA.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README-alt.md b/README-alt.md index 6476c63..7d3a3c7 100644 --- a/README-alt.md +++ b/README-alt.md @@ -13,7 +13,7 @@ bwa.kit/run-bwamem -o out hs38d6.fa read1.fq read2.fq | sh # skip "|sh" to show This will generate the following files: -* `out.aln.sam.gz`: unsorted alignments with ALT-aware mapping quality. In this +* `out.aln.bam`: unsorted alignments with ALT-aware mapping quality. In this file, one read may be placed on multiple overlapping ALT contigs at the same time even if the read is mapped better to some contigs than others. This makes it possible to analyze each contig independent of others. diff --git a/extras/typeHLA.sh b/extras/typeHLA.sh index 8b40d85..b5a2b4e 100755 --- a/extras/typeHLA.sh +++ b/extras/typeHLA.sh @@ -16,6 +16,11 @@ preres="resource-human-HLA" root=`dirname $0` pre=$1.$2 +if [ ! -s $pre.fq ]; then + echo '** Empty input file. Abort!' >&2 + exit 0 +fi + if [ $is_ctg -eq 0 ]; then echo "** De novo assembling..." >&2 len=`$root/seqtk comp $pre.fq | awk '{++x;y+=$2}END{printf("%.0f\n", y/x)}'`