minimap2 0.2.30.3 → 1.2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -63
  3. data/ext/minimap2/Makefile +1 -1
  4. data/ext/minimap2/format.c +12 -2
  5. data/ext/minimap2/hit.c +19 -8
  6. data/ext/minimap2/ksw2_ll_sse.c +2 -2
  7. data/ext/minimap2/minimap.h +2 -1
  8. data/ext/minimap2/pe.c +7 -2
  9. data/ext/ruby_minimap2/extconf.rb +47 -0
  10. data/ext/ruby_minimap2/native_main.c +15 -0
  11. data/ext/ruby_minimap2/ruby_minimap2.c +1134 -0
  12. data/lib/minimap2/aligner.rb +66 -374
  13. data/lib/minimap2/alignment.rb +22 -11
  14. data/lib/minimap2/version.rb +2 -1
  15. data/lib/minimap2.rb +39 -110
  16. metadata +8 -89
  17. data/ext/Rakefile +0 -60
  18. data/ext/cmappy/cmappy.c +0 -134
  19. data/ext/cmappy/cmappy.h +0 -46
  20. data/ext/minimap2/FAQ.md +0 -46
  21. data/ext/minimap2/MANIFEST.in +0 -10
  22. data/ext/minimap2/Makefile.simde +0 -97
  23. data/ext/minimap2/NEWS.md +0 -989
  24. data/ext/minimap2/README.md +0 -428
  25. data/ext/minimap2/code_of_conduct.md +0 -30
  26. data/ext/minimap2/cookbook.md +0 -243
  27. data/ext/minimap2/minimap2.1 +0 -844
  28. data/ext/minimap2/misc/README.md +0 -180
  29. data/ext/minimap2/misc/pafcluster.js +0 -241
  30. data/ext/minimap2/misc/paftools.js +0 -3734
  31. data/ext/minimap2/pyproject.toml +0 -2
  32. data/ext/minimap2/python/README.rst +0 -198
  33. data/ext/minimap2/python/cmappy.h +0 -152
  34. data/ext/minimap2/python/cmappy.pxd +0 -156
  35. data/ext/minimap2/python/mappy.pyx +0 -289
  36. data/ext/minimap2/python/minimap2.py +0 -41
  37. data/ext/minimap2/setup.py +0 -55
  38. data/ext/minimap2/test/MT-human.fa +0 -278
  39. data/ext/minimap2/test/MT-orang.fa +0 -276
  40. data/ext/minimap2/test/q-inv.fa +0 -4
  41. data/ext/minimap2/test/q2.fa +0 -2
  42. data/ext/minimap2/test/t-inv.fa +0 -127
  43. data/ext/minimap2/test/t2.fa +0 -2
  44. data/ext/minimap2/test/x3s-aln.txt +0 -5
  45. data/ext/minimap2/test/x3s-qry.fa +0 -5
  46. data/ext/minimap2/test/x3s-ref.fa +0 -10
  47. data/ext/minimap2/tex/Makefile +0 -21
  48. data/ext/minimap2/tex/bioinfo.cls +0 -930
  49. data/ext/minimap2/tex/blasr-mc.eval +0 -17
  50. data/ext/minimap2/tex/bowtie2-s3.sam.eval +0 -28
  51. data/ext/minimap2/tex/bwa-s3.sam.eval +0 -52
  52. data/ext/minimap2/tex/bwa.eval +0 -55
  53. data/ext/minimap2/tex/eval2roc.pl +0 -33
  54. data/ext/minimap2/tex/graphmap.eval +0 -4
  55. data/ext/minimap2/tex/hs38-simu.sh +0 -10
  56. data/ext/minimap2/tex/minialign.eval +0 -49
  57. data/ext/minimap2/tex/minimap2.bib +0 -460
  58. data/ext/minimap2/tex/minimap2.tex +0 -724
  59. data/ext/minimap2/tex/mm2-s3.sam.eval +0 -62
  60. data/ext/minimap2/tex/mm2-update.tex +0 -240
  61. data/ext/minimap2/tex/mm2.approx.eval +0 -12
  62. data/ext/minimap2/tex/mm2.eval +0 -13
  63. data/ext/minimap2/tex/natbib.bst +0 -1288
  64. data/ext/minimap2/tex/natbib.sty +0 -803
  65. data/ext/minimap2/tex/ngmlr.eval +0 -38
  66. data/ext/minimap2/tex/roc.gp +0 -60
  67. data/ext/minimap2/tex/snap-s3.sam.eval +0 -62
  68. data/ext/minimap2.patch +0 -19
  69. data/lib/minimap2/ffi/constants.rb +0 -267
  70. data/lib/minimap2/ffi/functions.rb +0 -239
  71. data/lib/minimap2/ffi/mappy.rb +0 -104
  72. data/lib/minimap2/ffi.rb +0 -27
@@ -1,3734 +0,0 @@
1
- #!/usr/bin/env k8
2
-
3
- var paftools_version = '2.30-r1287';
4
-
5
- /*****************************
6
- ***** Library functions *****
7
- *****************************/
8
-
9
- /*******************************
10
- * Command line option parsing *
11
- *******************************/
12
-
13
- var getopt = function(args, ostr) {
14
- var oli; // option letter list index
15
- if (typeof(getopt.place) == 'undefined')
16
- getopt.ind = 0, getopt.arg = null, getopt.place = -1;
17
- if (getopt.place == -1) { // update scanning pointer
18
- if (getopt.ind >= args.length || args[getopt.ind].charAt(getopt.place = 0) != '-') {
19
- getopt.place = -1;
20
- return null;
21
- }
22
- if (getopt.place + 1 < args[getopt.ind].length && args[getopt.ind].charAt(++getopt.place) == '-') { // found "--"
23
- ++getopt.ind;
24
- getopt.place = -1;
25
- return null;
26
- }
27
- }
28
- var optopt = args[getopt.ind].charAt(getopt.place++); // character checked for validity
29
- if (optopt == ':' || (oli = ostr.indexOf(optopt)) < 0) {
30
- if (optopt == '-') return null; // if the user didn't specify '-' as an option, assume it means null.
31
- if (getopt.place < 0) ++getopt.ind;
32
- return '?';
33
- }
34
- if (oli+1 >= ostr.length || ostr.charAt(++oli) != ':') { // don't need argument
35
- getopt.arg = null;
36
- if (getopt.place < 0 || getopt.place >= args[getopt.ind].length) ++getopt.ind, getopt.place = -1;
37
- } else { // need an argument
38
- if (getopt.place >= 0 && getopt.place < args[getopt.ind].length)
39
- getopt.arg = args[getopt.ind].substr(getopt.place);
40
- else if (args.length <= ++getopt.ind) { // no arg
41
- getopt.place = -1;
42
- if (ostr.length > 0 && ostr.charAt(0) == ':') return ':';
43
- return '?';
44
- } else getopt.arg = args[getopt.ind]; // white space
45
- getopt.place = -1;
46
- ++getopt.ind;
47
- }
48
- return optopt;
49
- }
50
-
51
- /***********************
52
- * Interval operations *
53
- ***********************/
54
-
55
- Interval = {};
56
-
57
- Interval.sort = function(a)
58
- {
59
- if (typeof a[0] == 'number')
60
- a.sort(function(x, y) { return x - y });
61
- else a.sort(function(x, y) { return x[0] != y[0]? x[0] - y[0] : x[1] - y[1] });
62
- }
63
-
64
- Interval.merge = function(a, sorted)
65
- {
66
- if (typeof sorted == 'undefined') sorted = true;
67
- if (!sorted) Interval.sort(a);
68
- var k = 0;
69
- for (var i = 1; i < a.length; ++i) {
70
- if (a[k][1] >= a[i][0])
71
- a[k][1] = a[k][1] > a[i][1]? a[k][1] : a[i][1];
72
- else a[++k] = a[i].slice(0);
73
- }
74
- a.length = k + 1;
75
- }
76
-
77
- Interval.index_end = function(a, sorted)
78
- {
79
- if (a.length == 0) return;
80
- if (typeof sorted == 'undefined') sorted = true;
81
- if (!sorted) Interval.sort(a);
82
- a[0].push(0);
83
- var k = 0, k_en = a[0][1];
84
- for (var i = 1; i < a.length; ++i) {
85
- if (k_en <= a[i][0]) {
86
- for (++k; k < i; ++k)
87
- if (a[k][1] > a[i][0])
88
- break;
89
- k_en = a[k][1];
90
- }
91
- a[i].push(k);
92
- }
93
- }
94
-
95
- Interval.find_intv = function(a, x)
96
- {
97
- var left = -1, right = a.length;
98
- if (typeof a[0] == 'number') {
99
- while (right - left > 1) {
100
- var mid = left + ((right - left) >> 1);
101
- if (a[mid] > x) right = mid;
102
- else if (a[mid] < x) left = mid;
103
- else return mid;
104
- }
105
- } else {
106
- while (right - left > 1) {
107
- var mid = left + ((right - left) >> 1);
108
- if (a[mid][0] > x) right = mid;
109
- else if (a[mid][0] < x) left = mid;
110
- else return mid;
111
- }
112
- }
113
- return left;
114
- }
115
-
116
- Interval.find_ovlp = function(a, st, en)
117
- {
118
- if (a.length == 0 || st >= en) return [];
119
- var l = Interval.find_intv(a, st);
120
- var k = l < 0? 0 : a[l][a[l].length - 1];
121
- var b = [];
122
- for (var i = k; i < a.length; ++i) {
123
- if (a[i][0] >= en) break;
124
- else if (st < a[i][1])
125
- b.push(a[i]);
126
- }
127
- return b;
128
- }
129
-
130
- /**********************************
131
- * Reverse and reverse complement *
132
- **********************************/
133
-
134
- function fasta_read(fn)
135
- {
136
- var h = {}, seqlen = [];
137
- var buf = new Bytes();
138
- var file = fn == '-'? new File() : new File(fn);
139
- if (typeof k8_version == "undefined") { // for k8-0.x
140
- var seq = null, name = null, gt = '>'.charCodeAt(0);
141
- while (file.readline(buf) >= 0) {
142
- if (buf[0] == gt) {
143
- if (seq != null && name != null) {
144
- seqlen.push([name, seq.length]);
145
- h[name] = seq;
146
- name = seq = null;
147
- }
148
- var m, line = buf.toString();
149
- if ((m = /^>(\S+)/.exec(line)) != null) {
150
- name = m[1];
151
- seq = new Bytes();
152
- }
153
- } else seq.set(buf);
154
- }
155
- if (seq != null && name != null) {
156
- seqlen.push([name, seq.length]);
157
- h[name] = seq;
158
- }
159
- } else { // for k8-1.x
160
- var seq = null, name = null;
161
- while (file.readline(buf) >= 0) {
162
- var line = buf.toString();
163
- if (line[0] == ">") {
164
- if (seq != null && name != null) {
165
- seqlen.push([name, seq.length]);
166
- h[name] = new Uint8Array(seq.buffer);
167
- name = seq = null;
168
- }
169
- var m;
170
- if ((m = /^>(\S+)/.exec(line)) != null) {
171
- name = m[1];
172
- seq = new Bytes();
173
- }
174
- } else seq.set(line);
175
- }
176
- if (seq != null && name != null) {
177
- seqlen.push([name, seq.length]);
178
- h[name] = new Uint8Array(seq.buffer);
179
- }
180
- }
181
- buf.destroy();
182
- file.close();
183
- return [h, seqlen];
184
- }
185
-
186
- function fasta_free(fa)
187
- {
188
- if (typeof k8_version == "undefined")
189
- for (var name in fa)
190
- fa[name].destroy();
191
- // FIXME: for k8-1.0, sequences are not freed. This is ok for now but not general.
192
- }
193
-
194
- Bytes.prototype.reverse = function()
195
- {
196
- if (typeof k8_version === "undefined") { // k8-0.x
197
- for (var i = 0; i < this.length>>1; ++i) {
198
- var tmp = this[i];
199
- this[i] = this[this.length - i - 1];
200
- this[this.length - i - 1] = tmp;
201
- }
202
- } else { // k8-1.x
203
- var buf = new Uint8Array(this.buffer);
204
- for (var i = 0; i < buf.length>>1; ++i) {
205
- var tmp = buf[i];
206
- buf[i] = buf[buf.length - i - 1];
207
- buf[buf.length - i - 1] = tmp;
208
- }
209
- }
210
- }
211
-
212
- // reverse complement a DNA string
213
- Bytes.prototype.revcomp = function()
214
- {
215
- if (Bytes.rctab == null) {
216
- var s1 = 'WSATUGCYRKMBDHVNwsatugcyrkmbdhvn';
217
- var s2 = 'WSTAACGRYMKVHDBNwstaacgrymkvhdbn';
218
- Bytes.rctab = [];
219
- for (var i = 0; i < 256; ++i) Bytes.rctab[i] = 0;
220
- for (var i = 0; i < s1.length; ++i)
221
- Bytes.rctab[s1.charCodeAt(i)] = s2.charCodeAt(i);
222
- }
223
- if (typeof k8_version === "undefined") { // k8-0.x
224
- for (var i = 0; i < this.length>>1; ++i) {
225
- var tmp = this[this.length - i - 1];
226
- this[this.length - i - 1] = Bytes.rctab[this[i]];
227
- this[i] = Bytes.rctab[tmp];
228
- }
229
- if (this.length&1)
230
- this[this.length>>1] = Bytes.rctab[this[this.length>>1]];
231
- } else { // k8-1.x
232
- var buf = new Uint8Array(this.buffer);
233
- for (var i = 0; i < buf.length>>1; ++i) {
234
- var tmp = buf[buf.length - i - 1];
235
- buf[buf.length - i - 1] = Bytes.rctab[buf[i]];
236
- buf[i] = Bytes.rctab[tmp];
237
- }
238
- if (buf.length&1)
239
- buf[buf.length>>1] = Bytes.rctab[buf[buf.length>>1]];
240
- }
241
- }
242
-
243
- /********************
244
- ***** paftools *****
245
- ********************/
246
-
247
- /*****************
248
- * Miscellaneous *
249
- *****************/
250
-
251
- // liftover
252
- function paf_liftover(args)
253
- {
254
- function read_bed(fn, to_merge)
255
- {
256
- if (fn == null) return null;
257
- if (typeof to_merge == 'undefined') to_merge = true;
258
- var file = fn == '-'? new File() : new File(fn);
259
- var buf = new Bytes();
260
- var bed = {};
261
- while (file.readline(buf) >= 0) {
262
- var t = buf.toString().split("\t");
263
- if (bed[t[0]] == null) bed[t[0]] = [];
264
- bed[t[0]].push([parseInt(t[1]), parseInt(t[2])]);
265
- }
266
- buf.destroy();
267
- file.close();
268
-
269
- for (var chr in bed) {
270
- Interval.sort(bed[chr]);
271
- if (to_merge)
272
- Interval.merge(bed[chr], true);
273
- Interval.index_end(bed[chr], true);
274
- }
275
- return bed;
276
- }
277
-
278
- var re_cigar = /(\d+)([MID])/g, re_tag = /^(\S\S):([AZif]):(\S+)$/;
279
- var c, to_merge = false, min_mapq = 5, min_len = 50000, max_div = 2.0;
280
- var re = /(\d+)([MID])/g;
281
- while ((c = getopt(args, "mq:l:d:")) != null) {
282
- if (c == 'm') to_merge = true;
283
- else if (c == 'q') min_mapq = parseInt(getopt.arg);
284
- else if (c == 'l') min_len = parseInt(getopt.arg);
285
- else if (c == 'd') max_div = parseFloat(getopt.arg);
286
- }
287
- if (args.length - getopt.ind < 2) {
288
- print("Usage: paftools.js liftover [options] <aln.paf> <query.bed>");
289
- print("Options:");
290
- print(" -q INT min mapping quality [" + min_mapq + "]");
291
- print(" -l INT min alignment length [" + min_len + "]");
292
- print(" -d FLOAT max sequence divergence (>=1 to disable) [1]");
293
- exit(1);
294
- }
295
- var bed = read_bed(args[getopt.ind+1], to_merge);
296
-
297
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
298
- var buf = new Bytes();
299
- while (file.readline(buf) >= 0) {
300
- var t = buf.toString().split("\t");
301
-
302
- if (bed[t[0]] == null) continue; // sequence not present in BED; skip
303
-
304
- // parse tp and cg tags
305
- var m, tp = null, cg = null;
306
- for (var i = 12; i < t.length; ++i) {
307
- if ((m = re_tag.exec(t[i])) != null) {
308
- if (m[1] == 'tp') tp = m[3];
309
- else if (m[1] == 'cg') cg = m[3];
310
- }
311
- }
312
- if (tp != 'P' && tp != 'I') continue; // only process primary alignments
313
- if (cg == null) throw Error("unable to find the 'cg' tag");
314
-
315
- // filter out bad alignments and check overlaps
316
- for (var i = 1; i <= 3; ++i)
317
- t[i] = parseInt(t[i]);
318
- for (var i = 6; i <= 11; ++i)
319
- t[i] = parseInt(t[i]);
320
- if (t[11] < min_mapq || t[10] < min_len) continue;
321
- var regs = Interval.find_ovlp(bed[t[0]], t[2], t[3]);
322
- if (regs.length == 0) continue; // not overlapping any regions in input BED
323
- if (max_div >= 0.0 && max_div < 1.0) {
324
- var n_gaps = 0, n_opens = 0;
325
- while ((m = re_cigar.exec(cg)) != null)
326
- if (m[2] == 'I' || m[2] == 'D')
327
- n_gaps += parseInt(m[1]), ++n_opens;
328
- var n_mm = t[10] - t[9] - n_gaps;
329
- var n_diff2 = n_mm + n_opens;
330
- if (n_diff2 / (n_diff2 + t[9]) > max_div)
331
- continue;
332
- }
333
-
334
- // extract start and end positions
335
- var a = [], r = [], strand = t[4];
336
- for (var i = 0; i < regs.length; ++i) {
337
- var s = regs[i][0], e = regs[i][1];
338
- if (strand == '+') {
339
- a.push([s, 0, i, -2]);
340
- a.push([e - 1, 1, i, -2]);
341
- } else {
342
- a.push([t[1] - e, 0, i, -2]);
343
- a.push([t[1] - s - 1, 1, i, -2]);
344
- }
345
- r.push([-2, -2]);
346
- }
347
- a.sort(function(x, y) { return x[0] - y[0] });
348
-
349
- // lift start/end positions
350
- var k = 0, x = t[7], y = strand == '+'? t[2] : t[1] - t[3];
351
- while ((m = re_cigar.exec(cg)) != null) { // TODO: be more careful about edge cases
352
- var len = parseInt(m[1]);
353
- if (m[2] == 'D') { // do nothing for D
354
- x += len;
355
- continue;
356
- }
357
- while (k < a.length && a[k][0] < y) ++k; // skip out-of-range positions
358
- for (var i = k; i < a.length; ++i) {
359
- if (y <= a[i][0] && a[i][0] < y + len)
360
- a[i][3] = m[2] == 'M'? x + (a[i][0] - y) : x;
361
- else break;
362
- }
363
- y += len;
364
- if (m[2] == 'M') x += len;
365
- }
366
- if (x != t[8] || (strand == '+' && y != t[3]) || (strand == '-' && y != t[1] - t[2]))
367
- throw Error("CIGAR is inconsistent with mapping coordinates");
368
-
369
- // generate result
370
- for (var i = 0; i < a.length; ++i) {
371
- if (a[i][1] == 0) r[a[i][2]][0] = a[i][3];
372
- else r[a[i][2]][1] = a[i][3] + 1; // change to half-close-half-open
373
- }
374
- for (var i = 0; i < r.length; ++i) {
375
- var name = [t[0], regs[i][0], regs[i][1]].join("_");
376
- if (r[i][0] < 0) name += "_t5", r[i][0] = t[7];
377
- if (r[i][1] < 0) name += "_t3", r[i][1] = t[8];
378
- print(t[5], r[i][0], r[i][1], name, 0, strand);
379
- }
380
- }
381
- buf.destroy();
382
- file.close();
383
- }
384
-
385
- // variant calling
386
- function paf_call(args)
387
- {
388
- var re_cs = /([:=*+-])(\d+|[A-Za-z]+)/g, re_tag = /\t(\S\S:[AZif]):(\S+)/g;
389
- var c, min_cov_len = 10000, min_var_len = 50000, gap_thres = 50, gap_thres_long = 1000, min_mapq = 5;
390
- var fa_tmp = null, fa, fa_lens, is_vcf = false, sample_name = "sample";
391
- while ((c = getopt(args, "l:L:g:q:B:f:s:")) != null) {
392
- if (c == 'l') min_cov_len = parseInt(getopt.arg);
393
- else if (c == 'L') min_var_len = parseInt(getopt.arg);
394
- else if (c == 'g') gap_thres = parseInt(getopt.arg);
395
- else if (c == 'G') gap_thres_long = parseInt(getopt.arg);
396
- else if (c == 'q') min_mapq = parseInt(getopt.arg);
397
- else if (c == 'f') fa_tmp = fasta_read(getopt.arg, fa_lens);
398
- else if (c == 's') sample_name = getopt.arg;
399
- }
400
- if (fa_tmp != null) fa = fa_tmp[0], fa_lens = fa_tmp[1], is_vcf = true;
401
-
402
- if (args.length == getopt.ind) {
403
- print("Usage: sort -k6,6 -k8,8n <with-cs.paf> | paftools.js call [options] -");
404
- print("Options:");
405
- print(" -l INT min alignment length to compute coverage ["+min_cov_len+"]");
406
- print(" -L INT min alignment length to call variants ["+min_var_len+"]");
407
- print(" -q INT min mapping quality ["+min_mapq+"]");
408
- print(" -g INT short/long gap threshold (for statistics only) ["+gap_thres+"]");
409
- print(" -f FILE reference sequences (enabling VCF output) [null]");
410
- print(" -s NAME sample name in VCF header ["+sample_name+"]");
411
- exit(1);
412
- }
413
-
414
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
415
- var buf = new Bytes();
416
- var tot_len = 0, n_sub = [0, 0, 0], n_ins = [0, 0, 0, 0, 0], n_del = [0, 0, 0, 0, 0];
417
-
418
- function print_vcf(o, fa)
419
- {
420
- var v = null;
421
- if (o[3] != 1) return; // coverage is one; skip
422
- if (o[5] == '-' && o[6] == '-') return;
423
- if (o[5] != '-' && o[6] != '-') { // snp
424
- v = [o[0], o[1] + 1, '.', o[5].toUpperCase(), o[6].toUpperCase()];
425
- } else if (o[1] > 0) { // shouldn't happen in theory
426
- if (fa[o[0]] == null) throw Error('sequence "' + o[0] + '" is absent from the reference FASTA');
427
- if (o[1] >= fa[o[0]].length) throw Error('position ' + o[1] + ' exceeds the length of sequence "' + o[0] + '"');
428
- var ref = String.fromCharCode(fa[o[0]][o[1]-1]).toUpperCase();
429
- if (o[5] == '-') // insertion
430
- v = [o[0], o[1], '.', ref, ref + o[6].toUpperCase()];
431
- else // deletion
432
- v = [o[0], o[1], '.', ref + o[5].toUpperCase(), ref];
433
- }
434
- v.push(o[4], '.', 'QNAME=' + o[7] + ';QSTART=' + (o[8]+1) + ';QSTRAND=' + (rev? '-' : '+'), 'GT', '1/1');
435
- if (v == null) throw Error("unexpected variant: [" + o.join(",") + "]");
436
- print(v.join("\t"));
437
- }
438
-
439
- function count_var(o)
440
- {
441
- if (o[3] > 1) return;
442
- if (o[5] == '-' && o[6] == '-') return;
443
- if (o[5] == '-') { // insertion
444
- var l = o[6].length;
445
- if (l == 1) ++n_ins[0];
446
- else if (l == 2) ++n_ins[1];
447
- else if (l < gap_thres) ++n_ins[2];
448
- else if (l < gap_thres_long) ++n_ins[3];
449
- else ++n_ins[4];
450
- } else if (o[6] == '-') { // deletion
451
- var l = o[5].length;
452
- if (l == 1) ++n_del[0];
453
- else if (l == 2) ++n_del[1];
454
- else if (l < gap_thres) ++n_del[2];
455
- else if (l < gap_thres_long) ++n_del[3];
456
- else ++n_del[4];
457
- } else {
458
- ++n_sub[0];
459
- var s = (o[5] + o[6]).toLowerCase();
460
- if (s == 'ag' || s == 'ga' || s == 'ct' || s == 'tc')
461
- ++n_sub[1];
462
- else ++n_sub[2];
463
- }
464
- }
465
-
466
- if (is_vcf) {
467
- print('##fileformat=VCFv4.1');
468
- for (var i = 0; i < fa_lens.length; ++i)
469
- print('##contig=<ID=' + fa_lens[i][0] + ',length=' + fa_lens[i][1] + '>');
470
- print('##INFO=<ID=QNAME,Number=1,Type=String,Description="Query name">');
471
- print('##INFO=<ID=QSTART,Number=1,Type=Integer,Description="Query start">');
472
- print('##INFO=<ID=QSTRAND,Number=1,Type=String,Description="Query strand">');
473
- print('##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">');
474
- print('#CHROM POS ID REF ALT QUAL FILTER INFO FORMAT '+sample_name);
475
- }
476
-
477
- var a = [], out = [];
478
- var c1_ctg = null, c1_start = 0, c1_end = 0, c1_counted = false, c1_len = 0;
479
- while (file.readline(buf) >= 0) {
480
- var line = buf.toString();
481
- var m, t = line.split("\t", 12);
482
- if (t.length < 12 || t[5] == '*') continue; // unmapped
483
- for (var i = 6; i <= 11; ++i)
484
- t[i] = parseInt(t[i]);
485
- if (t[10] < min_cov_len || t[11] < min_mapq) continue;
486
- //print(t[0], t[7], t[8], c1_start, c1_end);
487
- for (var i = 1; i <= 3; ++i)
488
- t[i] = parseInt(t[i]);
489
- var ctg = t[5], x = t[7], end = t[8];
490
- var query = t[0], rev = (t[4] == '-'), y = rev? t[3] : t[2];
491
- // collect tags
492
- var cs = null, tp = null, have_s1 = false, have_s2 = false;
493
- while ((m = re_tag.exec(line)) != null) {
494
- if (m[1] == 'cs:Z') cs = m[2];
495
- else if (m[1] == 'tp:A') tp = m[2];
496
- else if (m[1] == 's1:i') have_s1 = true;
497
- else if (m[1] == 's2:i') have_s2 = true;
498
- }
499
- if (have_s1 && !have_s2) continue;
500
- if (tp != null && (tp == 'S' || tp == 'i')) continue;
501
- // compute regions covered by 1 contig
502
- if (ctg != c1_ctg || x >= c1_end) {
503
- if (c1_counted && c1_end > c1_start) {
504
- c1_len += c1_end - c1_start;
505
- if (!is_vcf) print('R', c1_ctg, c1_start, c1_end);
506
- }
507
- c1_ctg = ctg, c1_start = x, c1_end = end;
508
- c1_counted = (t[10] >= min_var_len);
509
- } else if (end > c1_end) { // overlap
510
- if (c1_counted && x > c1_start) {
511
- c1_len += x - c1_start;
512
- if (!is_vcf) print('R', c1_ctg, c1_start, x);
513
- }
514
- c1_start = c1_end, c1_end = end;
515
- c1_counted = (t[10] >= min_var_len);
516
- } else if (end > c1_start) { // contained
517
- if (c1_counted && x > c1_start) {
518
- c1_len += x - c1_start;
519
- if (!is_vcf) print('R', c1_ctg, c1_start, x);
520
- }
521
- c1_start = end;
522
- } // else, the alignment precedes the cov1 region; do nothing
523
- // output variants ahead of this alignment
524
- while (out.length) {
525
- if (out[0][0] != ctg || out[0][2] <= x) {
526
- count_var(out[0]);
527
- if (is_vcf) print_vcf(out[0], fa);
528
- else print('V', out[0].join("\t"));
529
- out.shift();
530
- } else break;
531
- }
532
- // update coverage
533
- for (var i = 0; i < out.length; ++i)
534
- if (out[i][1] >= x && out[i][2] <= end)
535
- ++out[i][3];
536
- // drop alignments that don't overlap with the current one
537
- var k = 0;
538
- for (var i = 0; i < a.length; ++i)
539
- if (a[i][0] == ctg && a[i][2] > x)
540
- a[k++] = a[i];
541
- a.length = k;
542
- // core loop
543
- if (t[10] >= min_var_len) {
544
- if (cs == null) continue; // no cs tag
545
- var blen = 0, n_diff = 0;
546
- tot_len += t[10];
547
- while ((m = re_cs.exec(cs)) != null) {
548
- var cov = 1;
549
- if (m[1] == '*' || m[1] == '+' || m[1] == '-')
550
- for (var i = 0; i < a.length; ++i)
551
- if (a[i][2] > x) ++cov;
552
- var qs, qe;
553
- if (m[1] == '=' || m[1] == ':') {
554
- var l = m[1] == '='? m[2].length : parseInt(m[2]);
555
- if (rev) y -= l;
556
- else y += l;
557
- x += l, blen += l;
558
- } else if (m[1] == '*') {
559
- if (rev) qs = y - 1, qe = y, --y;
560
- else qs = y, qe = y + 1, ++y;
561
- var br = m[2].charAt(0), bq = m[2].charAt(1);
562
- if (br != 'n' && bq != 'n') { // don't call a SNP if there is an ambiguous base
563
- out.push([t[5], x, x+1, cov, t[11], br, bq, query, qs, qe, rev? '-' : '+']);
564
- ++n_diff;
565
- }
566
- ++x, ++blen;
567
- } else if (m[1] == '+') {
568
- var l = m[2].length;
569
- if (rev) qs = y - l, qe = y, y -= l;
570
- else qs = y, qe = y + l, y += l;
571
- out.push([t[5], x, x, cov, t[11], '-', m[2], query, qs, qe, rev? '-' : '+']);
572
- ++blen, ++n_diff;
573
- } else if (m[1] == '-') {
574
- var l = m[2].length;
575
- out.push([t[5], x, x + l, cov, t[11], m[2], '-', query, y, y, rev? '-' : '+']);
576
- x += l, ++blen, ++n_diff;
577
- }
578
- }
579
- }
580
- a.push([t[5], t[7], t[8]]);
581
- }
582
- if (c1_counted && c1_end > c1_start) {
583
- c1_len += c1_end - c1_start;
584
- if (!is_vcf) print('R', c1_ctg, c1_start, c1_end);
585
- }
586
- while (out.length) {
587
- count_var(out[0]);
588
- if (is_vcf) print_vcf(out[0], fa);
589
- else print('V', out[0].join("\t"));
590
- out.shift();
591
- }
592
-
593
- //warn(tot_len + " alignment columns considered in calling");
594
- warn(c1_len + " reference bases covered by exactly one contig");
595
- warn(n_sub[0] + " substitutions; ts/tv = " + (n_sub[1]/n_sub[2]).toFixed(3));
596
- warn(n_del[0] + " 1bp deletions");
597
- warn(n_ins[0] + " 1bp insertions");
598
- warn(n_del[1] + " 2bp deletions");
599
- warn(n_ins[1] + " 2bp insertions");
600
- warn(n_del[2] + " [3,"+gap_thres+") deletions");
601
- warn(n_ins[2] + " [3,"+gap_thres+") insertions");
602
- warn(n_del[3] + " ["+gap_thres+","+gap_thres_long+") deletions");
603
- warn(n_ins[3] + " ["+gap_thres+","+gap_thres_long+") insertions");
604
- warn(n_del[4] + " >=" + gap_thres_long + " deletions");
605
- warn(n_ins[4] + " >=" + gap_thres_long + " insertions");
606
-
607
- buf.destroy();
608
- file.close();
609
- if (fa != null) fasta_free(fa);
610
- }
611
-
612
- function paf_asmstat(args)
613
- {
614
- var c, min_query_len = 0, min_seg_len = 10000, max_diff = 0.01, bp_flank_len = 0, bp_gap_len = 0;
615
- while ((c = getopt(args, "l:d:b:g:q:")) != null) {
616
- if (c == 'l') min_seg_len = parseInt(getopt.arg);
617
- else if (c == 'd') max_diff = parseFloat(getopt.arg);
618
- else if (c == 'b') bp_flank_len = parseInt(getopt.arg);
619
- else if (c == 'g') bp_gap_len = parseInt(getopt.arg);
620
- else if (c == 'q') min_query_len = parseInt(getopt.arg);
621
- }
622
- if (getopt.ind == args.length) {
623
- print("Usage: paftools.js asmstat [options] <ref.fa.fai> <asm1.paf> [...]");
624
- print("Options:");
625
- print(" -q INT ignore query shorter than INT [0]");
626
- print(" -l INT min alignment block length [" + min_seg_len + "]");
627
- print(" -d FLOAT max gap-compressed sequence divergence [" + max_diff + "]");
628
- exit(1);
629
- }
630
-
631
- var file, buf = new Bytes();
632
-
633
- var ref_len = 0;
634
- file = new File(args[getopt.ind]);
635
- while (file.readline(buf) >= 0) {
636
- var t = buf.toString().split("\t");
637
- ref_len += parseInt(t[1]);
638
- }
639
- file.close();
640
-
641
- function process_query(qblocks, qblock_len, bp, qi) {
642
- qblocks.sort(function(a,b) { return a[0]-b[0]; });
643
- var last_k = null, last_blen = null, st = -1, en = -1, qcov = 0;
644
- for (var k = 0; k < qblocks.length; ++k) {
645
- var blen = qblocks[k][1] - qblocks[k][0];
646
- if (k > 0 && qblocks[k][0] < qblocks[k-1][1]) {
647
- if (qblocks[k][1] < qblocks[k-1][1]) continue;
648
- blen = qblocks[k][1] - qblocks[k-1][1];
649
- }
650
- qblock_len.push(blen);
651
- if (qblocks[k][0] > en) {
652
- qcov += en - st;
653
- st = qblocks[k][0];
654
- en = qblocks[k][1];
655
- } else en = en > qblocks[k][1]? en : qblocks[k][1];
656
- if (last_k != null) {
657
- var gap = 1000000000;
658
- if (qblocks[k][2] == qblocks[last_k][2] && qblocks[k][3] == qblocks[last_k][3]) { // same chr and strand
659
- var g1 = qblocks[k][0] - qblocks[last_k][1];
660
- var g2 = qblocks[k][2] == '+'? qblocks[k][4] - qblocks[last_k][5] : qblocks[last_k][4] - qblocks[k][5];
661
- gap = g1 > g2? g1 - g2 : g2 - g1;
662
- }
663
- var min = blen < last_blen? blen : last_blen;
664
- var flank = k == 0? min : blen;
665
- bp.push([flank, gap]);
666
- qi.bp.push([flank, gap]);
667
- }
668
- last_k = k, last_blen = blen;
669
- }
670
- qcov += en - st;
671
- return qcov;
672
- }
673
-
674
- function N50(lens, tot, quantile) {
675
- lens.sort(function(a,b) { return b - a; });
676
- if (tot == null) {
677
- tot = 0;
678
- for (var k = 0; k < lens.length; ++k)
679
- tot += lens[k];
680
- }
681
- var sum = 0;
682
- for (var k = 0; k < lens.length; ++k) {
683
- if (sum <= quantile * tot && sum + lens[k] > quantile * tot)
684
- return lens[k];
685
- sum += lens[k];
686
- }
687
- }
688
-
689
- function AUN(lens, tot) {
690
- lens.sort(function(a,b) { return b - a; });
691
- if (tot == null) {
692
- tot = 0;
693
- for (var k = 0; k < lens.length; ++k)
694
- tot += lens[k];
695
- }
696
- var x = 0, y = 0;
697
- for (var k = 0; k < lens.length; ++k) {
698
- var l = x + lens[k] <= tot? lens[k] : tot - x;
699
- x += lens[k];
700
- y += l * (l / tot);
701
- if (x >= tot) break;
702
- }
703
- return y.toFixed(0);
704
- }
705
-
706
- function count_bp(bp, min_blen, min_gap) {
707
- var n_bp = 0;
708
- for (var k = 0; k < bp.length; ++k)
709
- if (bp[k][0] >= min_blen && bp[k][1] >= min_gap)
710
- ++n_bp;
711
- return n_bp;
712
- }
713
-
714
- function compute_diff(cigar, NM) {
715
- var m, re = /(\d+)([MID])/g;
716
- var n_M = 0, n_gapo = 0, n_gaps = 0;
717
- while ((m = re.exec(cigar)) != null) {
718
- var len = parseInt(m[1]);
719
- if (m[2] == 'M') n_M += len;
720
- else ++n_gapo, n_gaps += len;
721
- }
722
- if (NM < n_gaps) throw Error('NM is smaller the number of gaps');
723
- return (NM - n_gaps + n_gapo) / (n_M + n_gapo);
724
- }
725
-
726
- var labels = ['Length', 'l_cov', 'Rcov', 'Rdup', 'Qcov', 'NG75', 'NG50', 'NGA50', 'AUNGA', '#breaks', 'bp(' + min_seg_len + ',0)', 'bp(' + min_seg_len + ',10k)'];
727
- var rst = [];
728
- for (var i = 0; i < labels.length; ++i)
729
- rst[i] = [];
730
-
731
- var n_asm = args.length - (getopt.ind + 1);
732
- var header = ["Metric"];
733
- for (var i = 0; i < n_asm; ++i) {
734
- var n_breaks = 0, qcov = 0;
735
- var fn = args[getopt.ind + 1 + i];
736
- var label = fn.replace(/.paf(.gz)?$/, "");
737
- header.push(label);
738
- var ref_blocks = [], qblock_len = [], qblocks = [], bp = [];
739
- var query = {}, qinfo = {};
740
- var last_qname = null;
741
- file = new File(fn);
742
- while (file.readline(buf) >= 0) {
743
- var m, line = buf.toString();
744
- var t = line.split("\t");
745
- t[1] = parseInt(t[1]);
746
- if (t[1] < min_query_len) continue;
747
- if (t.length < 2) continue;
748
- query[t[0]] = t[1];
749
- if (qinfo[t[0]] == null) qinfo[t[0]] = {};
750
- qinfo[t[0]].len = t[1];
751
- qinfo[t[0]].bp = [];
752
- if (t.length < 9 || t[5] == "*") continue;
753
- if (!/\ttp:A:[PI]/.test(line)) continue;
754
- var cigar = (m = /\tcg:Z:(\S+)/.exec(line)) != null? m[1] : null;
755
- var NM = (m = /\tNM:i:(\d+)/.exec(line)) != null? parseInt(m[1]) : null;
756
- var diff = cigar != null && NM != null? compute_diff(cigar, NM) : 0;
757
- t[2] = parseInt(t[2]);
758
- t[3] = parseInt(t[3]);
759
- t[7] = parseInt(t[7]);
760
- t[8] = parseInt(t[8]);
761
- if (t[0] == last_qname) ++n_breaks;
762
- if (diff > max_diff) continue;
763
- if (t[3] - t[2] < min_seg_len) continue;
764
- if (t[0] != last_qname) {
765
- if (last_qname != null)
766
- qcov += process_query(qblocks, qblock_len, bp, qinfo[last_qname]);
767
- qblocks = [];
768
- last_qname = t[0];
769
- }
770
- ref_blocks.push([t[5], t[7], t[8]]);
771
- qblocks.push([t[2], t[3], t[4], t[5], t[7], t[8]]);
772
- }
773
- if (last_qname != null)
774
- qcov += process_query(qblocks, qblock_len, bp, qinfo[last_qname]);
775
- file.close();
776
-
777
- // compute NG50
778
- var asm_len = 0, asm_lens = []
779
- for (var ctg in query) {
780
- asm_len += query[ctg];
781
- asm_lens.push(query[ctg]);
782
- }
783
- rst[0][i] = asm_len;
784
- rst[5][i] = N50(asm_lens, ref_len, 0.75);
785
- rst[6][i] = N50(asm_lens, ref_len, 0.5);
786
-
787
- // compute coverage
788
- var l_cov = 0;
789
- ref_blocks.sort(function(a, b) { return a[0] > b[0]? 1 : a[0] < b[0]? -1 : a[1] - b[1]; });
790
- var last_ref = null, st = -1, en = -1;
791
- for (var j = 0; j < ref_blocks.length; ++j) {
792
- if (ref_blocks[j][0] != last_ref || ref_blocks[j][1] > en) {
793
- l_cov += en - st;
794
- last_ref = ref_blocks[j][0];
795
- st = ref_blocks[j][1];
796
- en = ref_blocks[j][2];
797
- } else en = en > ref_blocks[j][2]? en : ref_blocks[j][2];
798
- }
799
- l_cov += en - st;
800
- rst[1][i] = l_cov;
801
- rst[2][i] = (100.0 * (l_cov / ref_len)).toFixed(2) + '%';
802
- rst[4][i] = (100.0 * (qcov / asm_len)).toFixed(2) + '%';
803
-
804
- // compute cov1 and cov2+ lengths; see paf_call() for details
805
- var c1_ctg = null, c1_start = 0, c1_end = 0, c1_len = 0;
806
- for (var j = 0; j < ref_blocks.length; ++j) {
807
- if (ref_blocks[j][0] != c1_ctg || ref_blocks[j][1] >= c1_end) {
808
- if (c1_end > c1_start)
809
- c1_len += c1_end - c1_start;
810
- c1_ctg = ref_blocks[j][0], c1_start = ref_blocks[j][1], c1_end = ref_blocks[j][2];
811
- } else if (ref_blocks[j][2] > c1_end) { // overlap
812
- if (ref_blocks[j][1] > c1_start)
813
- c1_len += ref_blocks[j][1] - c1_start;
814
- c1_start = c1_end, c1_end = ref_blocks[j][2];
815
- } else if (ref_blocks[j][2] > c1_start) { // contained
816
- if (ref_blocks[j][1] > c1_start)
817
- c1_len += ref_blocks[j][1] - c1_start;
818
- c1_start = ref_blocks[j][2];
819
- }
820
- //print(ref_blocks[j][0], ref_blocks[j][1], ref_blocks[j][2], c1_start, c1_end, c1_len);
821
- }
822
- if (c1_end > c1_start)
823
- c1_len += c1_end - c1_start;
824
- rst[3][i] = (100 * (l_cov - c1_len) / l_cov).toFixed(2) + '%';
825
-
826
- // compute NGA50
827
- rst[7][i] = N50(qblock_len, ref_len, 0.5);
828
-
829
- // compute AUNGA
830
- rst[8][i] = AUN(qblock_len, ref_len);
831
-
832
- // compute break points
833
- rst[9][i] = n_breaks;
834
- rst[10][i] = count_bp(bp, 500, 0);
835
- rst[11][i] = count_bp(bp, 500, 10000);
836
-
837
- // nb-plot; NOT USED
838
- /*
839
- var qa = [];
840
- for (var qn in qinfo)
841
- qa.push([qinfo[qn].len, qinfo[qn].bp]);
842
- qa = qa.sort(function(a, b) { return b[0] - a[0] });
843
- var sum = 0, n_bp = 0, next_quantile = 0.1;
844
- for (var j = 0; j < qa.length; ++j) {
845
- sum += qa[j][0];
846
- for (var k = 0; k < qa[j][1].length; ++k)
847
- if (qa[j][1][k][0] >= bp_flank_len && qa[j][1][k][1] >= bp_gap_len)
848
- ++n_bp;
849
- if (sum >= ref_len * next_quantile) {
850
- print(label, Math.floor(next_quantile * 100 + .5), qa[j][0], (sum / n_bp).toFixed(0), n_bp);
851
- next_quantile += 0.1;
852
- if (next_quantile >= 1.0) break;
853
- }
854
- }
855
- */
856
- }
857
- buf.destroy();
858
-
859
- if (bp_flank_len <= 0) {
860
- print(header.join("\t"));
861
- for (var i = 0; i < labels.length; ++i)
862
- print(labels[i], rst[i].join("\t"));
863
- }
864
- }
865
-
866
- function paf_asmgene(args)
867
- {
868
- var c, opt = { min_cov:0.99, min_iden:0.99 }, print_err = false, auto_only = false;
869
- while ((c = getopt(args, "i:c:ea")) != null)
870
- if (c == 'i') opt.min_iden = parseFloat(getopt.arg);
871
- else if (c == 'c') opt.min_cov = parseFloat(getopt.arg);
872
- else if (c == 'e') print_err = true;
873
- else if (c == 'a') auto_only = true;
874
-
875
- var n_fn = args.length - getopt.ind;
876
- if (n_fn < 2) {
877
- print("Usage: paftools.js asmgene [options] <ref-splice.paf> <asm-splice.paf> [...]");
878
- print("Options:");
879
- print(" -i FLOAT min identity [" + opt.min_iden + "]");
880
- print(" -c FLOAT min coverage [" + opt.min_cov + "]");
881
- print(" -a only evaluate genes mapped to the autosomes");
882
- print(" -e print fragmented/missing genes");
883
- exit(1);
884
- }
885
-
886
- function process_query(opt, a) {
887
- var b = [], cnt = [0, 0, 0];
888
- for (var j = 0; j < a.length; ++j) {
889
- if (a[j][4] < a[j][5] * opt.min_iden)
890
- continue;
891
- b.push(a[j].slice(0));
892
- }
893
- if (b.length == 0) return cnt;
894
- // count full
895
- var n_full = 0;
896
- for (var j = 0; j < b.length; ++j)
897
- if (b[j][3] - b[j][2] >= b[j][1] * opt.min_cov)
898
- ++n_full;
899
- cnt[0] = n_full;
900
- // compute coverage
901
- b = b.sort(function(x, y) { return x[2] - y[2] });
902
- var l_cov = 0, st = b[0][2], en = b[0][3];
903
- for (var j = 1; j < b.length; ++j) {
904
- if (b[j][2] <= en)
905
- en = b[j][3] > en? b[j][3] : en;
906
- else l_cov += en - st;
907
- }
908
- l_cov += en - st;
909
- cnt[1] = l_cov / b[0][1];
910
- cnt[2] = b.length;
911
- return cnt;
912
- }
913
-
914
- var buf = new Bytes();
915
- var gene = {}, header = [], refpos = {};
916
- for (var i = getopt.ind; i < args.length; ++i) {
917
- var fn = args[i];
918
- var label = fn.replace(/.paf(.gz)?$/, "");
919
- header.push(label);
920
- var file = new File(fn), a = [];
921
- while (file.readline(buf) >= 0) {
922
- var t = buf.toString().split("\t");
923
- var ql = parseInt(t[1]), qs = parseInt(t[2]), qe = parseInt(t[3]), mlen = parseInt(t[9]), blen = parseInt(t[10]), mapq = parseInt(t[11]);
924
- if (i == getopt.ind) refpos[t[0]] = [t[0], t[1], t[5], t[7], t[8]];
925
- if (gene[t[0]] == null) gene[t[0]] = [];
926
- if (a.length && t[0] != a[0][0]) {
927
- gene[a[0][0]][i - getopt.ind] = process_query(opt, a);
928
- a = [];
929
- }
930
- a.push([t[0], ql, qs, qe, mlen, blen]);
931
- }
932
- if (a.length)
933
- gene[t[0]][i - getopt.ind] = process_query(opt, a);
934
- file.close();
935
- }
936
-
937
- // select the longest genes (not optimal, but should be good enough)
938
- var gene_list = [], gene_nr = {};
939
- for (var g in refpos)
940
- gene_list.push(refpos[g]);
941
- gene_list = gene_list.sort(function(a, b) { return a[2] < b[2]? -1 : a[2] > b[2]? 1 : a[3] - b[3] });
942
- var last = 0;
943
- for (var j = 1; j < gene_list.length; ++j) {
944
- if (gene_list[j][2] != gene_list[last][2] || gene_list[j][3] >= gene_list[last][4]) {
945
- gene_nr[gene_list[last][0]] = 1;
946
- last = j;
947
- } else if (gene_list[j][1] > gene_list[last][1]) {
948
- last = j;
949
- }
950
- }
951
- gene_nr[gene_list[last][0]] = 1;
952
-
953
- // count and print
954
- var col1 = ["full_sgl", "full_dup", "frag", "part50+", "part10+", "part10-", "dup_cnt", "dup_sum"];
955
- var rst = [];
956
- for (var k = 0; k < col1.length; ++k) {
957
- rst[k] = [];
958
- for (var i = 0; i < n_fn; ++i)
959
- rst[k][i] = 0;
960
- }
961
- for (var g in gene) { // count single-copy genes
962
- if (gene[g][0] == null || gene[g][0][0] != 1) continue;
963
- if (gene_nr[g] == null) continue;
964
- if (auto_only && /^(chr)?[XY]$/.test(refpos[g][2])) continue;
965
- for (var i = 0; i < n_fn; ++i) {
966
- if (gene[g][i] == null) {
967
- rst[5][i]++;
968
- if (print_err) print('M', header[i], refpos[g].join("\t"));
969
- } else if (gene[g][i][0] == 1) {
970
- rst[0][i]++;
971
- } else if (gene[g][i][0] > 1) {
972
- rst[1][i]++;
973
- if (print_err) print('D', header[i], refpos[g].join("\t"));
974
- } else if (gene[g][i][1] >= opt.min_cov) {
975
- rst[2][i]++;
976
- if (print_err) print('F', header[i], refpos[g].join("\t"));
977
- } else if (gene[g][i][1] >= 0.5) {
978
- rst[3][i]++;
979
- if (print_err) print('5', header[i], refpos[g].join("\t"));
980
- } else if (gene[g][i][1] >= 0.1) {
981
- rst[4][i]++;
982
- if (print_err) print('1', header[i], refpos[g].join("\t"));
983
- } else {
984
- rst[5][i]++;
985
- if (print_err) print('0', header[i], refpos[g].join("\t")); // TODO: reduce code duplicates...
986
- }
987
- }
988
- }
989
- for (var g in gene) { // count multi-copy genes
990
- if (gene[g][0] == null || gene[g][0][0] <= 1) continue;
991
- if (gene_nr[g] == null) continue;
992
- if (auto_only && /^(chr)?[XY]$/.test(refpos[g][2])) continue;
993
- for (var i = 0; i < n_fn; ++i) {
994
- if (gene[g][i] != null) rst[7][i] += gene[g][i][0];
995
- if (gene[g][i] != null && gene[g][i][0] > 1) {
996
- rst[6][i]++;
997
- } else if (print_err) {
998
- print('d', header[i], gene[g][0][0], refpos[g].join("\t"));
999
- }
1000
- }
1001
- }
1002
- print('H', 'Metric', header.join("\t"));
1003
- for (var k = 0; k < rst.length; ++k) {
1004
- print('X', col1[k], rst[k].join("\t"));
1005
- }
1006
- buf.destroy();
1007
- }
1008
-
1009
- function paf_stat(args)
1010
- {
1011
- var c, gap_out_len = null, count_err = false;
1012
- while ((c = getopt(args, "cl:")) != null)
1013
- if (c == 'l') gap_out_len = parseInt(getopt.arg);
1014
- else if (c == 'c') count_err = true;
1015
-
1016
- if (getopt.ind == args.length) {
1017
- print("Usage: paftools.js stat [-c] [-l gapOutLen] <in.sam>|<in.paf>");
1018
- exit(1);
1019
- }
1020
-
1021
- var buf = new Bytes();
1022
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
1023
- var re = /(\d+)([MIDSHNX=])/g;
1024
-
1025
- var lineno = 0, n_pri = 0, n_2nd = 0, n_seq = 0, n_cigar_64k = 0, l_tot = 0, l_cov = 0;
1026
- var n_gap = [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]], n_sub = 0;
1027
-
1028
- function cov_len(regs)
1029
- {
1030
- regs.sort(function(a,b) {return a[0]-b[0]});
1031
- var st = regs[0][0], en = regs[0][1], l = 0;
1032
- for (var i = 1; i < regs.length; ++i) {
1033
- if (regs[i][0] < en)
1034
- en = en > regs[i][1]? en : regs[i][1];
1035
- else l += en - st, st = regs[i][0], en = regs[i][1];
1036
- }
1037
- l += en - st;
1038
- return l;
1039
- }
1040
-
1041
- var last = null, last_qlen = null, regs = [];
1042
- while (file.readline(buf) >= 0) {
1043
- var line = buf.toString();
1044
- ++lineno;
1045
- if (line.charAt(0) != '@') {
1046
- var t = line.split("\t", 12);
1047
- var m, rs, cigar = null, is_pri = false, is_sam = false, is_rev = false, tname = null;
1048
- var atlen = null, aqlen, qs, qe, mapq, ori_qlen, NM = null, nn = 0;
1049
- if (t.length < 2) continue;
1050
- if (t[4] == '+' || t[4] == '-' || t[4] == '*') { // PAF
1051
- if (t[4] == '*') continue; // unmapped
1052
- if (!/\ts2:i:\d+/.test(line)) {
1053
- ++n_2nd;
1054
- continue;
1055
- }
1056
- if ((m = /\tNM:i:(\d+)/.exec(line)) != null)
1057
- NM = parseInt(m[1]);
1058
- if ((m = /\tnn:i:(\d+)/.exec(line)) != null)
1059
- nn = parseInt(m[1]);
1060
- if ((m = /\tcg:Z:(\S+)/.exec(line)) != null)
1061
- cigar = m[1];
1062
- if (cigar == null) {
1063
- warn("WARNING: no CIGAR at line " + lineno);
1064
- continue;
1065
- }
1066
- tname = t[5];
1067
- qs = parseInt(t[2]), qe = parseInt(t[3]);
1068
- aqlen = qe - qs;
1069
- is_rev = t[4] == '+'? false : true;
1070
- rs = parseInt(t[7]);
1071
- atlen = parseInt(t[8]) - rs;
1072
- mapq = parseInt(t[11]);
1073
- ori_qlen = parseInt(t[1]);
1074
- } else { // SAM
1075
- var flag = parseInt(t[1]);
1076
- if ((flag & 4) || t[2] == '*' || t[5] == '*') continue;
1077
- if (flag & 0x100) {
1078
- ++n_2nd;
1079
- continue;
1080
- }
1081
- if ((m = /\tNM:i:(\d+)/.exec(line)) != null)
1082
- NM = parseInt(m[1]);
1083
- if ((m = /\tnn:i:(\d+)/.exec(line)) != null)
1084
- nn = parseInt(m[1]);
1085
- cigar = t[5];
1086
- tname = t[2];
1087
- rs = parseInt(t[3]) - 1;
1088
- mapq = parseInt(t[4]);
1089
- aqlen = t[9].length;
1090
- is_sam = true;
1091
- is_rev = !!(flag&0x10);
1092
- }
1093
- ++n_pri;
1094
- if (last != t[0]) {
1095
- if (last != null) {
1096
- l_tot += last_qlen;
1097
- l_cov += cov_len(regs);
1098
- }
1099
- regs = [];
1100
- ++n_seq, last = t[0];
1101
- }
1102
- var M = 0, tl = 0, ql = 0, clip = [0, 0], n_cigar = 0, sclip = 0;
1103
- var n_gapo = 0, n_gap_all = 0, l_match = 0;
1104
- while ((m = re.exec(cigar)) != null) {
1105
- var l = parseInt(m[1]);
1106
- ++n_cigar;
1107
- if (m[2] == 'M' || m[2] == '=' || m[2] == 'X') {
1108
- tl += l, ql += l, M += l;
1109
- l_match += l;
1110
- } else if (m[2] == 'I' || m[2] == 'D') {
1111
- var type;
1112
- if (l < 50) type = 0;
1113
- else if (l < 100) type = 1;
1114
- else if (l < 300) type = 2;
1115
- else if (l < 400) type = 3;
1116
- else if (l < 1000) type = 4;
1117
- else type = 5;
1118
- if (m[2] == 'I') ql += l, ++n_gap[0][type];
1119
- else tl += l, ++n_gap[1][type];
1120
- if (gap_out_len != null && l >= gap_out_len)
1121
- print(t[0], ql, is_rev? '-' : '+', tname, rs + tl, m[2], l);
1122
- ++n_gapo, n_gap_all += l;
1123
- } else if (m[2] == 'N') {
1124
- tl += l;
1125
- } else if (m[2] == 'S') {
1126
- clip[M == 0? 0 : 1] = l, sclip += l;
1127
- } else if (m[2] == 'H') {
1128
- clip[M == 0? 0 : 1] = l;
1129
- }
1130
- }
1131
- if (NM != null) {
1132
- var tmp = NM - n_gap_all - nn;
1133
- if (tmp < 0 && nn == 0) warn("WARNING: NM is smaller than the number of gaps at line " + lineno + ": NM=" + NM + ", nn=" + nn + ", G=" + n_gap_all);
1134
- if (tmp < 0) tmp = 0;
1135
- n_sub += tmp;
1136
- }
1137
- if (n_cigar > 65535) ++n_cigar_64k;
1138
- if (ql + sclip != aqlen)
1139
- warn("WARNING: aligned query length is inconsistent with CIGAR at line " + lineno + " (" + (ql+sclip) + " != " + aqlen + ")");
1140
- if (atlen != null && atlen != tl)
1141
- warn("WARNING: aligned reference length is inconsistent with CIGAR at line " + lineno);
1142
- if (is_sam) {
1143
- qs = clip[is_rev? 1 : 0], qe = qs + ql;
1144
- ori_qlen = clip[0] + ql + clip[1];
1145
- }
1146
- if (count_err && NM != null) {
1147
- var n_mm = NM - n_gap_all;
1148
- if (n_mm < 0) warn("WARNING: NM is smaller than the number of gaps at line " + lineno);
1149
- if (n_mm < 0) n_mm = 0;
1150
- print(t[0], ori_qlen, t[11], ori_qlen - (qe - qs), NM, l_match + n_gap_all, n_mm + n_gapo, l_match + n_gapo);
1151
- }
1152
- regs.push([qs, qe]);
1153
- last_qlen = ori_qlen;
1154
- }
1155
- }
1156
- if (regs.length) {
1157
- l_tot += last_qlen;
1158
- l_cov += cov_len(regs);
1159
- }
1160
-
1161
- file.close();
1162
- buf.destroy();
1163
-
1164
- if (gap_out_len == null && !count_err) {
1165
- print("Number of mapped sequences: " + n_seq);
1166
- print("Number of primary alignments: " + n_pri);
1167
- print("Number of secondary alignments: " + n_2nd);
1168
- print("Number of primary alignments with >65535 CIGAR operations: " + n_cigar_64k);
1169
- print("Number of bases in mapped sequences: " + l_tot);
1170
- print("Number of mapped bases: " + l_cov);
1171
- print("Number of substitutions: " + n_sub);
1172
- print("Number of insertions in [0,50): " + n_gap[0][0]);
1173
- print("Number of insertions in [50,100): " + n_gap[0][1]);
1174
- print("Number of insertions in [100,300): " + n_gap[0][2]);
1175
- print("Number of insertions in [300,400): " + n_gap[0][3]);
1176
- print("Number of insertions in [400,1000): " + n_gap[0][4]);
1177
- print("Number of insertions in [1000,inf): " + n_gap[0][5]);
1178
- print("Number of deletions in [0,50): " + n_gap[1][0]);
1179
- print("Number of deletions in [50,100): " + n_gap[1][1]);
1180
- print("Number of deletions in [100,300): " + n_gap[1][2]);
1181
- print("Number of deletions in [300,400): " + n_gap[1][3]);
1182
- print("Number of deletions in [400,1000): " + n_gap[1][4]);
1183
- print("Number of deletions in [1000,inf): " + n_gap[1][5]);
1184
- }
1185
- }
1186
-
1187
- function paf_bedcov(args)
1188
- {
1189
- function read_bed(fn, to_merge, to_dedup)
1190
- {
1191
- var file = new File(fn);
1192
- var buf = new Bytes();
1193
- var h = {};
1194
- while (file.readline(buf) >= 0) {
1195
- var t = buf.toString().split("\t");
1196
- if (h[t[0]] == null)
1197
- h[t[0]] = [];
1198
- var bst = parseInt(t[1]);
1199
- var ben = parseInt(t[2]);
1200
- if (t.length >= 12 && /^\d+$/.test(t[9])) {
1201
- t[9] = parseInt(t[9]);
1202
- var sz = t[10].split(",");
1203
- var st = t[11].split(",");
1204
- for (var i = 0; i < t[9]; ++i) {
1205
- st[i] = parseInt(st[i]);
1206
- sz[i] = parseInt(sz[i]);
1207
- h[t[0]].push([bst + st[i], bst + st[i] + sz[i], 0, 0, 0]);
1208
- }
1209
- } else {
1210
- h[t[0]].push([bst, ben, 0, 0, 0]);
1211
- }
1212
- }
1213
- buf.destroy();
1214
- file.close();
1215
- for (var chr in h) {
1216
- if (to_merge) Interval.merge(h[chr], false);
1217
- else if (to_dedup) Interval.dedup(h[chr], false);
1218
- else Interval.sort(h[chr]);
1219
- Interval.index_end(h[chr]);
1220
- }
1221
- return h;
1222
- }
1223
-
1224
- var c, print_len = false, to_merge = true, to_dedup = false, fn_excl = null;
1225
- while ((c = getopt(args, "pde:")) != null) {
1226
- if (c == 'p') print_len = true;
1227
- else if (c == 'd') to_dedup = true, to_merge = false;
1228
- else if (c == 'e') fn_excl = getopt.arg;
1229
- }
1230
-
1231
- if (args.length - getopt.ind < 2) {
1232
- print("Usage: paftools.js bedcov [options] <regions.bed> <target.bed>");
1233
- print("Options:");
1234
- print(" -e FILE exclude target regions (2nd file) overlapping BED FILE []");
1235
- print(" -p print number of covered bases for each target");
1236
- exit(1);
1237
- }
1238
-
1239
- var excl = fn_excl != null? read_bed(fn_excl, true, false) : null;
1240
- var target = read_bed(args[getopt.ind], to_merge, to_dedup);
1241
-
1242
- var file, buf = new Bytes();
1243
- var tot_len = 0, hit_len = 0;
1244
- file = args[getopt.ind+1] != '-'? new File(args[getopt.ind+1]) : new File();
1245
- while (file.readline(buf) >= 0) {
1246
- var t = buf.toString().split("\t");
1247
- var a = [];
1248
- var bst = parseInt(t[1]);
1249
- var ben = parseInt(t[2]);
1250
- if (t.length >= 12 && /^\d+$/.test(t[9])) { // BED12
1251
- t[9] = parseInt(t[9]);
1252
- var sz = t[10].split(",");
1253
- var st = t[11].split(",");
1254
- for (var i = 0; i < t[9]; ++i) {
1255
- st[i] = parseInt(st[i]);
1256
- sz[i] = parseInt(sz[i]);
1257
- a.push([bst + st[i], bst + st[i] + sz[i], false]);
1258
- }
1259
- } else a.push([bst, ben, false]); // 3-column BED
1260
- var feat_len = 0;
1261
- for (var i = 0; i < a.length; ++i) {
1262
- if (excl != null && excl[t[0]] != null) {
1263
- var oe = Interval.find_ovlp(excl[t[0]], a[i][0], a[i][1]);
1264
- if (oe.length > 0)
1265
- continue;
1266
- }
1267
- a[i][2] = true;
1268
- feat_len += a[i][1] - a[i][0];
1269
- }
1270
- tot_len += feat_len;
1271
- if (target[t[0]] == null) continue;
1272
- var b = [];
1273
- for (var i = 0; i < a.length; ++i) {
1274
- if (!a[i][2]) continue;
1275
- var o = Interval.find_ovlp(target[t[0]], a[i][0], a[i][1]);
1276
- for (var j = 0; j < o.length; ++j) {
1277
- var max_st = o[j][0] > a[i][0]? o[j][0] : a[i][0];
1278
- var min_en = o[j][1] < a[i][1]? o[j][1] : a[i][1];
1279
- b.push([max_st, min_en]);
1280
- o[j][2] += min_en - max_st;
1281
- ++o[j][3];
1282
- if (max_st == o[j][0] && min_en == o[j][1])
1283
- ++o[j][4];
1284
- }
1285
- }
1286
- // find the length covered
1287
- var feat_hit_len = 0;
1288
- if (b.length > 0) {
1289
- b.sort(function(a,b) {return a[0]-b[0]});
1290
- var st = b[0][0], en = b[0][1];
1291
- for (var i = 1; i < b.length; ++i) {
1292
- if (b[i][0] <= en) en = en > b[i][1]? en : b[i][1];
1293
- else feat_hit_len += en - st, st = b[i][0], en = b[i][1];
1294
- }
1295
- feat_hit_len += en - st;
1296
- }
1297
- hit_len += feat_hit_len;
1298
- if (print_len) print('F', t.slice(0, 4).join("\t"), feat_len, feat_hit_len);
1299
- }
1300
- file.close();
1301
-
1302
- buf.destroy();
1303
-
1304
- warn("# target bases: " + tot_len);
1305
- warn("# target bases overlapping regions: " + hit_len + ' (' + (100.0 * hit_len / tot_len).toFixed(2) + '%)');
1306
- }
1307
-
1308
- function paf_vcfpair(args)
1309
- {
1310
- var c, is_male = false, sample = 'syndip', hgver = null;
1311
- var PAR = { '37':[[0, 2699520], [154931043, 155260560]] };
1312
- while ((c = getopt(args, "ms:g:")) != null) {
1313
- if (c == 'm') is_male = true;
1314
- else if (c == 's') sample = getopt.arg;
1315
- else if (c == 'g') hgver = getopt.arg;
1316
- }
1317
- if (is_male && (hgver == null || PAR[hgver] == null))
1318
- throw("for a male, -g must be specified to properly handle PARs on chrX");
1319
-
1320
- if (getopt.ind == args.length) {
1321
- print("Usage: paftools.js vcfpair [options] <in.pair.vcf>");
1322
- print("Options:");
1323
- print(" -m the sample is male");
1324
- print(" -g STR human genome version '37' []");
1325
- print(" -s STR sample name [" + sample + "]");
1326
- exit(1);
1327
- }
1328
-
1329
- var re_ctg = is_male? /^(chr)?([0-9]+|X|Y)$/ : /^(chr)?([0-9]+|X)$/;
1330
- var label = ['1', '2'];
1331
- var buf = new Bytes();
1332
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
1333
- while (file.readline(buf) >= 0) {
1334
- var m, line = buf.toString();
1335
- if (line.charAt(0) == '#') {
1336
- if (/^##(source|reference)=/.test(line)) continue;
1337
- if ((m = /^##contig=.*ID=([^\s,]+)/.exec(line)) != null) {
1338
- if (!re_ctg.test(m[1])) continue;
1339
- } else if (/^#CHROM/.test(line)) {
1340
- var t = line.split("\t");
1341
- --t.length;
1342
- t[t.length-1] = sample;
1343
- line = t.join("\t");
1344
- print('##FILTER=<ID=HET1,Description="Heterozygous in the first haplotype">');
1345
- print('##FILTER=<ID=HET2,Description="Heterozygous in the second haplotype">');
1346
- print('##FILTER=<ID=GAP1,Description="Uncalled in the first haplotype">');
1347
- print('##FILTER=<ID=GAP2,Description="Uncalled in the second haplotype">');
1348
- }
1349
- print(line);
1350
- continue;
1351
- }
1352
- var t = line.split("\t");
1353
- if (!re_ctg.test(t[0])) continue;
1354
- var GT = null, AD = null, FILTER = [], HT = [null, null];
1355
- for (var i = 0; i < 2; ++i) {
1356
- if ((m = /^(\.|[0-9]+)\/(\.|[0-9]+):(\S+)/.exec(t[9+i])) == null) {
1357
- warn(line);
1358
- throw Error("malformatted VCF");
1359
- }
1360
- var s = m[3].split(",");
1361
- if (AD == null) {
1362
- AD = [];
1363
- for (var j = 0; j < s.length; ++j)
1364
- AD[j] = 0;
1365
- }
1366
- for (var j = 0; j < s.length; ++j)
1367
- AD[j] += parseInt(s[j]);
1368
- if (m[1] == '.') {
1369
- FILTER.push('GAP' + label[i]);
1370
- HT[i] = '.';
1371
- } else if (m[1] != m[2]) {
1372
- FILTER.push('HET' + label[i]);
1373
- HT[i] = '.';
1374
- } else HT[i] = m[1];
1375
- }
1376
- --t.length;
1377
- // test if this is in a haploid region
1378
- var hap = 0, st = parseInt(t[1]), en = st + t[3].length;
1379
- if (is_male) {
1380
- if (/^(chr)?X/.test(t[0])) {
1381
- if (hgver != null && PAR[hgver] != null) {
1382
- var r = PAR[hgver], in_par = false;
1383
- for (var i = 0; i < r.length; ++i)
1384
- if (r[i][0] <= st && en <= r[i][1])
1385
- in_par = true;
1386
- hap = in_par? 0 : 2;
1387
- }
1388
- } else if (/^(chr)?Y/.test(t[0])) {
1389
- hap = 1;
1390
- }
1391
- }
1392
- // special treatment for haploid regions
1393
- if (hap > 0 && FILTER.length == 1) {
1394
- if ((hap == 2 && FILTER[0] == "GAP1") || (hap == 1 && FILTER[0] == "GAP2"))
1395
- FILTER.length = 0;
1396
- }
1397
- // update VCF
1398
- t[5] = 30; // fake QUAL
1399
- t[6] = FILTER.length? FILTER.join(";") : ".";
1400
- t[9] = HT.join("|") + ":" + AD.join(",");
1401
- print(t.join("\t"));
1402
- }
1403
- file.close();
1404
- buf.destroy();
1405
- }
1406
-
1407
- /**********************
1408
- * Conversion related *
1409
- **********************/
1410
-
1411
- function paf_view(args)
1412
- {
1413
- var c, line_len = 80, fmt = "aln";
1414
- while ((c = getopt(args, "f:l:")) != null) {
1415
- if (c == 'f') {
1416
- fmt = getopt.arg;
1417
- if (fmt != "aln" && fmt != "lastz-cigar" && fmt != "maf")
1418
- throw Error("format must be one of aln, lastz-cigar and maf");
1419
- } else if (c == 'l') line_len = parseInt(getopt.arg);
1420
- }
1421
- if (line_len == 0) line_len = 0x7fffffff;
1422
-
1423
- if (getopt.ind == args.length) {
1424
- print("Usage: paftools.js view [options] <in.paf>");
1425
- print("Options:");
1426
- print(" -f STR output format: aln (BLAST-like), maf or lastz-cigar [aln]");
1427
- print(" -l INT line length in BLAST-like output [80]");
1428
- exit(1);
1429
- }
1430
-
1431
- function padding_str(x, len, right)
1432
- {
1433
- var s = x.toString();
1434
- if (s.length < len) {
1435
- if (right) s += Array(len - s.length + 1).join(" ");
1436
- else s = Array(len - s.length + 1).join(" ") + s;
1437
- }
1438
- return s;
1439
- }
1440
-
1441
- function update_aln(s_ref, s_qry, s_mid, type, seq, slen)
1442
- {
1443
- var l = type == '*'? 1 : seq.length;
1444
- if (type == '=' || type == ':') {
1445
- s_ref.set(seq);
1446
- s_qry.set(seq);
1447
- s_mid.set(Array(l+1).join("|"));
1448
- slen[0] += l, slen[1] += l;
1449
- } else if (type == '*') {
1450
- s_ref.set(seq.charAt(0));
1451
- s_qry.set(seq.charAt(1));
1452
- s_mid.set(' ');
1453
- slen[0] += 1, slen[1] += 1;
1454
- } else if (type == '+') {
1455
- s_ref.set(Array(l+1).join("-"));
1456
- s_qry.set(seq);
1457
- s_mid.set(Array(l+1).join(" "));
1458
- slen[1] += l;
1459
- } else if (type == '-') {
1460
- s_ref.set(seq);
1461
- s_qry.set(Array(l+1).join("-"));
1462
- s_mid.set(Array(l+1).join(" "));
1463
- slen[0] += l;
1464
- }
1465
- }
1466
-
1467
- function print_aln(rs, qs, strand, slen, elen, s_ref, s_qry, s_mid)
1468
- {
1469
- print(["Ref+:", padding_str(rs + slen[0] + 1, 10, false), s_ref.toString(), padding_str(rs + elen[0], 10, true)].join(" "));
1470
- print(" " + s_mid.toString());
1471
- var st, en;
1472
- if (strand == '+') st = qs + slen[1] + 1, en = qs + elen[1];
1473
- else st = qs - slen[1], en = qs - elen[1] + 1;
1474
- print(["Qry" + strand + ":", padding_str(st, 10, false), s_qry.toString(), padding_str(en, 10, true)].join(" "));
1475
- }
1476
-
1477
- var s_ref = new Bytes(), s_qry = new Bytes(), s_mid = new Bytes(); // these are used to show padded alignment
1478
- var re_cs = /([:=\-\+\*])(\d+|[A-Za-z]+)/g;
1479
- var re_cg = /(\d+)([MIDNSHP=X])/g;
1480
-
1481
- var buf = new Bytes();
1482
- var file = args[getopt.ind] == "-"? new File() : new File(args[getopt.ind]);
1483
- var lineno = 0;
1484
- if (fmt == "maf") print("##maf version=1\n");
1485
- while (file.readline(buf) >= 0) {
1486
- var m, line = buf.toString();
1487
- var t = line.split("\t", 12);
1488
- ++lineno;
1489
- s_ref.length = s_qry.length = s_mid.length = 0;
1490
- var slen = [0, 0], elen = [0, 0];
1491
- if (fmt == "lastz-cigar") { // LASTZ-cigar output
1492
- var cg = (m = /\tcg:Z:(\S+)/.exec(line)) != null? m[1] : null;
1493
- if (cg == null) {
1494
- warn("WARNING: converting to LASTZ-cigar format requires the 'cg' tag, which is absent on line " + lineno);
1495
- continue;
1496
- }
1497
- var score = (m = /\tAS:i:(\d+)/.exec(line)) != null? m[1] : 0;
1498
- var out = ['cigar:', t[0], t[2], t[3], t[4], t[5], t[7], t[8], '+', score];
1499
- while ((m = re_cg.exec(cg)) != null)
1500
- out.push(m[2], m[1]);
1501
- print(out.join(" "));
1502
- } else if (fmt == "maf") { // MAF output
1503
- var cs = (m = /\tcs:Z:(\S+)/.exec(line)) != null? m[1] : null;
1504
- if (cs == null) {
1505
- warn("WARNING: converting to MAF requires the 'cs' tag, which is absent on line " + lineno);
1506
- continue;
1507
- }
1508
- while ((m = re_cs.exec(cs)) != null) {
1509
- if (m[1] == ':')
1510
- throw Error("converting to MAF only works with 'minimap2 --cs=long'");
1511
- update_aln(s_ref, s_qry, s_mid, m[1], m[2], elen);
1512
- }
1513
- var score = (m = /\tAS:i:(\d+)/.exec(line)) != null? parseInt(m[1]) : 0;
1514
- var len = t[0].length > t[5].length? t[0].length : t[5].length;
1515
- print("a " + score);
1516
- print(["s", padding_str(t[5], len, true), padding_str(t[7], 10, false), padding_str(parseInt(t[8]) - parseInt(t[7]), 10, false),
1517
- "+", padding_str(t[6], 10, false), s_ref.toString()].join(" "));
1518
- var qs, qe, ql = parseInt(t[1]);
1519
- if (t[4] == '+') {
1520
- qs = parseInt(t[2]);
1521
- qe = parseInt(t[3]);
1522
- } else {
1523
- qs = ql - parseInt(t[3]);
1524
- qe = ql - parseInt(t[2]);
1525
- }
1526
- print(["s", padding_str(t[0], len, true), padding_str(qs, 10, false), padding_str(qe - qs, 10, false),
1527
- t[4], padding_str(ql, 10, false), s_qry.toString()].join(" "));
1528
- print("");
1529
- } else { // BLAST-like output
1530
- var cs = (m = /\tcs:Z:(\S+)/.exec(line)) != null? m[1] : null;
1531
- if (cs == null) {
1532
- warn("WARNING: converting to BLAST-like alignment requires the 'cs' tag, which is absent on line " + lineno);
1533
- continue;
1534
- }
1535
- var n_mm = 0, n_oi = 0, n_od = 0, n_ei = 0, n_ed = 0;
1536
- while ((m = re_cs.exec(cs)) != null) {
1537
- if (m[1] == '*') ++n_mm;
1538
- else if (m[1] == '+') ++n_oi, n_ei += m[2].length;
1539
- else if (m[1] == '-') ++n_od, n_ed += m[2].length;
1540
- }
1541
- line = line.replace(/\tc[sg]:Z:\S+/g, ""); // get rid of cs or cg tags
1542
- print('>' + line + "\tmm:i:"+n_mm + "\toi:i:"+n_oi + "\tei:i:"+n_ei + "\tod:i:"+n_od + "\ted:i:"+n_ed);
1543
- var rs = parseInt(t[7]), qs = t[4] == '+'? parseInt(t[2]) : parseInt(t[3]);
1544
- var n_blocks = 0;
1545
- while ((m = re_cs.exec(cs)) != null) {
1546
- if (m[1] == ':') m[2] = Array(parseInt(m[2]) + 1).join("=");
1547
- var start = 0, rest = m[1] == '*'? 1 : m[2].length;
1548
- while (rest > 0) {
1549
- var l_proc;
1550
- if (s_ref.length + rest >= line_len) {
1551
- l_proc = line_len - s_ref.length;
1552
- update_aln(s_ref, s_qry, s_mid, m[1], m[1] == '*'? m[2] : m[2].substr(start, l_proc), elen);
1553
- if (n_blocks > 0) print("");
1554
- print_aln(rs, qs, t[4], slen, elen, s_ref, s_qry, s_mid);
1555
- ++n_blocks;
1556
- s_ref.length = s_qry.length = s_mid.length = 0;
1557
- slen[0] = elen[0], slen[1] = elen[1];
1558
- } else {
1559
- l_proc = rest;
1560
- update_aln(s_ref, s_qry, s_mid, m[1], m[1] == '*'? m[2] : m[2].substr(start, l_proc), elen);
1561
- }
1562
- rest -= l_proc, start += l_proc;
1563
- }
1564
- }
1565
- if (s_ref.length > 0) {
1566
- if (n_blocks > 0) print("");
1567
- print_aln(rs, qs, t[4], slen, elen, s_ref, s_qry, s_mid);
1568
- ++n_blocks;
1569
- }
1570
- print("//");
1571
- }
1572
- }
1573
- file.close();
1574
- buf.destroy();
1575
-
1576
- s_ref.destroy(); s_qry.destroy(); s_mid.destroy();
1577
- }
1578
-
1579
- function paf_gff2bed(args)
1580
- {
1581
- var c, fn_ucsc_fai = null, is_short = false, keep_gff = false, print_junc = false, output_gene = false, ens_canon_only = false;
1582
- while ((c = getopt(args, "u:sgjGe")) != null) {
1583
- if (c == 'u') fn_ucsc_fai = getopt.arg;
1584
- else if (c == 's') is_short = true;
1585
- else if (c == 'g') keep_gff = true;
1586
- else if (c == 'j') print_junc = true;
1587
- else if (c == 'G') output_gene = true;
1588
- else if (c == 'e') ens_canon_only = true;
1589
- }
1590
-
1591
- if (getopt.ind == args.length) {
1592
- print("Usage: paftools.js gff2bed [options] <in.gff>");
1593
- print("Options:");
1594
- print(" -j output junction BED");
1595
- print(" -s print names in the short form");
1596
- print(" -u FILE hg38.fa.fai for chr name conversion");
1597
- print(" -e only show transcript tagged with 'Ensembl_canonical'");
1598
- print(" -g output GFF (used with -u)");
1599
- exit(1);
1600
- }
1601
-
1602
- var ens2ucsc = {};
1603
- if (fn_ucsc_fai != null) {
1604
- var buf = new Bytes();
1605
- var file = new File(fn_ucsc_fai);
1606
- while (file.readline(buf) >= 0) {
1607
- var t = buf.toString().split("\t");
1608
- var s = t[0];
1609
- if (/_(random|alt|decoy)$/.test(s)) {
1610
- s = s.replace(/_(random|alt|decoy)$/, '');
1611
- s = s.replace(/^chr\S+_/, '');
1612
- } else {
1613
- s = s.replace(/^chrUn_/, '');
1614
- }
1615
- s = s.replace(/v(\d+)/, ".$1");
1616
- if (s != t[0]) ens2ucsc[s] = t[0];
1617
- }
1618
- file.close();
1619
- buf.destroy();
1620
- }
1621
-
1622
- var colors = {
1623
- 'protein_coding':'0,128,255',
1624
- 'mRNA':'0,128,255',
1625
- 'lincRNA':'0,192,0',
1626
- 'snRNA':'0,192,0',
1627
- 'miRNA':'0,192,0',
1628
- 'misc_RNA':'0,192,0'
1629
- };
1630
-
1631
- function print_bed12(exons, cds_st, cds_en, is_short, print_junc)
1632
- {
1633
- if (exons.length == 0) return;
1634
- var name = is_short? exons[0][7] + "|" + exons[0][5] : exons[0].slice(4, 7).join("|");
1635
- var a = exons.sort(function(a,b) {return a[1]-b[1]});
1636
- if (print_junc) {
1637
- for (var i = 1; i < a.length; ++i)
1638
- print(a[i][0], a[i-1][2], a[i][1], name, 1000, a[i][3]);
1639
- return;
1640
- }
1641
- var sizes = [], starts = [], st, en;
1642
- st = a[0][1];
1643
- en = a[a.length - 1][2];
1644
- if (cds_st == 1<<30) cds_st = st;
1645
- if (cds_en == 0) cds_en = en;
1646
- if (cds_st < st || cds_en > en)
1647
- throw Error("inconsistent thick start or end for transcript " + a[0][4]);
1648
- for (var i = 0; i < a.length; ++i) {
1649
- sizes.push(a[i][2] - a[i][1]);
1650
- starts.push(a[i][1] - st);
1651
- }
1652
- var color = colors[a[0][5]];
1653
- if (color == null) color = '196,196,196';
1654
- print(a[0][0], st, en, name, 1000, a[0][3], cds_st, cds_en, color, a.length, sizes.join(",") + ",", starts.join(",") + ",");
1655
- }
1656
-
1657
- var re_gtf = /\b(transcript_id|transcript_type|transcript_biotype|gene_name|gene_id|gbkey|transcript_name|tag) "([^"]+)";/g;
1658
- var re_gff3 = /\b(transcript_id|transcript_type|transcript_biotype|gene_name|gene_id|gbkey|transcript_name)=([^;]+)/g;
1659
- var re_gtf_gene = /\b(gene_id|gene_type|gene_name) "([^;]+)";/g;
1660
- var re_gff3_gene = /\b(gene_id|gene_type|source_gene|gene_biotype|gene_name)=([^;]+);/g;
1661
- var buf = new Bytes();
1662
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
1663
-
1664
- var exons = [], cds_st = 1<<30, cds_en = 0, last_id = null;
1665
- while (file.readline(buf) >= 0) {
1666
- var t = buf.toString().split("\t");
1667
- if (keep_gff) {
1668
- if (t[0].charAt(0) != '#' && ens2ucsc[t[0]] != null)
1669
- t[0] = ens2ucsc[t[0]];
1670
- print(t.join("\t"));
1671
- continue;
1672
- }
1673
- if (t[0].charAt(0) == '#') continue;
1674
- if (output_gene) {
1675
- var id = null, src = null, biotype = null, type = "", name = "N/A";
1676
- if (t[2] != "gene") continue;
1677
- while ((m = re_gtf_gene.exec(t[8])) != null) {
1678
- if (m[1] == "gene_id") id = m[2];
1679
- else if (m[1] == "gene_type") type = m[2];
1680
- else if (m[1] == "gene_name") name = m[2];
1681
- }
1682
- while ((m = re_gff3_gene.exec(t[8])) != null) {
1683
- if (m[1] == "gene_id") id = m[2];
1684
- else if (m[1] == "source_gene") src = m[2];
1685
- else if (m[1] == "gene_type") type = m[2];
1686
- else if (m[1] == "gene_biotype") biotype = m[2];
1687
- else if (m[1] == "gene_name") name = m[2];
1688
- }
1689
- if (src != null) id = src;
1690
- if (type == "" && biotype != null) type = biotype;
1691
- print(t[0], parseInt(t[3]) - 1, t[4], [id, type, name].join("|"), 1000, t[6]);
1692
- continue;
1693
- }
1694
- if (t[2] != "CDS" && t[2] != "exon") continue;
1695
- t[3] = parseInt(t[3]) - 1;
1696
- t[4] = parseInt(t[4]);
1697
- var id = null, type = "", name = "N/A", biotype = "", m, tname = "N/A", ens_canonical = false;
1698
- while ((m = re_gtf.exec(t[8])) != null) {
1699
- if (m[1] == "transcript_id") id = m[2];
1700
- else if (m[1] == "transcript_type") type = m[2];
1701
- else if (m[1] == "transcript_biotype" || m[1] == "gbkey") biotype = m[2];
1702
- else if (m[1] == "gene_name" || m[1] == "gene_id") name = m[2];
1703
- else if (m[1] == "transcript_name") tname = m[2];
1704
- else if (m[1] == "tag" && m[2] == "Ensembl_canonical") ens_canonical = true;
1705
- }
1706
- while ((m = re_gff3.exec(t[8])) != null) {
1707
- if (m[1] == "transcript_id") id = m[2];
1708
- else if (m[1] == "transcript_type") type = m[2];
1709
- else if (m[1] == "transcript_biotype" || m[1] == "gbkey") biotype = m[2];
1710
- else if (m[1] == "gene_name" || m[1] == "gene_id") name = m[2];
1711
- else if (m[1] == "transcript_name") tname = m[2];
1712
- }
1713
- if (ens_canon_only && !ens_canonical) continue;
1714
- if (type == "" && biotype != "") type = biotype;
1715
- if (id == null) throw Error("No transcript_id");
1716
- if (id != last_id) {
1717
- print_bed12(exons, cds_st, cds_en, is_short, print_junc);
1718
- exons = [], cds_st = 1<<30, cds_en = 0;
1719
- last_id = id;
1720
- }
1721
- if (t[2] == "CDS") {
1722
- cds_st = cds_st < t[3]? cds_st : t[3];
1723
- cds_en = cds_en > t[4]? cds_en : t[4];
1724
- } else if (t[2] == "exon") {
1725
- if (fn_ucsc_fai != null) {
1726
- if (ens2ucsc[t[0]] != null)
1727
- t[0] = ens2ucsc[t[0]];
1728
- else if (/^[A-Z]+\d+\.\d+$/.test(t[0]))
1729
- t[0] = t[0].replace(/([A-Z]+\d+)\.(\d+)/, "chrUn_$1v$2");
1730
- }
1731
- exons.push([t[0], t[3], t[4], t[6], id, type, name, tname]);
1732
- }
1733
- }
1734
- if (last_id != null)
1735
- print_bed12(exons, cds_st, cds_en, is_short, print_junc);
1736
-
1737
- file.close();
1738
- buf.destroy();
1739
- }
1740
-
1741
- function paf_sam2paf(args)
1742
- {
1743
- var c, pri_only = false, long_cs = false, pri_pri_only = false;
1744
- while ((c = getopt(args, "pPL")) != null) {
1745
- if (c == 'p') pri_only = true;
1746
- else if (c == 'P') pri_pri_only = pri_only = true;
1747
- else if (c == 'L') long_cs = true;
1748
- }
1749
- if (args.length == getopt.ind) {
1750
- print("Usage: paftools.js sam2paf [options] <in.sam>");
1751
- print("Options:");
1752
- print(" -p convert primary or supplementary alignments only");
1753
- print(" -P convert primary alignments only");
1754
- print(" -L output the cs tag in the long form");
1755
- exit(1);
1756
- }
1757
-
1758
- var file = args[getopt.ind] == "-"? new File() : new File(args[getopt.ind]);
1759
- var buf = new Bytes();
1760
- var re = /(\d+)([MIDSHNX=])/g, re_MD = /(\d+)|(\^[A-Za-z]+)|([A-Za-z])/g, re_tag = /\t(\S\S:[AZif]):(\S+)/g;
1761
-
1762
- var ctg_len = {}, lineno = 0;
1763
- while (file.readline(buf) >= 0) {
1764
- var m, n_cigar = 0, line = buf.toString();
1765
- ++lineno;
1766
- if (line.charAt(0) == '@') {
1767
- if (/^@SQ/.test(line)) {
1768
- var name = (m = /\tSN:(\S+)/.exec(line)) != null? m[1] : null;
1769
- var l = (m = /\tLN:(\d+)/.exec(line)) != null? parseInt(m[1]) : null;
1770
- if (name != null && l != null) ctg_len[name] = l;
1771
- }
1772
- continue;
1773
- }
1774
- var t = line.split("\t", 11);
1775
- var flag = parseInt(t[1]);
1776
- if (t[9] != '*' && t[10] != '*' && t[9].length != t[10].length)
1777
- throw Error("at line " + lineno + ": inconsistent SEQ and QUAL lengths - " + t[9].length + " != " + t[10].length);
1778
- if (t[2] == '*' || (flag&4) || t[5] == '*') continue;
1779
- if (pri_only && (flag&0x100)) continue;
1780
- if (pri_pri_only && (flag&0x900)) continue;
1781
- var tlen = ctg_len[t[2]];
1782
- if (tlen == null) throw Error("at line " + lineno + ": can't find the length of contig " + t[2]);
1783
- // find tags
1784
- var nn = 0, NM = null, MD = null, cs_str = null, md_list = [];
1785
- while ((m = re_tag.exec(line)) != null) {
1786
- if (m[1] == "NM:i") NM = parseInt(m[2]);
1787
- else if (m[1] == "nn:i") nn = parseInt(m[2]);
1788
- else if (m[1] == "MD:Z") MD = m[2];
1789
- else if (m[1] == "cs:Z") cs_str = m[2];
1790
- }
1791
- if (t[9] == '*') MD = cs_str = null;
1792
- // infer various lengths from CIGAR
1793
- var clip = [0, 0], soft_clip = 0, I = [0, 0], D = [0, 0], M = 0, N = 0, mm = 0, have_M = false, have_ext = false, cigar = [];
1794
- while ((m = re.exec(t[5])) != null) {
1795
- var l = parseInt(m[1]), op = m[2];
1796
- if (op == 'M') M += l, have_M = true;
1797
- else if (op == 'I') ++I[0], I[1] += l;
1798
- else if (op == 'D') ++D[0], D[1] += l;
1799
- else if (op == 'N') N += l;
1800
- else if (op == 'S') clip[n_cigar == 0? 0 : 1] = l, soft_clip += l;
1801
- else if (op == 'H') clip[n_cigar == 0? 0 : 1] = l;
1802
- else if (op == '=') M += l, have_ext = true, op = 'M';
1803
- else if (op == 'X') M += l, mm += l, have_ext = true, op = 'M';
1804
- ++n_cigar;
1805
- if (MD != null && op != 'H') {
1806
- if (cigar.length > 0 && cigar[cigar.length-1][1] == op)
1807
- cigar[cigar.length-1][0] += l;
1808
- else cigar.push([l, op]);
1809
- }
1810
- }
1811
- var ql = M + I[1] + soft_clip;
1812
- var tl = M + D[1] + N;
1813
- var ts = parseInt(t[3]) - 1, te = ts + tl;
1814
- // checking coordinate and length consistencies
1815
- if (n_cigar > 65535)
1816
- warn("WARNING at line " + lineno + ": " + n_cigar + " CIGAR operations");
1817
- if (te > tlen) {
1818
- warn("WARNING at line " + lineno + ": alignment end position larger than ref length; skipped");
1819
- continue;
1820
- }
1821
- if (t[9] != '*' && t[9].length != ql) {
1822
- warn("WARNING at line " + lineno + ": SEQ length inconsistent with CIGAR (" + t[9].length + " != " + ql + "); skipped");
1823
- continue;
1824
- }
1825
- // parse MD
1826
- var cs = [];
1827
- if (MD != null && cs_str == null && t[9] != "*") {
1828
- var k = 0, cx = 0, cy = 0, mx = 0, my = 0; // cx: cigar ref position; cy: cigar query; mx: MD ref; my: MD query
1829
- while ((m = re_MD.exec(MD)) != null) {
1830
- if (m[2] != null) { // deletion from the reference
1831
- var len = m[2].length - 1;
1832
- cs.push('-', m[2].substr(1));
1833
- mx += len, cx += len, ++k;
1834
- } else { // copy or mismatch
1835
- var ml = m[1] != null? parseInt(m[1]) : 1;
1836
- while (k < cigar.length && cigar[k][1] != 'D') {
1837
- var cl = cigar[k][0], op = cigar[k][1];
1838
- if (op == 'M') {
1839
- if (my + ml < cy + cl) {
1840
- if (ml > 0) {
1841
- if (m[3] != null) cs.push('*', m[3], t[9][my]);
1842
- else if (long_cs) cs.push('=', t[9].substr(my, ml));
1843
- else cs.push(':', ml);
1844
- }
1845
- mx += ml, my += ml, ml = 0;
1846
- break;
1847
- } else {
1848
- var dl = cy + cl - my;
1849
- if (long_cs) cs.push('=', t[9].substr(my, dl));
1850
- else cs.push(':', dl);
1851
- cx += cl, cy += cl, ++k;
1852
- mx += dl, my += dl, ml -= dl;
1853
- }
1854
- } else if (op == 'I') {
1855
- cs.push('+', t[9].substr(cy, cl));
1856
- cy += cl, my += cl, ++k;
1857
- } else if (op == 'S') {
1858
- cy += cl, my += cl, ++k;
1859
- } else throw Error("at line " + lineno + ": inconsistent MD tag");
1860
- }
1861
- if (ml != 0) throw Error("at line " + lineno + ": inconsistent MD tag");
1862
- }
1863
- }
1864
- if (cx != mx || cy != my) throw Error("at line " + lineno + ": inconsistent MD tag");
1865
- }
1866
- // compute matching length, block length and calibrate NM
1867
- if (have_ext && !have_M) { // extended CIGAR
1868
- if (NM != null && NM != I[1] + D[1] + mm)
1869
- warn("WARNING at line " + lineno + ": NM is different from sum of gaps and mismatches");
1870
- NM = I[1] + D[1] + mm;
1871
- } else if (NM != null) { // standard CIGAR; NM present
1872
- if (NM < I[1] + D[1]) {
1873
- warn("WARNING at line " + lineno + ": NM is less than the total number of gaps (" + NM + " < " + (I[1]+D[1]) + ")");
1874
- NM = I[1] + D[1];
1875
- }
1876
- mm = NM - (I[1] + D[1]);
1877
- } else { // no way to compute mm
1878
- warn("WARNING at line " + lineno + ": unable to find the number of mismatches; assuming zero");
1879
- mm = 0;
1880
- }
1881
- var mlen = M - mm;
1882
- var blen = M + I[1] + D[1];
1883
- // find query name, start and end
1884
- var qlen = M + I[1] + clip[0] + clip[1];
1885
- var qname = t[0], qs, qe;
1886
- if ((flag&1) && (flag&0x40)) qname += '/1';
1887
- if ((flag&1) && (flag&0x80)) qname += '/2';
1888
- if (flag&16) qs = clip[1], qe = qlen - clip[0];
1889
- else qs = clip[0], qe = qlen - clip[1];
1890
- // optional tags
1891
- var type = flag&0x100? 'S' : 'P';
1892
- var tags = ["tp:A:" + type];
1893
- if (NM != null) {
1894
- tags.push("NM:i:"+NM);
1895
- tags.push("mm:i:"+mm);
1896
- }
1897
- tags.push("gn:i:"+(I[1]+D[1]), "go:i:"+(I[0]+D[0]), "cg:Z:" + t[5].replace(/\d+[SH]/g, ''));
1898
- if (cs_str != null) tags.push("cs:Z:" + cs_str);
1899
- else if (cs.length > 0) tags.push("cs:Z:" + cs.join(""));
1900
- // print out
1901
- var a = [qname, qlen, qs, qe, flag&16? '-' : '+', t[2], tlen, ts, te, mlen, blen, t[4]];
1902
- print(a.join("\t"), tags.join("\t"));
1903
- }
1904
-
1905
- buf.destroy();
1906
- file.close();
1907
- }
1908
-
1909
- function paf_delta2paf(args)
1910
- {
1911
- if (args.length == 0) {
1912
- print("Usage: paftools.js delta2paf <in.delta>");
1913
- exit(1);
1914
- }
1915
-
1916
- var buf = new Bytes();
1917
- var file = args[0] == '-'? new File() : new File(args[0]);
1918
-
1919
- var rname, qname, rlen, qlen, qs, qe, rs, re, strand, NM, cigar, x, y, seen_gt = false;
1920
- while (file.readline(buf) >= 0) {
1921
- var m, line = buf.toString();
1922
- if ((m = /^>(\S+)\s+(\S+)\s+(\d+)\s+(\d+)/.exec(line)) != null) {
1923
- rname = m[1], qname = m[2], rlen = parseInt(m[3]), qlen = parseInt(m[4]);
1924
- seen_gt = true;
1925
- continue;
1926
- }
1927
- if (!seen_gt) continue;
1928
- var t = line.split(" ");
1929
- if (t.length == 7) {
1930
- for (var i = 0; i < 5; ++i)
1931
- t[i] = parseInt(t[i]);
1932
- strand = ((t[0] < t[1] && t[2] < t[3]) || (t[0] > t[1] && t[2] > t[3]))? 1 : -1;
1933
- rs = (t[0] < t[1]? t[0] : t[1]) - 1;
1934
- re = t[1] > t[0]? t[1] : t[0];
1935
- qs = (t[2] < t[3]? t[2] : t[3]) - 1;
1936
- qe = t[3] > t[2]? t[3] : t[2];
1937
- x = y = 0;
1938
- NM = parseInt(t[4]);
1939
- cigar = [];
1940
- } else if (t.length == 1) {
1941
- var d = parseInt(t[0]);
1942
- if (d == 0) {
1943
- var blen = 0, cigar_str = [];
1944
- if (re - rs - x != qe - qs - y) throw Error("inconsisnt alignment");
1945
- cigar.push((re - rs - x) << 4);
1946
- for (var i = 0; i < cigar.length; ++i) {
1947
- blen += cigar[i] >> 4;
1948
- cigar_str.push((cigar[i]>>4) + "MID".charAt(cigar[i]&0xf));
1949
- }
1950
- print([qname, qlen, qs, qe, strand > 0? '+' : '-', rname, rlen, rs, re, blen - NM, blen, 0, "NM:i:" + NM, "cg:Z:" + cigar_str.join("")].join("\t"));
1951
- } else if (d > 0) {
1952
- var l = d - 1;
1953
- x += l + 1, y += l;
1954
- if (l > 0) cigar.push(l<<4);
1955
- if (cigar.length > 0 && (cigar[cigar.length-1]&0xf) == 2)
1956
- cigar[cigar.length-1] += 1<<4;
1957
- else cigar.push(1<<4|2); // deletion
1958
- } else {
1959
- var l = -d - 1;
1960
- x += l, y += l + 1;
1961
- if (l > 0) cigar.push(l<<4);
1962
- if (cigar.length > 0 && (cigar[cigar.length-1]&0xf) == 1)
1963
- cigar[cigar.length-1] += 1<<4;
1964
- else cigar.push(1<<4|1); // insertion
1965
- }
1966
- }
1967
- }
1968
- file.close();
1969
- buf.destroy();
1970
- }
1971
-
1972
- function paf_splice2bed(args)
1973
- {
1974
- var colors = ["0,128,255", "255,0,0", "0,192,0"];
1975
-
1976
- function print_lines(a, fmt, keep_multi)
1977
- {
1978
- if (a.length == 0) return;
1979
- if (fmt == "bed") {
1980
- var n_pri = 0;
1981
- for (var i = 0; i < a.length; ++i)
1982
- if (a[i][8] == 0) ++n_pri;
1983
- if (n_pri > 1) {
1984
- for (var i = 0; i < a.length; ++i)
1985
- if (a[i][8] == 0) a[i][8] = 1;
1986
- } else if (n_pri == 0) {
1987
- warn("Warning: " + a[0][3] + " doesn't have a primary alignment");
1988
- }
1989
- for (var i = 0; i < a.length; ++i) {
1990
- if (!keep_multi && a[i][8] == 2) continue;
1991
- a[i][8] = colors[a[i][8]];
1992
- print(a[i].join("\t"));
1993
- }
1994
- }
1995
- a.length = 0;
1996
- }
1997
-
1998
- var re = /(\d+)([MIDNSHP=X])/g;
1999
- var c, fmt = "bed", fn_name_conv = null, keep_multi = false;
2000
- while ((c = getopt(args, "f:n:m")) != null) {
2001
- if (c == 'f') fmt = getopt.arg;
2002
- else if (c == 'n') fn_name_conv = getopt.arg;
2003
- else if (c == 'm') keep_multi = true;
2004
- }
2005
- if (getopt.ind == args.length) {
2006
- print("Usage: paftools.js splice2bed [options] <in.paf>|<in.sam>");
2007
- print("Options:");
2008
- print(" -m keep multiple mappings (SAM flag 0x100)");
2009
- exit(1);
2010
- }
2011
-
2012
- var conv = null;
2013
- if (fn_name_conv != null) {
2014
- conv = new Map();
2015
- var file = new File(fn_name_conv);
2016
- var buf = new Bytes();
2017
- while (file.readline(buf) >= 0) {
2018
- var t = buf.toString().split("\t");
2019
- conv.put(t[0], t[1]);
2020
- }
2021
- buf.destroy();
2022
- file.close();
2023
- }
2024
-
2025
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
2026
- var buf = new Bytes();
2027
- var a = [];
2028
- while (file.readline(buf) >= 0) {
2029
- var line = buf.toString();
2030
- if (line.charAt(0) == '@') continue; // skip SAM header lines
2031
- var t = line.split("\t");
2032
- var is_pri = false, cigar = null, a1;
2033
- var qname = conv != null? conv.get(t[0]) : null;
2034
- if (qname != null) t[0] = qname;
2035
- if (t.length >= 10 && t[4] != '+' && t[4] != '-' && /^\d+/.test(t[1])) { // SAM
2036
- var flag = parseInt(t[1]);
2037
- if (flag&1) t[0] += '/' + (flag>>6&3);
2038
- }
2039
- if (a.length && a[0][3] != t[0]) {
2040
- print_lines(a, fmt, keep_multi);
2041
- a = [];
2042
- }
2043
- if (t.length >= 12 && (t[4] == '+' || t[4] == '-')) { // PAF
2044
- for (var i = 12; i < t.length; ++i) {
2045
- if (t[i].substr(0, 5) == 'cg:Z:') {
2046
- cigar = t[i].substr(5);
2047
- } else if (t[i].substr(0, 5) == 's2:i:') {
2048
- is_pri = true;
2049
- }
2050
- }
2051
- a1 = [t[5], t[7], t[8], t[0], Math.floor(t[9]/t[10]*1000), t[4]];
2052
- } else if (t.length >= 10) { // SAM
2053
- var flag = parseInt(t[1]);
2054
- if ((flag&4) || a[2] == '*') continue;
2055
- cigar = t[5];
2056
- is_pri = (flag&0x100)? false : true;
2057
- a1 = [t[2], parseInt(t[3])-1, null, t[0], 1000, (flag&16)? '-' : '+'];
2058
- } else {
2059
- throw Error("unrecognized input format");
2060
- }
2061
- if (cigar == null) throw Error("missing CIGAR");
2062
- var m, x0 = 0, x = 0, bs = [], bl = [];
2063
- while ((m = re.exec(cigar)) != null) {
2064
- if (m[2] == 'M' || m[2] == 'D') {
2065
- x += parseInt(m[1]);
2066
- } else if (m[2] == 'N') {
2067
- bs.push(x0);
2068
- bl.push(x - x0);
2069
- x += parseInt(m[1]);
2070
- x0 = x;
2071
- }
2072
- }
2073
- bs.push(x0);
2074
- bl.push(x - x0);
2075
- // write the BED12 line
2076
- if (a1[2] == null) a1[2] = a1[1] + x;
2077
- a1.push(a1[1], a1[2]); // thick start/end is the same as start/end
2078
- a1.push(is_pri? 0 : 2, bs.length, bl.join(",")+",", bs.join(",")+",");
2079
- a.push(a1);
2080
- }
2081
- print_lines(a, fmt, keep_multi);
2082
- buf.destroy();
2083
- file.close();
2084
- if (conv != null) conv.destroy();
2085
- }
2086
-
2087
- /**********************
2088
- * Evaluation related *
2089
- **********************/
2090
-
2091
- // evaluate mapping accuracy
2092
- function paf_mapeval(args)
2093
- {
2094
- var c, max_mapq = 60, mode = 0, err_out_q = 256, print_err = false, ovlp_ratio = 0.1, cap_short_mapq = false;
2095
- while ((c = getopt(args, "Q:r:m:c")) != null) {
2096
- if (c == 'Q') err_out_q = parseInt(getopt.arg), print_err = true;
2097
- else if (c == 'r') ovlp_ratio = parseFloat(getopt.arg);
2098
- else if (c == 'm') mode = parseInt(getopt.arg);
2099
- else if (c == 'c') cap_short_mapq = true;
2100
- }
2101
-
2102
- if (args.length == getopt.ind) {
2103
- warn("Usage: paftools.js mapeval [options] <in.paf>|<in.sam>");
2104
- warn("Options:");
2105
- warn(" -r FLOAT mapping correct if overlap_length/union_length>FLOAT [" + ovlp_ratio + "]");
2106
- warn(" -Q INT print wrong mappings with mapQ>=INT [don't print]");
2107
- warn(" -m INT 0: eval the longest aln only; 1: first aln only; 2: all primary aln [0]");
2108
- exit(1);
2109
- }
2110
-
2111
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
2112
- var buf = new Bytes();
2113
-
2114
- var tot = [], err = [];
2115
- for (var q = 0; q <= max_mapq; ++q)
2116
- tot[q] = err[q] = 0;
2117
-
2118
- function is_correct(s, b)
2119
- {
2120
- if (s[0] != b[0] || s[3] != b[3]) return false;
2121
- var o, l;
2122
- if (s[1] < b[1]) {
2123
- if (s[2] <= b[1]) return false;
2124
- o = (s[2] < b[2]? s[2] : b[2]) - b[1];
2125
- l = (s[2] > b[2]? s[2] : b[2]) - s[1];
2126
- } else {
2127
- if (b[2] <= s[1]) return false;
2128
- o = (s[2] < b[2]? s[2] : b[2]) - s[1];
2129
- l = (s[2] > b[2]? s[2] : b[2]) - b[1];
2130
- }
2131
- return o/l > ovlp_ratio? true : false;
2132
- }
2133
-
2134
- function count_err(qname, a, tot, err, mode)
2135
- {
2136
- if (a.length == 0) return;
2137
-
2138
- var m, s;
2139
- if ((m = /^(\S+)!(\S+)!(\d+)!(\d+)!([\+\-])$/.exec(qname)) != null) { // pbsim single-end reads
2140
- s = [m[1], m[2], parseInt(m[3]), parseInt(m[4]), m[5]];
2141
- } else if ((m = /^(\S+)!(\S+)!(\d+)_(\d+)!(\d+)_(\d+)!([\+\-])([\+\-])\/([12])$/.exec(qname)) != null) { // mason2 paired-end reads
2142
- if (m[9] == '1') {
2143
- s = [m[1], m[2], parseInt(m[3]), parseInt(m[5]), m[7]];
2144
- } else {
2145
- s = [m[1], m[2], parseInt(m[4]), parseInt(m[6]), m[8]];
2146
- }
2147
- } else throw Error("Failed to parse simulated read names '" + qname + "'");
2148
- s.shift(); // skip the orginal read name
2149
-
2150
- if (mode == 0 || mode == 1) { // longest only or first only
2151
- var max_i = 0;
2152
- if (mode == 0) { // longest only
2153
- var max = 0;
2154
- for (var i = 0; i < a.length; ++i)
2155
- if (a[i][5] > max)
2156
- max = a[i][5], max_i = i;
2157
- }
2158
- var mapq = a[max_i][4];
2159
- ++tot[mapq];
2160
- if (!is_correct(s, a[max_i])) {
2161
- if (mapq >= err_out_q)
2162
- print('E', qname, a[max_i].join("\t"));
2163
- ++err[mapq];
2164
- }
2165
- } else if (mode == 2) { // all primary mode
2166
- var max_err_mapq = -1, max_mapq = 0, max_err_i = -1;
2167
- if (cap_short_mapq) {
2168
- var max = 0, max_q = 0;
2169
- for (var i = 0; i < a.length; ++i)
2170
- if (a[i][5] > max)
2171
- max = a[i][5], max_q = a[i][4];
2172
- for (var i = 0; i < a.length; ++i)
2173
- a[i][4] = max_q < a[i][4]? max_q : a[i][4];
2174
- }
2175
- for (var i = 0; i < a.length; ++i) {
2176
- max_mapq = max_mapq > a[i][4]? max_mapq : a[i][4];
2177
- if (!is_correct(s, a[i]))
2178
- if (a[i][4] > max_err_mapq)
2179
- max_err_mapq = a[i][4], max_err_i = i;
2180
- }
2181
- if (max_err_mapq >= 0) {
2182
- ++tot[max_err_mapq], ++err[max_err_mapq];
2183
- if (max_err_mapq >= err_out_q)
2184
- print('E', qname, a[max_err_i].join("\t"));
2185
- } else ++tot[max_mapq];
2186
- }
2187
- }
2188
-
2189
- var lineno = 0, last = null, a = [], n_unmapped = null;
2190
- var re_cigar = /(\d+)([MIDSHN=X])/g;
2191
- while (file.readline(buf) >= 0) {
2192
- var m, line = buf.toString();
2193
- ++lineno;
2194
- if (line[0] != '@') {
2195
- var t = line.split("\t");
2196
- if (t[4] == '+' || t[4] == '-') { // PAF
2197
- if (last != t[0]) {
2198
- if (last != null) count_err(last, a, tot, err, mode);
2199
- a = [], last = t[0];
2200
- }
2201
- if (/\ts1:i:\d+/.test(line) && !/\ts2:i:\d+/.test(line)) // secondary alignment in minimap2 PAF
2202
- continue;
2203
- var mapq = parseInt(t[11]);
2204
- if (mapq > max_mapq) mapq = max_mapq;
2205
- a.push([t[5], parseInt(t[7]), parseInt(t[8]), t[4], mapq, parseInt(t[9])]);
2206
- } else { // SAM
2207
- var flag = parseInt(t[1]);
2208
- var read_no = flag>>6&0x3;
2209
- var qname = t[0];
2210
- if (!/\/[12]$/.test(qname))
2211
- qname = read_no == 1 || read_no == 2? t[0] + '/' + read_no : t[0];
2212
- if (last != qname) {
2213
- if (last != null) count_err(last, a, tot, err, mode);
2214
- a = [], last = qname;
2215
- }
2216
- if (flag&0x100) continue; // secondary alignment
2217
- if ((flag&0x4) || t[2] == '*') { // unmapped
2218
- if (n_unmapped == null) n_unmapped = 0;
2219
- ++n_unmapped;
2220
- continue;
2221
- }
2222
- var mapq = parseInt(t[4]);
2223
- if (mapq > max_mapq) mapq = max_mapq;
2224
- var pos = parseInt(t[3]) - 1, pos_end = pos;
2225
- var n_gap = 0, mlen = 0;
2226
- while ((m = re_cigar.exec(t[5])) != null) {
2227
- var len = parseInt(m[1]);
2228
- if (m[2] == 'M' || m[2] == 'X' || m[2] == '=') pos_end += len, mlen += len;
2229
- else if (m[2] == 'I') n_gap += len;
2230
- else if (m[2] == 'D') n_gap += len, pos_end += len;
2231
- }
2232
- var score = pos_end - pos;
2233
- if ((m = /\tNM:i:(\d+)/.exec(line)) != null) {
2234
- var NM = parseInt(m[1]);
2235
- if (NM >= n_gap) score = mlen - (NM - n_gap);
2236
- }
2237
- a.push([t[2], pos, pos_end, (flag&16)? '-' : '+', mapq, score]);
2238
- }
2239
- }
2240
- }
2241
- if (last != null) count_err(last, a, tot, err, mode);
2242
-
2243
- buf.destroy();
2244
- file.close();
2245
-
2246
- var sum_tot = 0, sum_err = 0, q_out = -1, sum_tot2 = 0, sum_err2 = 0;
2247
- for (var q = max_mapq; q >= 0; --q) {
2248
- if (tot[q] == 0) continue;
2249
- if (q_out < 0 || err[q] > 0) {
2250
- if (q_out >= 0) print('Q', q_out, sum_tot, sum_err, (sum_err2/sum_tot2).toFixed(9), sum_tot2);
2251
- sum_tot = sum_err = 0, q_out = q;
2252
- }
2253
- sum_tot += tot[q], sum_err += err[q];
2254
- sum_tot2 += tot[q], sum_err2 += err[q];
2255
- }
2256
- print('Q', q_out, sum_tot, sum_err, (sum_err2/sum_tot2).toFixed(9), sum_tot2);
2257
- if (n_unmapped != null) print('U', n_unmapped);
2258
- }
2259
-
2260
- // convert mason2 SAM to FASTQ
2261
- function paf_mason2fq(args)
2262
- {
2263
- if (args.length == 0) {
2264
- print("Usage: paftools.js mason2fq <mason.sam>");
2265
- exit(1);
2266
- }
2267
-
2268
- function print_se(a)
2269
- {
2270
- print('@' + a.slice(0, 5).join("!") + " " + a[8]);
2271
- print(a[5]);
2272
- print("+");
2273
- print(a[6]);
2274
- }
2275
-
2276
- var buf = new Bytes(), buf2 = new Bytes();
2277
- var file = new File(args[0]);
2278
- var re = /(\d+)([MIDSHN])/g;
2279
- var last = null;
2280
- while (file.readline(buf) >= 0) {
2281
- var t = buf.toString().split("\t");
2282
- if (t[0].charAt(0) == '@') continue;
2283
- var m, l_ref = 0;
2284
- while ((m = re.exec(t[5])) != null)
2285
- if (m[2] == 'D' || m[2] == 'M' || m[2] == 'N')
2286
- l_ref += parseInt(m[1]);
2287
- var flag = parseInt(t[1]);
2288
- var rev = !!(flag&16);
2289
- var seq, qual;
2290
- if (rev) {
2291
- buf2.length = 0;
2292
- buf2.set(t[9], 0);
2293
- buf2.revcomp();
2294
- seq = buf2.toString();
2295
- buf2.set(t[10], 0);
2296
- buf2.reverse();
2297
- qual = buf2.toString();
2298
- } else seq = t[9], qual = t[10];
2299
- var qname = t[0];
2300
- qname = qname.replace(/^simulated./, "");
2301
- var chr = t[2];
2302
- var pos = parseInt(t[3]) - 1;
2303
- var strand = (flag&16)? '-' : '+';
2304
- var read_no = flag&0xc0;
2305
- if (read_no == 0x40) read_no = 1;
2306
- else if (read_no == 0x80) read_no = 2;
2307
- else read_no = 0;
2308
- var err = 0, snp = 0, indel = 0;
2309
- for (var i = 11; i < t.length; ++i) {
2310
- if ((m = /^XE:i:(\d+)/.exec(t[i])) != null) err = m[1];
2311
- else if ((m = /^XS:i:(\d+)/.exec(t[i])) != null) snp = m[1];
2312
- else if ((m = /^XI:i:(\d+)/.exec(t[i])) != null) indel = m[1];
2313
- }
2314
- var comment = [err, snp, indel].join(":");
2315
- if (last == null) {
2316
- last = [qname, chr, pos, pos + l_ref, strand, seq, qual, read_no, comment];
2317
- } else if (last[0] != qname) {
2318
- print_se(last);
2319
- last = [qname, chr, pos, pos + l_ref, strand, seq, qual, read_no, comment];
2320
- } else {
2321
- if (read_no == 2) { // last[] is the first read
2322
- if (last[7] != 1) throw Error("ERROR: can't find read1");
2323
- var name = [qname, chr, last[2] + "_" + pos, last[3] + "_" + (pos + l_ref), last[4] + strand].join("!");
2324
- print('@' + name + '/1' + ' ' + last[8]); print(last[5]); print("+"); print(last[6]);
2325
- print('@' + name + '/2' + ' ' + comment); print(seq); print("+"); print(qual);
2326
- } else {
2327
- if (last[7] != 2) throw Error("ERROR: can't find read2");
2328
- var name = [qname, chr, pos + "_" + last[2], (pos + l_ref) + "_" + last[3], strand + last[4]].join("!");
2329
- print('@' + name + '/1' + ' ' + comment); print(seq); print("+"); print(qual);
2330
- print('@' + name + '/2' + ' ' + last[8]); print(last[5]); print("+"); print(last[6]);
2331
- }
2332
- last = null;
2333
- }
2334
- }
2335
- if (last != null) print_se(last);
2336
- file.close();
2337
- buf.destroy();
2338
- buf2.destroy();
2339
- }
2340
-
2341
- // convert pbsim MAF to FASTQ
2342
- function paf_pbsim2fq(args)
2343
- {
2344
- if (args.length < 2) {
2345
- print("Usage: paftools.js pbsim2fq <ref.fa.fai> <pbsim1.maf> [[pbsim2.maf] ...]");
2346
- exit(1);
2347
- }
2348
-
2349
- var file, buf = new Bytes(), buf2 = new Bytes();
2350
- file = new File(args[0]);
2351
- var chr_list = [];
2352
- while (file.readline(buf) >= 0) {
2353
- var t = buf.toString().split(/\s+/);
2354
- chr_list.push(t[0]);
2355
- }
2356
- file.close();
2357
-
2358
- for (var k = 1; k < args.length; ++k) {
2359
- var fn = args[k];
2360
- file = new File(fn);
2361
- var state = 0, reg;
2362
- while (file.readline(buf) >= 0) {
2363
- var line = buf.toString();
2364
- if (state == 0 && line.charAt(0) == 'a') {
2365
- state = 1;
2366
- } else if (state == 1 && line.charAt(0) == 's') {
2367
- var t = line.split(/\s+/);
2368
- var st = parseInt(t[2]);
2369
- reg = [st, st + parseInt(t[3])];
2370
- state = 2;
2371
- } else if (state == 2 && line.charAt(0) == 's') {
2372
- var m, t = line.split(/\s+/);
2373
- if ((m = /S(\d+)_\d+/.exec(t[1])) == null) throw Error("Failed to parse the read name");
2374
- var chr_id = parseInt(m[1]) - 1;
2375
- if (chr_id >= chr_list.length) throw Error("Index outside the chr list");
2376
- var name = [t[1], chr_list[chr_id], reg[0], reg[1], t[4]].join("!");
2377
- var seq = t[6].replace(/\-/g, "");
2378
- if (seq.length != parseInt(t[5])) throw Error("Inconsistent read length");
2379
- if (seq.indexOf("NN") < 0) {
2380
- if (t[4] == '-') {
2381
- buf2.set(seq, 0);
2382
- buf2.length = seq.length;
2383
- buf2.revcomp();
2384
- seq = buf2.toString();
2385
- }
2386
- print(">" + name);
2387
- print(seq);
2388
- }
2389
- state = 0;
2390
- }
2391
- }
2392
- file.close();
2393
- }
2394
- buf.destroy();
2395
- buf2.destroy();
2396
- }
2397
-
2398
- function paf_junceval(args)
2399
- {
2400
- var c, l_fuzzy = 0, print_ovlp = false, print_err_only = false, first_only = false, chr_only = false, aa = false, is_bed = false;
2401
- while ((c = getopt(args, "l:epcab1")) != null) {
2402
- if (c == 'l') l_fuzzy = parseInt(getopt.arg);
2403
- else if (c == 'e') print_err_only = print_ovlp = true;
2404
- else if (c == 'p') print_ovlp = true;
2405
- else if (c == 'c') chr_only = true;
2406
- else if (c == 'a') aa = true;
2407
- else if (c == 'b') is_bed = true;
2408
- else if (c == '1') first_only = true;
2409
- }
2410
-
2411
- if (args.length - getopt.ind < 1) {
2412
- print("Usage: paftools.js junceval [options] <gene.gtf> <aln.sam>");
2413
- print("Options:");
2414
- print(" -l INT tolerance of junction positions (0 for exact) [0]");
2415
- print(" -p print overlapping introns");
2416
- print(" -e print erroreous overlapping introns");
2417
- print(" -c only consider alignments to /^(chr)?([0-9]+|X|Y)$/");
2418
- print(" -a miniprot PAF as input");
2419
- print(" -b BED as input");
2420
- print(" -1 only process the first alignment of each query");
2421
- exit(1);
2422
- }
2423
-
2424
- var file, buf = new Bytes();
2425
-
2426
- var tr = {};
2427
- file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
2428
- while (file.readline(buf) >= 0) {
2429
- var m, t = buf.toString().split("\t");
2430
- if (t[0].charAt(0) == '#') continue;
2431
- if (t[2] != 'exon') continue;
2432
- var st = parseInt(t[3]) - 1;
2433
- var en = parseInt(t[4]);
2434
- if ((m = /transcript_id "(\S+)"/.exec(t[8])) == null) continue;
2435
- var tid = m[1];
2436
- if (tr[tid] == null) tr[tid] = [t[0], t[6], 0, 0, []];
2437
- tr[tid][4].push([st, en]);
2438
- }
2439
- file.close();
2440
-
2441
- var anno = {};
2442
- for (var tid in tr) {
2443
- var t = tr[tid];
2444
- Interval.sort(t[4]);
2445
- t[2] = t[4][0][0];
2446
- t[3] = t[4][t[4].length - 1][1];
2447
- if (anno[t[0]] == null) anno[t[0]] = [];
2448
- var s = t[4];
2449
- for (var i = 0; i < s.length - 1; ++i) {
2450
- if (s[i][1] >= s[i+1][0])
2451
- warn("WARNING: incorrect annotation for transcript "+tid+" ("+s[i][1]+" >= "+s[i+1][0]+")")
2452
- anno[t[0]].push([s[i][1], s[i+1][0]]);
2453
- }
2454
- }
2455
- tr = null;
2456
-
2457
- for (var chr in anno) {
2458
- var e = anno[chr];
2459
- if (e.length == 0) continue;
2460
- Interval.sort(e);
2461
- var k = 0;
2462
- for (var i = 1; i < e.length; ++i) // dedup
2463
- if (e[i][0] != e[k][0] || e[i][1] != e[k][1])
2464
- e[++k] = e[i].slice(0);
2465
- e.length = k + 1;
2466
- Interval.index_end(e);
2467
- }
2468
-
2469
- var n_pri = 0, n_unmapped = 0, n_mapped = 0;
2470
- var n_sgl = 0, n_splice = 0, n_splice_hit = 0, n_splice_novel = 0;
2471
-
2472
- file = getopt.ind+1 >= args.length || args[getopt.ind+1] == '-'? new File() : new File(args[getopt.ind+1]);
2473
- var last_qname = null;
2474
- var re_cigar = /(\d+)([MIDNSHP=XFGUV])/g;
2475
- while (file.readline(buf) >= 0) {
2476
- var m, t = buf.toString().split("\t");
2477
- var ctg_name = null, cigar = null, pos = null, qname;
2478
-
2479
- if (t[0].charAt(0) == '@') continue;
2480
- if (t[0] == "##PAF") t.shift();
2481
- qname = t[0];
2482
- if (is_bed) {
2483
- ctg_name = t[0], pos = parseInt(t[1]), cigar == null;
2484
- } else if (t[4] == '+' || t[4] == '-' || t[4] == '*') { // PAF
2485
- ctg_name = t[5], pos = parseInt(t[7]);
2486
- var type = 'P';
2487
- for (i = 12; i < t.length; ++i) {
2488
- if ((m = /^(tp:A|cg:Z):(\S+)/.exec(t[i])) != null) {
2489
- if (m[1] == 'tp:A') type = m[2];
2490
- else cigar = m[2];
2491
- }
2492
- }
2493
- if (type == 'S') continue; // secondary
2494
- } else { // SAM
2495
- ctg_name = t[2], pos = parseInt(t[3]) - 1, cigar = t[5];
2496
- var flag = parseInt(t[1]);
2497
- if (flag & 1) {
2498
- if (flag & 0x40) qname += '/1';
2499
- else if (flag & 0x80) qname += '/2';
2500
- }
2501
- if (flag&0x100) continue; // secondary
2502
- }
2503
-
2504
- if (chr_only && !/^(chr)?([0-9]+|X|Y)$/.test(ctg_name)) continue;
2505
- if (first_only && last_qname == qname) continue;
2506
- if (ctg_name == '*') { // unmapped
2507
- ++n_unmapped;
2508
- continue;
2509
- } else {
2510
- ++n_pri;
2511
- if (last_qname != qname) {
2512
- ++n_mapped;
2513
- last_qname = qname;
2514
- }
2515
- }
2516
-
2517
- var intron = [];
2518
- if (is_bed) {
2519
- intron.push([pos, parseInt(t[2])]);
2520
- } else if (aa) {
2521
- var tmp_junc = [], tmp = 0;
2522
- while ((m = re_cigar.exec(cigar)) != null) {
2523
- var len = parseInt(m[1]), op = m[2];
2524
- if (op == 'N') {
2525
- tmp_junc.push([tmp, tmp + len]);
2526
- tmp += len;
2527
- } else if (op == 'U') {
2528
- tmp_junc.push([tmp + 1, tmp + len - 2]);
2529
- tmp += len;
2530
- } else if (op == 'V') {
2531
- tmp_junc.push([tmp + 2, tmp + len - 1]);
2532
- tmp += len;
2533
- } else if (op == 'M' || op == 'X' || op == '=' || op == 'D') {
2534
- tmp += len * 3;
2535
- } else if (op == 'F' || op == 'G') {
2536
- tmp += len;
2537
- }
2538
- }
2539
- if (t[4] == '+') {
2540
- for (var i = 0; i < tmp_junc.length; ++i)
2541
- intron.push([pos + tmp_junc[i][0], pos + tmp_junc[i][1]]);
2542
- } else if (t[4] == '-') {
2543
- var glen = parseInt(t[8]) - parseInt(t[7]);
2544
- for (var i = tmp_junc.length - 1; i >= 0; --i)
2545
- intron.push([pos + (glen - tmp_junc[i][1]), pos + (glen - tmp_junc[i][0])]);
2546
- }
2547
- } else {
2548
- while ((m = re_cigar.exec(cigar)) != null) {
2549
- var len = parseInt(m[1]), op = m[2];
2550
- if (op == 'N') {
2551
- intron.push([pos, pos + len]);
2552
- pos += len;
2553
- } else if (op == 'M' || op == 'X' || op == '=' || op == 'D') pos += len;
2554
- }
2555
- }
2556
- if (intron.length == 0) {
2557
- ++n_sgl;
2558
- continue;
2559
- }
2560
- n_splice += intron.length;
2561
-
2562
- var chr = anno[ctg_name];
2563
- if (chr != null) {
2564
- for (var i = 0; i < intron.length; ++i) {
2565
- var o = Interval.find_ovlp(chr, intron[i][0], intron[i][1]);
2566
- if (o.length > 0) {
2567
- var hit = false;
2568
- for (var j = 0; j < o.length; ++j) {
2569
- var st_diff = intron[i][0] - o[j][0];
2570
- var en_diff = intron[i][1] - o[j][1];
2571
- if (st_diff < 0) st_diff = -st_diff;
2572
- if (en_diff < 0) en_diff = -en_diff;
2573
- if (st_diff <= l_fuzzy && en_diff <= l_fuzzy)
2574
- ++n_splice_hit, hit = true;
2575
- if (hit) break;
2576
- }
2577
- if (print_ovlp) {
2578
- var type = hit? 'C' : 'P';
2579
- if (hit && print_err_only) continue;
2580
- var x = '[';
2581
- for (var j = 0; j < o.length; ++j) {
2582
- if (j) x += ', ';
2583
- x += '(' + o[j][0] + "," + o[j][1] + ')';
2584
- }
2585
- x += ']';
2586
- print(type, qname, i+1, ctg_name, intron[i][0], intron[i][1], x);
2587
- }
2588
- } else {
2589
- ++n_splice_novel;
2590
- if (print_ovlp)
2591
- print('N', qname, i+1, ctg_name, intron[i][0], intron[i][1]);
2592
- }
2593
- }
2594
- } else {
2595
- n_splice_novel += intron.length;
2596
- }
2597
- }
2598
- file.close();
2599
-
2600
- buf.destroy();
2601
-
2602
- if (!print_ovlp) {
2603
- print("# unmapped reads: " + n_unmapped);
2604
- print("# mapped reads: " + n_mapped);
2605
- print("# primary alignments: " + n_pri);
2606
- print("# singletons: " + n_sgl);
2607
- print("# predicted introns: " + n_splice);
2608
- print("# non-overlapping introns: " + n_splice_novel);
2609
- print("# correct introns: " + n_splice_hit + " (" + (n_splice_hit / n_splice * 100).toFixed(2) + "%)");
2610
- }
2611
- }
2612
-
2613
- function paf_exoneval(args) // adapted from paf_junceval()
2614
- {
2615
- var c, l_fuzzy = 0, print_ovlp = false, print_err_only = false, first_only = false, chr_only = false, aa = false, is_bed = false, use_cds = false, eval_base = false;
2616
- var skip_start = false, skip_last = false;
2617
- while ((c = getopt(args, "l:epcab1dsft")) != null) {
2618
- if (c == 'l') l_fuzzy = parseInt(getopt.arg);
2619
- else if (c == 'e') print_err_only = print_ovlp = true;
2620
- else if (c == 'p') print_ovlp = true;
2621
- else if (c == 'c') chr_only = true;
2622
- else if (c == 'a') aa = true, use_cds = true;
2623
- else if (c == 'b') is_bed = true;
2624
- else if (c == '1') first_only = true;
2625
- else if (c == 'd') use_cds = true;
2626
- else if (c == 's') eval_base = true;
2627
- else if (c == 'f') skip_start = true;
2628
- else if (c == 't') skip_last = skip_start = true;
2629
- }
2630
-
2631
- if (args.length - getopt.ind < 1) {
2632
- print("Usage: paftools.js exoneval [options] <gene.gtf> <aln.sam>");
2633
- print("Options:");
2634
- print(" -l INT tolerance of junction positions (0 for exact) [0]");
2635
- print(" -d evaluate coding regions only (exon regions by default)");
2636
- print(" -a miniprot PAF as input (force -d)");
2637
- print(" -p print overlapping exons");
2638
- print(" -e print erroreous overlapping exons");
2639
- print(" -c only consider alignments to /^(chr)?([0-9]+|X|Y)$/");
2640
- print(" -1 only process the first alignment of each query");
2641
- print(" -f skip the first exon in the miniprot mode");
2642
- print(" -t skip the first and the last exons");
2643
- print(" -b BED as input");
2644
- print(" -s compute base Sn and Sp (more memory)");
2645
- exit(1);
2646
- }
2647
-
2648
- var file, buf = new Bytes();
2649
-
2650
- warn("Reading reference GTF...");
2651
- var tr = {};
2652
- file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
2653
- while (file.readline(buf) >= 0) {
2654
- var m, t = buf.toString().split("\t");
2655
- if (t[0].charAt(0) == '#') continue;
2656
- if (use_cds) {
2657
- if (t[2] != "cds" && t[2] != "CDS") continue;
2658
- } else {
2659
- if (t[2] != 'exon') continue;
2660
- }
2661
- var st = parseInt(t[3]) - 1;
2662
- var en = parseInt(t[4]);
2663
- if ((m = /transcript_id "(\S+)"/.exec(t[8])) == null) continue;
2664
- var tid = m[1];
2665
- if (tr[tid] == null) tr[tid] = [t[0], t[6], 0, 0, []];
2666
- tr[tid][4].push([st, en]); // this keeps transcript
2667
- }
2668
- file.close();
2669
-
2670
- var anno = {};
2671
- for (var tid in tr) { // traverse each transcript
2672
- var t = tr[tid];
2673
- Interval.sort(t[4]);
2674
- t[2] = t[4][0][0];
2675
- t[3] = t[4][t[4].length - 1][1];
2676
- if (anno[t[0]] == null) anno[t[0]] = [];
2677
- var s = t[4];
2678
- for (var i = 0; i < s.length; ++i) // traverse each exon
2679
- anno[t[0]].push([s[i][0], s[i][1]]);
2680
- }
2681
- tr = null;
2682
-
2683
- for (var chr in anno) { // index exons
2684
- var e = anno[chr];
2685
- if (e.length == 0) continue;
2686
- Interval.sort(e);
2687
- var k = 0;
2688
- for (var i = 1; i < e.length; ++i) // dedup
2689
- if (e[i][0] != e[k][0] || e[i][1] != e[k][1])
2690
- e[++k] = e[i].slice(0);
2691
- e.length = k + 1;
2692
- Interval.index_end(e);
2693
- }
2694
-
2695
- var n_pri = 0, n_unmapped = 0, n_mapped = 0;
2696
- var n_exon = 0, n_exon_hit = 0, n_exon_novel = 0;
2697
-
2698
- file = getopt.ind+1 >= args.length || args[getopt.ind+1] == '-'? new File() : new File(args[getopt.ind+1]);
2699
- var last_qname = null, qexon = {};
2700
- var re_cigar = /(\d+)([MIDNSHP=XFGUV])/g;
2701
-
2702
- warn("Evaluating alignments...");
2703
- while (file.readline(buf) >= 0) {
2704
- var m, t = buf.toString().split("\t");
2705
- var ctg_name = null, cigar = null, pos = null, qname;
2706
-
2707
- if (t[0].charAt(0) == '@') continue;
2708
- if (t[0] == "##PAF") t.shift();
2709
- qname = t[0];
2710
- if (is_bed) {
2711
- ctg_name = t[0], pos = parseInt(t[1]), cigar == null;
2712
- } else if (t[4] == '+' || t[4] == '-' || t[4] == '*') { // PAF
2713
- ctg_name = t[5], pos = parseInt(t[7]);
2714
- var type = 'P';
2715
- for (i = 12; i < t.length; ++i) {
2716
- if ((m = /^(tp:A|cg:Z):(\S+)/.exec(t[i])) != null) {
2717
- if (m[1] == 'tp:A') type = m[2];
2718
- else cigar = m[2];
2719
- }
2720
- }
2721
- if (type == 'S') continue; // secondary
2722
- } else { // SAM
2723
- ctg_name = t[2], pos = parseInt(t[3]) - 1, cigar = t[5];
2724
- var flag = parseInt(t[1]);
2725
- if (flag&0x100) continue; // secondary
2726
- }
2727
-
2728
- if (chr_only && !/^(chr)?([0-9]+|X|Y)$/.test(ctg_name)) continue;
2729
- if (first_only && last_qname == qname) continue;
2730
- if (ctg_name == '*') { // unmapped
2731
- ++n_unmapped;
2732
- continue;
2733
- } else {
2734
- ++n_pri;
2735
- if (last_qname != qname) {
2736
- ++n_mapped;
2737
- last_qname = qname;
2738
- }
2739
- }
2740
-
2741
- var exon = [];
2742
- if (is_bed) { // BED
2743
- exon.push([pos, parseInt(t[2])]);
2744
- } else if (aa) {
2745
- var tmp_exon = [], tmp = 0, tmp_st = 0;
2746
- while ((m = re_cigar.exec(cigar)) != null) {
2747
- var len = parseInt(m[1]), op = m[2];
2748
- if (op == 'N') {
2749
- tmp_exon.push([tmp_st, tmp]);
2750
- tmp_st = tmp + len, tmp += len;
2751
- } else if (op == 'U') {
2752
- tmp_exon.push([tmp_st, tmp + 1]);
2753
- tmp_st = tmp + len - 2, tmp += len;
2754
- } else if (op == 'V') {
2755
- tmp_exon.push([tmp_st, tmp + 2]);
2756
- tmp_st = tmp + len - 1, tmp += len;
2757
- } else if (op == 'M' || op == 'X' || op == '=' || op == 'D') {
2758
- tmp += len * 3;
2759
- } else if (op == 'F' || op == 'G') {
2760
- tmp += len;
2761
- }
2762
- }
2763
- tmp_exon.push([tmp_st, tmp]);
2764
- if (t[4] == '+') {
2765
- for (var i = 0; i < tmp_exon.length; ++i)
2766
- exon.push([pos + tmp_exon[i][0], pos + tmp_exon[i][1]]);
2767
- } else if (t[4] == '-') { // For protein-to-genome alignment, the coordinates are on the query strand. Need to flip them.
2768
- var glen = parseInt(t[8]) - parseInt(t[7]);
2769
- for (var i = tmp_exon.length - 1; i >= 0; --i)
2770
- exon.push([pos + (glen - tmp_exon[i][1]), pos + (glen - tmp_exon[i][0])]);
2771
- }
2772
- if (skip_start) exon.shift();
2773
- if (skip_last) exon.pop();
2774
- } else {
2775
- var tmp_st = pos;
2776
- while ((m = re_cigar.exec(cigar)) != null) {
2777
- var len = parseInt(m[1]), op = m[2];
2778
- if (op == 'N') {
2779
- exon.push([tmp_st, pos]);
2780
- tmp_st = pos + len, pos += len;
2781
- } else if (op == 'M' || op == 'X' || op == '=' || op == 'D') pos += len;
2782
- }
2783
- exon.push([tmp_st, pos]);
2784
- }
2785
- n_exon += exon.length;
2786
-
2787
- var chr = anno[ctg_name];
2788
- if (chr != null) {
2789
- for (var i = 0; i < exon.length; ++i) {
2790
- if (eval_base) {
2791
- if (qexon[ctg_name] == null) qexon[ctg_name] = [];
2792
- qexon[ctg_name].push([exon[i][0], exon[i][1]]);
2793
- }
2794
- var o = Interval.find_ovlp(chr, exon[i][0], exon[i][1]);
2795
- if (o.length > 0) {
2796
- var hit = false;
2797
- for (var j = 0; j < o.length; ++j) {
2798
- var st_diff = exon[i][0] - o[j][0];
2799
- var en_diff = exon[i][1] - o[j][1];
2800
- if (st_diff < 0) st_diff = -st_diff;
2801
- if (en_diff < 0) en_diff = -en_diff;
2802
- if (st_diff <= l_fuzzy && en_diff <= l_fuzzy)
2803
- ++n_exon_hit, hit = true;
2804
- if (hit) break;
2805
- }
2806
- if (print_ovlp) {
2807
- var type = hit? 'C' : 'P';
2808
- if (hit && print_err_only) continue;
2809
- var x = '[';
2810
- for (var j = 0; j < o.length; ++j) {
2811
- if (j) x += ', ';
2812
- x += '(' + o[j][0] + "," + o[j][1] + ')';
2813
- }
2814
- x += ']';
2815
- print(type, qname, i+1, ctg_name, exon[i][0], exon[i][1], x);
2816
- }
2817
- } else {
2818
- ++n_exon_novel;
2819
- if (print_ovlp)
2820
- print('N', qname, i+1, ctg_name, exon[i][0], exon[i][1]);
2821
- }
2822
- }
2823
- } else {
2824
- n_exon_novel += exon.length;
2825
- }
2826
- }
2827
- file.close();
2828
-
2829
- buf.destroy();
2830
-
2831
- if (!print_ovlp) {
2832
- print("# unmapped reads: " + n_unmapped);
2833
- print("# mapped reads: " + n_mapped);
2834
- print("# primary alignments: " + n_pri);
2835
- print("# predicted exons: " + n_exon);
2836
- print("# non-overlapping exons: " + n_exon_novel);
2837
- print("# correct exons: " + n_exon_hit + " (" + (n_exon_hit / n_exon * 100).toFixed(2) + "%)");
2838
- }
2839
-
2840
- function merge_and_index(ex) {
2841
- for (var chr in ex) {
2842
- var a = [];
2843
- e = ex[chr];
2844
- Interval.sort(e);
2845
- var st = e[0][0], en = e[0][1];
2846
- for (var i = 1; i < e.length; ++i) { // merge
2847
- if (e[i][0] > en) {
2848
- a.push([st, en]);
2849
- st = e[i][0], en = e[i][1];
2850
- } else {
2851
- en = en > e[i][1]? en : e[i][1];
2852
- }
2853
- }
2854
- a.push([st, en]);
2855
- Interval.index_end(a);
2856
- ex[chr] = a;
2857
- }
2858
- }
2859
-
2860
- function cal_sn(a0, a1) {
2861
- var tot = 0, cov = 0;
2862
- for (var chr in a1) {
2863
- var e0 = a0[chr], e1 = a1[chr];
2864
- for (var i = 0; i < e1.length; ++i)
2865
- tot += e1[i][1] - e1[i][0];
2866
- if (e0 == null) continue;
2867
- for (var i = 0; i < e1.length; ++i) {
2868
- var o = Interval.find_ovlp(e0, e1[i][0], e1[i][1]);
2869
- for (var j = 0; j < o.length; ++j) { // this only works when there are no overlaps between intervals
2870
- var st = e1[i][0] > o[j][0]? e1[i][0] : o[j][0];
2871
- var en = e1[i][1] < o[j][1]? e1[i][1] : o[j][1];
2872
- cov += en - st;
2873
- }
2874
- }
2875
- }
2876
- return [tot, cov];
2877
- }
2878
-
2879
- if (eval_base) {
2880
- warn("Computing base Sn and Sp...");
2881
- merge_and_index(qexon);
2882
- merge_and_index(anno);
2883
- var sn = cal_sn(qexon, anno);
2884
- var sp = cal_sn(anno, qexon);
2885
- print("Base Sn: " + sn[1] + " / " + sn[0] + " = " + (sn[1] / sn[0] * 100).toFixed(2) + "%");
2886
- print("Base Sp: " + sp[1] + " / " + sp[0] + " = " + (sp[1] / sp[0] * 100).toFixed(2) + "%");
2887
- }
2888
- }
2889
-
2890
- // evaluate overlap sensitivity
2891
- function paf_ov_eval(args)
2892
- {
2893
- var c, min_ovlp = 2000, min_frac = 0.95, min_mapq = 10;
2894
- while ((c = getopt(args, "q:l:f:")) != null) {
2895
- if (c == 'q') min_mapq = parseInt(getopt.arg);
2896
- else if (c == 'l') min_ovlp = parseInt(getopt.arg);
2897
- else if (c == 'f') min_frac = parseFloat(getopt.arg);
2898
- }
2899
- if (args.length - getopt.ind < 2) {
2900
- print("Usage: sort -k6,6 -k8,8n to-ref.paf | paftools.js ov-eval [options] - <ovlp.paf>");
2901
- print("Options:");
2902
- print(" -l INT min overlap length [2000]");
2903
- print(" -q INT min mapping quality [10]");
2904
- print(" -f FLOAT min fraction of mapped length [0.95]");
2905
- exit(1);
2906
- }
2907
-
2908
- var buf = new Bytes();
2909
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
2910
- var a = [], h = {};
2911
- while (file.readline(buf) >= 0) {
2912
- var t = buf.toString().split("\t");
2913
- var is_pri = false;
2914
- if (parseInt(t[11]) < min_mapq) continue;
2915
- for (var i = 12; i < t.length; ++i)
2916
- if (t[i] == 'tp:A:P')
2917
- is_pri = true;
2918
- if (!is_pri) continue;
2919
- for (var i = 1; i <= 3; ++i)
2920
- t[i] = parseInt(t[i]);
2921
- for (var i = 6; i <= 8; ++i)
2922
- t[i] = parseInt(t[i]);
2923
- if (t[3] - t[2] < min_ovlp || t[8] - t[7] < min_ovlp || (t[3] - t[2]) / t[1] < min_frac)
2924
- continue;
2925
- var ctg = t[5], st = t[7], en = t[8];
2926
- while (a.length > 0) {
2927
- if (a[0][0] == ctg && a[0][2] > st)
2928
- break;
2929
- else a.shift();
2930
- }
2931
- for (var j = 0; j < a.length; ++j) {
2932
- if (a[j][3] == t[0]) continue;
2933
- var len = (en > a[j][2]? a[j][2] : en) - st;
2934
- if (len >= min_ovlp) {
2935
- var key = a[j][3] < t[0]? a[j][3] + "\t" + t[0] : t[0] + "\t" + a[j][3];
2936
- h[key] = len;
2937
- }
2938
- }
2939
- a.push([ctg, st, en, t[0]]);
2940
- }
2941
- file.close();
2942
-
2943
- file = new File(args[getopt.ind + 1]);
2944
- while (file.readline(buf) >= 0) {
2945
- var t = buf.toString().split("\t");
2946
- var key = t[0] < t[5]? t[0] + "\t" + t[5] : t[5] + "\t" + t[0];
2947
- if (h[key] > 0) h[key] = -h[key];
2948
- }
2949
- file.close();
2950
- buf.destroy();
2951
-
2952
- var n_ovlp = 0, n_missing = 0;
2953
- for (var key in h) {
2954
- ++n_ovlp;
2955
- if (h[key] > 0) ++n_missing;
2956
- }
2957
- print(n_ovlp + " overlaps inferred from the reference mapping");
2958
- print(n_missing + " missed by the read overlapper");
2959
- print((100 * (1 - n_missing / n_ovlp)).toFixed(2) + "% sensitivity");
2960
- }
2961
-
2962
- function paf_vcfstat(args)
2963
- {
2964
- var c, ts = { "AG":1, "GA":1, "CT":1, "TC":1 };
2965
- while ((c = getopt(args, "")) != null) {
2966
- }
2967
- var buf = new Bytes();
2968
- var file = args.length == getopt.ind? new File() : new File(args[getopt.ind]);
2969
- var x = { sub:0, ts:0, tv:0, ins:0, del:0, ins1:0, del1:0, ins2:0, del2:0, ins50:0, del50:0, ins1k:0, del1k:0, ins7k:0, del7k:0, insinf:0, delinf:0 };
2970
- while (file.readline(buf) >= 0) {
2971
- var t = buf.toString().split("\t");
2972
- if (t[0][0] == '#') continue;
2973
- var alt = t[4].split(",");
2974
- var ref = t[3];
2975
- for (var i = 0; i < alt.length; ++i) {
2976
- var a = alt[i];
2977
- if (a[0] == '<' || a[1] == '>') continue;
2978
- var l = ref.length < a.length? ref.length : a.length;
2979
- for (var j = 0; j < l; ++j) {
2980
- if (ref[j] != a[j]) {
2981
- ++x.sub;
2982
- if (ts[ref[j] + a[j]]) ++x.ts;
2983
- else ++x.tv;
2984
- }
2985
- }
2986
- var d = a.length - ref.length;
2987
- if (d > 0) {
2988
- ++x.ins;
2989
- if (d == 1) ++x.ins1;
2990
- else if (d == 2) ++x.ins2;
2991
- else if (d < 50) ++x.ins50;
2992
- else if (d < 1000) ++x.ins1k;
2993
- else if (d < 7000) ++x.ins7k;
2994
- else ++x.insinf;
2995
- } else if (d < 0) {
2996
- d = -d;
2997
- ++x.del;
2998
- if (d == 1) ++x.del1;
2999
- else if (d == 2) ++x.del2;
3000
- else if (d < 50) ++x.del50;
3001
- else if (d < 1000) ++x.del1k;
3002
- else if (d < 7000) ++x.del7k;
3003
- else ++x.delinf;
3004
- }
3005
- }
3006
- }
3007
- file.close();
3008
- buf.destroy();
3009
- print("# substitutions: " + x.sub);
3010
- print("ts/tv: " + (x.ts / x.tv).toFixed(3));
3011
- print("# insertions: " + x.ins);
3012
- print("# 1bp insertions: " + x.ins1);
3013
- print("# 2bp insertions: " + x.ins2);
3014
- print("# [3,50) insertions: " + x.ins50);
3015
- print("# [50,1000) insertions: " + x.ins1k);
3016
- print("# [1000,7000) insertions: " + x.ins7k);
3017
- print("# >=7000 insertions: " + x.insinf);
3018
- print("# deletions: " + x.del);
3019
- print("# 1bp deletions: " + x.del1);
3020
- print("# 2bp deletions: " + x.del2);
3021
- print("# [3,50) deletions: " + x.del50);
3022
- print("# [50,1000) deletions: " + x.del1k);
3023
- print("# [1000,7000) deletions: " + x.del7k);
3024
- print("# >=7000 deletions: " + x.delinf);
3025
- }
3026
-
3027
- function paf_parseNum(s) {
3028
- var m, x = null;
3029
- if ((m = /^(\d*\.?\d*)([mMgGkK]?)/.exec(s)) != null) {
3030
- x = parseFloat(m[1]);
3031
- if (m[2] == 'k' || m[2] == 'K') x *= 1000;
3032
- else if (m[2] == 'm' || m[2] == 'M') x *= 1000000;
3033
- else if (m[2] == 'g' || m[2] == 'G') x *= 1000000000;
3034
- }
3035
- return Math.floor(x + .499);
3036
- }
3037
-
3038
- function paf_misjoin(args)
3039
- {
3040
- var c, min_seg_len = 1000000, max_gap = 1000000, fn_cen = null, show_long = false, show_err = false, cen_ratio = 0.5;
3041
- var n_diff = [0, 0], n_gap = [0, 0], n_inv = [0, 0], n_inv_end = [0, 0];
3042
- while ((c = getopt(args, "l:g:c:per:")) != null) {
3043
- if (c == 'l') min_seg_len = paf_parseNum(getopt.arg);
3044
- else if (c == 'g') max_gap = paf_parseNum(getopt.arg);
3045
- else if (c == 'c') fn_cen = getopt.arg;
3046
- else if (c == 'r') cen_ratio = parseFloat(getopt.arg);
3047
- else if (c == 'p') show_long = true;
3048
- else if (c == 'e') show_err = true;
3049
- }
3050
- if (args.length == getopt.ind) {
3051
- print("Usage: paftools.js misjoin [options] <in.paf>");
3052
- print("Options:");
3053
- print(" -c FILE BED for centromeres []");
3054
- print(" -r FLOAT count a centromeric event if overlap ratio > FLOAT [" + cen_ratio + "]");
3055
- print(" -l NUM min alignment block length [1m]");
3056
- print(" -g NUM max gap size [1m]");
3057
- print(" -e output misjoins not involving centromeres");
3058
- print(" -p output long alignment blocks for debugging");
3059
- return;
3060
- }
3061
- var cen = {};
3062
- var file, buf = new Bytes();
3063
- if (fn_cen != null) {
3064
- file = new File(fn_cen);
3065
- while (file.readline(buf) >= 0) {
3066
- var t = buf.toString().split("\t");
3067
- if (cen[t[0]] == null) cen[t[0]] = [];
3068
- cen[t[0]].push([parseInt(t[1]), parseInt(t[2])]);
3069
- }
3070
- file.close();
3071
- }
3072
-
3073
- function test_cen(cen, chr, st, en) {
3074
- var b = cen[chr], len = 0;
3075
- if (b == null) return false;
3076
- for (var j = 0; j < b.length; ++j)
3077
- if (b[j][0] < en && b[j][1] > st) {
3078
- var s = b[j][0] > st? b[j][0] : st;
3079
- var e = b[j][1] < en? b[j][1] : en;
3080
- len += e - s;
3081
- }
3082
- return len < (en - st) * cen_ratio? false : true;
3083
- }
3084
-
3085
- function test_cen_point(cen, chr, x) {
3086
- var b = cen[chr];
3087
- if (b == null) return false;
3088
- for (var j = 0; j < b.length; ++j)
3089
- if (x >= b[j][0] && x < b[j][1])
3090
- return true;
3091
- return false;
3092
- }
3093
-
3094
- if (show_err || show_long) {
3095
- print("C\tJ inter-chromosomal misjoin");
3096
- print("C\tj inter-chromosomal misjoin with both breakpoints ending in centromeres");
3097
- print("C\tG long gap on the reference genome");
3098
- print("C\tg long gap on the reference genome with both breakpoints ending in centromeres");
3099
- print("C\tM closed inversion");
3100
- print("C");
3101
- }
3102
- function process(a) {
3103
- var k = 0;
3104
- for (var i = 0; i < a.length; ++i) {
3105
- for (var j = 1; j <= 3; ++j) a[i][j] = parseInt(a[i][j]);
3106
- for (var j = 6; j <= 11; ++j) a[i][j] = parseInt(a[i][j]);
3107
- if (a[i][10] >= min_seg_len) a[k++] = a[i];
3108
- }
3109
- a.length = k;
3110
- if (a.length == 1) return;
3111
- a = a.sort(function(x,y){return x[2]-y[2]});
3112
- if (show_long) for (var i = 0; i < a.length; ++i) print(a[i].join("\t"));
3113
- for (var i = 1; i < a.length; ++i) {
3114
- var ov = [false, false], end_cen = [false, false];
3115
- ov[0] = test_cen(cen, a[i-1][5], a[i-1][7], a[i-1][8]);
3116
- ov[1] = test_cen(cen, a[i][5], a[i][7], a[i][8]);
3117
- end_cen[0] = test_cen_point(cen, a[i-1][5], a[i-1][4] == '+'? a[i-1][8] : a[i-1][7]);
3118
- end_cen[1] = test_cen_point(cen, a[i][5], a[i][4] == '+'? a[i][7] : a[i][8]);
3119
- if (a[i-1][5] != a[i][5]) { // different chr
3120
- if (ov[0] || ov[1]) ++n_diff[1];
3121
- else if (show_err) {
3122
- var label = end_cen[0] && end_cen[1]? 'j' : 'J';
3123
- print(label, a[i-1].slice(0, 12).join("\t"));
3124
- print(label, a[i].slice(0, 12).join("\t"));
3125
- }
3126
- ++n_diff[0];
3127
- } else if (a[i-1][4] == a[i][4]) { // a gap
3128
- var dq = a[i][2] - a[i-1][3];
3129
- var dr = a[i][4] == '+'? a[i][7] - a[i-1][8] : a[i-1][7] - a[i][8];
3130
- var gap = dr > dq? dr - dq : dq - dr;
3131
- if (gap > max_gap) {
3132
- if (ov[0] || ov[1]) ++n_gap[1];
3133
- else if (show_err) {
3134
- var label = end_cen[0] && end_cen[1]? 'g' : 'G';
3135
- print(label, a[i-1].slice(0, 12).join("\t"));
3136
- print(label, a[i].slice(0, 12).join("\t"));
3137
- }
3138
- ++n_gap[0];
3139
- }
3140
- } else if (i + 1 < a.length && a[i+1][4] == a[i-1][4]) { // bracketed inversion
3141
- if (ov[0] || ov[1]) ++n_inv[1];
3142
- else if (show_err) {
3143
- print("M", a[i-1].slice(0, 12).join("\t"));
3144
- print("M", a[i].slice(0, 12).join("\t"));
3145
- print("M", a[i+1].slice(0, 12).join("\t"));
3146
- }
3147
- ++n_inv[0];
3148
- ++i;
3149
- } else { // hanging inversion
3150
- if (ov[0] || ov[1]) ++n_inv_end[1];
3151
- ++n_inv_end[0];
3152
- }
3153
- }
3154
- }
3155
-
3156
- file = args[getopt.ind] == "-"? new File() : new File(args[getopt.ind]);
3157
- var a = [];
3158
- while (file.readline(buf) >= 0) {
3159
- var t = buf.toString().split("\t");
3160
- if (a.length > 0 && a[0][0] != t[0]) {
3161
- process(a);
3162
- a.length = 0;
3163
- }
3164
- a.push(t);
3165
- }
3166
- if (a.length > 0) process(a);
3167
- file.close();
3168
- buf.destroy();
3169
- print("# inter-chromosomal misjoins: " + n_diff.join(","));
3170
- print("# intra-chromosomal gaps: " + n_gap.join(","));
3171
- print("# candidate inversions in the middle: " + n_inv.join(","));
3172
- print("# candidate inversions at contig ends: " + n_inv_end.join(","));
3173
- }
3174
-
3175
- function _paf_get_alen(t)
3176
- {
3177
- var svlen = null, alen = null;
3178
- if ((m = /(^|;)SVLEN=(-?\d+)/.exec(t[7])) != null)
3179
- svlen = parseInt(m[2]);
3180
- var s = t[4].split(",");
3181
- var min_abs_diff = 1<<30, max_abs_diff = 0;
3182
- if (svlen != null && svlen != 0)
3183
- alen = svlen, min_abs_diff = max_abs_diff = svlen > 0? svlen : -svlen;
3184
- var rlen = t[3].length;
3185
- for (var i = 0; i < s.length; ++i) {
3186
- if (/^<\S+>$/.test(s[i])) continue;
3187
- var diff = s[i].length - rlen;
3188
- var abs_diff = diff > 0? diff : -diff;
3189
- min_abs_diff = min_abs_diff < abs_diff? min_abs_diff : abs_diff;
3190
- if (max_abs_diff < abs_diff)
3191
- max_abs_diff = abs_diff, alen = diff;
3192
- }
3193
- return [alen, min_abs_diff, max_abs_diff];
3194
- }
3195
-
3196
- function paf_sveval(args)
3197
- {
3198
- var c, min_flt = 30, min_size = 50, max_size = 100000, win_size = 500, print_err = false, print_match = false, bed_fn = null;
3199
- var len_diff_ratio = 0.5;
3200
- while ((c = getopt(args, "f:i:x:w:er:pd:")) != null) {
3201
- if (c == 'f') min_flt = paf_parseNum(getopt.arg);
3202
- else if (c == 'i') min_size = paf_parseNum(getopt.arg);
3203
- else if (c == 'x') max_size = paf_parseNum(getopt.arg);
3204
- else if (c == 'w') win_size = paf_parseNum(getopt.arg);
3205
- else if (c == 'd') len_diff_ratio = parseFloat(getopt.arg);
3206
- else if (c == 'r') bed_fn = getopt.arg;
3207
- else if (c == 'e') print_err = true;
3208
- else if (c == 'p') print_match = true;
3209
- }
3210
- if (args.length - getopt.ind < 2) {
3211
- print("Usage: paftools.js sveval [options] <base.vcf> <call.vcf>");
3212
- print("Options:");
3213
- print(" -r FILE confident region in BED []");
3214
- print(" -f INT min length to discard [" + min_flt + "]");
3215
- print(" -i INT min SV length [" + min_size + "]");
3216
- print(" -x INT max SV length [" + max_size + "]");
3217
- print(" -w INT fuzzy windown size [" + win_size + "]");
3218
- print(" -d FLOAT max allele diff if there is a single allele in the window [" + len_diff_ratio + "]");
3219
- print(" -e print errors");
3220
- return;
3221
- }
3222
-
3223
- function read_bed(fn) {
3224
- var buf = new Bytes();
3225
- var file = new File(fn);
3226
- var bed = {};
3227
- while (file.readline(buf) >= 0) {
3228
- var t = buf.toString().split("\t");
3229
- if (bed[t[0]] == null) bed[t[0]] = [];
3230
- bed[t[0]].push([parseInt(t[1]), parseInt(t[2])]);
3231
- }
3232
- file.close();
3233
- buf.destroy();
3234
- for (var x in bed) {
3235
- Interval.sort(bed[x]);
3236
- Interval.merge(bed[x]);
3237
- Interval.index_end(bed[x]);
3238
- }
3239
- return bed;
3240
- }
3241
-
3242
- var bed = bed_fn != null? read_bed(bed_fn) : null;
3243
-
3244
- function read_vcf(fn, bed) {
3245
- var buf = new Bytes();
3246
- var file = new File(fn);
3247
- var v = {};
3248
- while (file.readline(buf) >= 0) {
3249
- var m, t = buf.toString().split("\t");
3250
- if (t[0][0] == '#') continue;
3251
- if (bed != null && bed[t[0]] == null) continue;
3252
- if (t[4] == '<INV>' || t[4] == '<INVDUP>') continue; // no inversion
3253
- if (/[\[\]]/.test(t[4])) continue; // no break points
3254
- if (t[6] != "." && t[6] != "PASS") continue;
3255
- var st = parseInt(t[1]) - 1, en = st + t[3].length;
3256
- // parse svlen
3257
- var b = _paf_get_alen(t), svlen = b[0];
3258
- var abslen = svlen == null? 0 : svlen > 0? svlen : -svlen;
3259
- if (abslen < min_flt || abslen > max_size) continue;
3260
- // update end
3261
- if ((m = /(^|;)END=(\d+)/.exec(t[7])) != null)
3262
- en = parseInt(m[2]);
3263
- else if (svlen != null && svlen < 0)
3264
- en = st + (-svlen);
3265
- if (en < st) en = st;
3266
- if (st == en) --st, ++en;
3267
- if (bed != null && Interval.find_ovlp(bed[t[0]], st, en).length == 0) continue;
3268
- // insert
3269
- if (v[t[0]] == null) v[t[0]] = [];
3270
- v[t[0]].push([st, en, svlen, abslen]);
3271
- }
3272
- file.close();
3273
- buf.destroy();
3274
- for (var x in v) {
3275
- Interval.sort(v[x]);
3276
- Interval.index_end(v[x]);
3277
- }
3278
- return v;
3279
- }
3280
-
3281
- function compare_vcf(v0, v1, label) {
3282
- var m = 0, n = 0;
3283
- for (var x in v1) {
3284
- var a1 = v1[x], a0 = v0[x];
3285
- for (var i = 0; i < a1.length; ++i) {
3286
- if (a1[i][3] < min_size) continue;
3287
- ++n;
3288
- if (a0 == null) continue;
3289
- var ws = win_size + (a1[i][3]>>1);
3290
- var st = a1[i][0] > ws? a1[i][0] - ws : 0;
3291
- b = Interval.find_ovlp(a0, st, a1[i][1] + ws);
3292
- var n_ins = 0, n_del = 0, sv_del = null, sv_ins = null;
3293
- for (var j = 0; j < b.length; ++j) {
3294
- if (b[j][2] < 0) ++n_del, sv_del = -b[j][2];
3295
- else if (b[j][2] > 0) ++n_ins, sv_ins = b[j][2];
3296
- if (print_match)
3297
- print("MA", x, a1[i].slice(0, 3).join("\t"), b[j].slice(0, 3).join("\t"));
3298
- }
3299
- var match = false;
3300
- if (a1[i][2] > 0) { // insertion
3301
- if (n_ins == 1) {
3302
- var diff = sv_ins - a1[i][3];
3303
- if (diff < 0) diff = -diff;
3304
- if (diff < min_size || diff / a1[i][3] < len_diff_ratio)
3305
- match = true;
3306
- } else if (n_ins > 1) match = true; // multiple insertions; ambiguous
3307
- } else if (a1[i][2] < 0) {
3308
- if (n_del == 1) { // deletion
3309
- var diff = sv_del - a1[i][3];
3310
- if (diff < 0) diff = -diff;
3311
- if (diff < min_size || diff / a1[i][3] < len_diff_ratio)
3312
- match = true;
3313
- } else if (n_del > 1) match = true; // multiple deletions; ambiguous
3314
- }
3315
- if (match) ++m;
3316
- else if (print_err) {
3317
- if ((a1[i][2] > 0 && n_ins > 0) || (a1[i][2] < 0 && n_del > 0))
3318
- print("MM", x, a1[i].slice(0, 3).join("\t"));
3319
- print(label, x, a1[i].slice(0, 3).join("\t"));
3320
- }
3321
- }
3322
- }
3323
- return [n, m];
3324
- }
3325
-
3326
- var v_base = read_vcf(args[getopt.ind+0], bed);
3327
- var v_call = read_vcf(args[getopt.ind+1], bed);
3328
- var fn = compare_vcf(v_call, v_base, 'FN');
3329
- var fp = compare_vcf(v_base, v_call, 'FP');
3330
- print('SN', fn[0], fn[1], (fn[1] / fn[0]).toFixed(6));
3331
- print('PC', fp[0], fp[1], (fp[1] / fp[0]).toFixed(6));
3332
- print('F1', ((fn[1] / fn[0] + fp[1] / fp[0]) / 2).toFixed(6));
3333
- }
3334
-
3335
- function paf_vcfsel(args)
3336
- {
3337
- var c, min_l = 0, max_l = 1<<30;
3338
- while ((c = getopt(args, "l:L:")) != null) {
3339
- if (c == 'l') min_l = parseInt(getopt.arg);
3340
- else if (c == 'L') max_l = parseInt(getopt.arg);
3341
- }
3342
-
3343
- var buf = new Bytes();
3344
- if (getopt.ind == args.length) {
3345
- print("Usage: paftools.js vcfsel [options] <in.vcf>");
3346
- return 1;
3347
- }
3348
- var file = args[getopt.ind] == "-"? new File() : new File(args[getopt.ind]);
3349
- while (file.readline(buf) >= 0) {
3350
- var m, line = buf.toString();
3351
- if (line[0] == '#') {
3352
- print(line);
3353
- continue;
3354
- }
3355
- var t = line.split("\t");
3356
- var st = parseInt(t[1]), en = st + t[3].length - 1;
3357
- if ((m = /(^|;)END=(\d+)/.exec(t[7])) != null)
3358
- en = parseInt(m[2]);
3359
- if (en < st) {
3360
- warn("END is smaller than POS: " + en + " < " + st);
3361
- en = st;
3362
- }
3363
- var b = _paf_get_alen(t);
3364
- var alen = b[0], min_abs_diff = b[1], max_abs_diff = b[2];
3365
- if (max_abs_diff < min_l || min_abs_diff > max_l)
3366
- continue;
3367
- print(line);
3368
- }
3369
- file.close();
3370
- buf.destroy();
3371
- }
3372
-
3373
- function paf_pafcmp(args)
3374
- {
3375
- var c, opt = { min_len:5000, min_mapq:10, min_ovlp:0.5 };
3376
- while ((c = getopt(args, "q:")) != null) {
3377
- if (c == 'q') opt.min_mapq = parseInt(getopt.arg);
3378
- }
3379
-
3380
- var buf = new Bytes();
3381
- if (args.length - getopt.ind < 2) {
3382
- print("Usage: paftools.js pafcmp [options] <base.paf> <test.paf>");
3383
- print("Options:");
3384
- print(" -q INT min mapping quality [" + opt.min_mapq + "]");
3385
- return 1;
3386
- }
3387
-
3388
- var eval = { n_base:0, n_test:0, n_out_high:0, n_out_low:0, n_hit:0, n_wrong:0, n_miss:0 };
3389
-
3390
- function process_base(base, a) {
3391
- if (a.length != 1) return;
3392
- for (var i = 1; i < 4; ++i)
3393
- a[0][i] = parseInt(a[0][i]);
3394
- for (var i = 6; i < 12; ++i)
3395
- a[0][i] = parseInt(a[0][i]);
3396
- if (a[0][1] < opt.min_len) return;
3397
- if (a[0][11] >= opt.min_mapq) ++eval.n_base;
3398
- base[a[0][0]] = [a[0][5], a[0][7], a[0][8], a[0][11], 0, 0];
3399
- }
3400
-
3401
- var file = new File(args[getopt.ind]);
3402
- warn("Reading " + args[getopt.ind] + "...");
3403
- var a = [], base = {};
3404
- while (file.readline(buf) >= 0) {
3405
- var line = buf.toString();
3406
- var t = line.split("\t");
3407
- if (/\ttp:A:S/.test(line)) continue;
3408
- if (a.length > 0 && a[0][0] != t[0]) {
3409
- process_base(base, a);
3410
- a = [];
3411
- }
3412
- a.push(t);
3413
- }
3414
- process_base(base, a);
3415
- file.close();
3416
-
3417
- function process_test(base, a) {
3418
- for (var i = 1; i < 4; ++i)
3419
- a[0][i] = parseInt(a[0][i]);
3420
- for (var i = 6; i < 12; ++i)
3421
- a[0][i] = parseInt(a[0][i]);
3422
- if (a[0][1] < opt.min_len) return;
3423
- if (a[0][11] >= opt.min_mapq) ++eval.n_test;
3424
- var c = [a[0][5], a[0][7], a[0][8], a[0][11]];
3425
- if (base[a[0][0]] == null) {
3426
- if (c[3] >= opt.min_mapq) ++opt.n_out_high;
3427
- else ++opt.n_out_low;
3428
- } else {
3429
- var b = base[a[0][0]];
3430
- var inter = 0, union = (b[2] - b[1]) + (c[2] - c[1]);
3431
- if (b[0] == c[0]) { // same chr
3432
- if (b[1] < c[1]) {
3433
- if (b[2] > c[1])
3434
- inter = b[2] - c[1], union = c[2] - b[1];
3435
- } else { // c[1] < b[1]
3436
- if (c[2] > b[1])
3437
- inter = c[2] - b[1], union = b[2] - c[1];
3438
- }
3439
- }
3440
- if (inter >= union * opt.min_ovlp) {
3441
- if (b[3] >= opt.min_mapq) ++eval.n_hit;
3442
- ++b[4];
3443
- } else {
3444
- if (b[3] >= opt.min_mapq) {
3445
- print("W", a[0][0], b.slice(0, 4).join("\t"), c.join("\t"));
3446
- ++eval.n_wrong;
3447
- }
3448
- ++b[5];
3449
- }
3450
- }
3451
- }
3452
-
3453
- file = new File(args[getopt.ind+1]);
3454
- warn("Reading " + args[getopt.ind+1] + "...");
3455
- a = [];
3456
- while (file.readline(buf) >= 0) {
3457
- var line = buf.toString();
3458
- var t = line.split("\t");
3459
- if (/\ttp:A:S/.test(line)) continue;
3460
- if (a.length > 0 && a[0][0] != t[0]) {
3461
- process_test(base, a);
3462
- a = [];
3463
- }
3464
- a.push(t);
3465
- }
3466
- process_test(base, a);
3467
- file.close();
3468
-
3469
- for (var r in base) {
3470
- var b = base[r];
3471
- if (b[3] >= opt.min_mapq && b[4] == 0 && b[5] == 0) {
3472
- ++eval.n_miss;
3473
- print("M", r, b.slice(0, 4).join("\t"));
3474
- }
3475
- }
3476
-
3477
- print("X", eval.n_base + " base alignments with mapQ>=" + opt.min_mapq);
3478
- // print("X", eval.n_test + " test alignments with mapQ>=" + opt.min_mapq);
3479
- print("X", eval.n_hit + " base alignments correctly mapped by test");
3480
- print("X", eval.n_wrong + " wrong test alignment");
3481
- print("X", eval.n_miss + " base alignments missing");
3482
- print("X", eval.n_out_high + " additional test alignments with mapQ>=" + opt.min_mapq);
3483
-
3484
- buf.destroy();
3485
- }
3486
-
3487
- function paf_longcs2seq(args) {
3488
- var c, opt = { query:false };
3489
- while ((c = getopt(args, "q")) != null)
3490
- if (c == 'q') opt.query = true;
3491
- if (args.length == getopt.ind) {
3492
- print("Usage: paftools.js longcs2seq [-q] <long-cs.paf>");
3493
- return;
3494
- }
3495
- var re_cs = /([:=*+-])(\d+|[A-Za-z]+)/g
3496
- var buf = new Bytes();
3497
- var file = args[getopt.ind] == "-"? new File() : new File(args[getopt.ind]);
3498
- while (file.readline(buf) >= 0) {
3499
- var m, cs = null, t = buf.toString().split("\t");
3500
- for (var i = 12; i < t.length; ++i)
3501
- if ((m = /^cs:Z:(\S+)/.exec(t[i])) != null) {
3502
- cs = m[1];
3503
- break;
3504
- }
3505
- if (cs == null) continue;
3506
- var ts = "", qs = "";
3507
- while ((m = re_cs.exec(cs)) != null) {
3508
- if (m[1] == "=") ts += m[2], qs += m[2];
3509
- else if (m[1] == "+") qs += m[2].toUpperCase();
3510
- else if (m[1] == "-") ts += m[2].toUpperCase();
3511
- else if (m[1] == "*") ts += m[2][0].toUpperCase(), qs += m[2][1].toUpperCase();
3512
- else if (m[1] == ":") throw Error("Long cs is required");
3513
- }
3514
- if (opt.query) {
3515
- print(">" + t[0] + "_" + t[2] + "_" + t[3]);
3516
- print(qs);
3517
- } else {
3518
- print(">" + t[5] + "_" + t[7] + "_" + t[8]);
3519
- print(ts);
3520
- }
3521
- }
3522
- file.close();
3523
- buf.destroy();
3524
- }
3525
-
3526
- function paf_paf2gff(args) {
3527
- var c, opt = { aa:false };
3528
- var re_cigar = /(\d+)([A-Z=])/g;
3529
- while ((c = getopt(args, "a")) != null) {
3530
- if (c == 'a') opt.aa = true;
3531
- }
3532
- if (args.length == getopt.ind) {
3533
- print("Usage: paftools.js paf2gff [-a] <in.paf>");
3534
- return;
3535
- }
3536
- var buf = new Bytes();
3537
- var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]);
3538
- var hid = 1, last_name = null;
3539
- while (file.readline(buf) >= 0) {
3540
- var m, t = buf.toString().split("\t");
3541
- if (t[5] == '*') continue; // skip unmapped lines
3542
-
3543
- if (t[0] != last_name) last_name = t[0], hid = 1;
3544
- else ++hid;
3545
- for (var i = 1; i <= 3; ++i) t[i] = parseInt(t[i]);
3546
- for (var i = 6; i <= 11; ++i) t[i] = parseInt(t[i]);
3547
- var cigar = null, score = null, np = null, dist_stop = null, dist_start = null;
3548
- for (var i = 12; i < t.length; ++i) {
3549
- if ((m = /^(cg:Z|AS:i|np:i|da:i|do:i):(\S+)/.exec(t[i])) != null) {
3550
- if (m[1] == 'cg:Z') cigar = m[2];
3551
- else if (m[1] == 'AS:i') score = parseInt(m[2]);
3552
- else if (m[1] == 'np:i') np = parseInt(m[2]);
3553
- else if (m[1] == 'do:i') dist_stop = parseInt(m[2]);
3554
- else if (m[1] == 'da:i') dist_start = parseInt(m[2]);
3555
- }
3556
- }
3557
- if (cigar == null) throw Error("failed to find the cg:Z tag");
3558
- if (score == null) throw Error("failed to find the AS:i tag");
3559
-
3560
- var st = 0, en = 0, phase = 0, pseudo = false, fs = 0, a = [];
3561
- if (dist_start != null && dist_start == 0)
3562
- a.push([t[5], 'paf2gff', 'start_codon', 0, 3, 0, t[4], '.', 0]);
3563
- while ((m = re_cigar.exec(cigar)) != null) {
3564
- var len = parseInt(m[1]);
3565
- if (m[2] == 'M' || m[2] == 'D') {
3566
- en += opt.aa? len * 3 : len;
3567
- } else if (m[2] == 'F' || m[2] == 'G' || m[2] == 'R') {
3568
- en += len, pseudo = true, fs = 1;
3569
- } else if (m[2] == 'N') {
3570
- a.push([t[5], 'paf2gff', 'exon', st, en, 0, t[4], phase, fs]);
3571
- st = en + len, en += len, phase = 0, fs = 0;
3572
- } else if (m[2] == 'U') { // ...xGT...AGxx...
3573
- a.push([t[5], 'paf2gff', 'exon', st, en + 1, 0, t[4], phase, fs]);
3574
- st = en + len - 2, en += len, phase = 2, fs = 0;
3575
- } else if (m[2] == 'V') { // ...xxGT...AGx...
3576
- a.push([t[5], 'paf2gff', 'exon', st, en + 2, 0, t[4], phase, fs]);
3577
- st = en + len - 1, en += len, phase = 1, fs = 0;
3578
- }
3579
- }
3580
- a.push([t[5], 'paf2gff', 'exon', st, en, 0, t[4], phase, fs]);
3581
- if (en != t[8] - t[7]) throw Error("inconsistent cigar");
3582
- if (dist_stop != null && dist_stop == 0)
3583
- a.push([t[5], 'paf2gff', 'stop_codon', en, en + 3, 0, t[4], '.', 0]);
3584
- var type = pseudo? 'pseudogene' : 'protein_coding';
3585
- var attr = ['transcript_id=' + t[0] + '#' + hid, 'transcript_type=' + type].join(";");
3586
- var trans_attr = 'identity=' + (t[9] / t[10]).toFixed(4);
3587
- if (np != null) trans_attr += ';positive=' + (np * 3 / t[10]).toFixed(4);
3588
- trans_attr += ';aa_start=' + t[2];
3589
- trans_attr += ';aa_end=' + (t[1] - t[3]);
3590
- if (dist_start != null && dist_start >= 0) trans_attr += ';dist_start_codon=' + dist_start;
3591
- if (dist_stop != null && dist_stop >= 0) trans_attr += ';dist_stop_codon=' + dist_stop;
3592
- var trans_st = t[7], trans_en = t[8];
3593
- if (dist_stop != null && dist_stop == 0) {
3594
- if (t[4] == '-') trans_st -= 3;
3595
- else trans_en += 3;
3596
- }
3597
- print([t[5], 'paf2gff', 'transcript', trans_st + 1, trans_en, score, t[4], '.', attr + ';' + trans_attr].join("\t"));
3598
- if (opt.aa && t[4] == '-') {
3599
- var b = [], len = t[8] - t[7];
3600
- for (var i = a.length - 1; i >= 0; --i) {
3601
- var x = len - a[i][3];
3602
- a[i][3] = len - a[i][4];
3603
- a[i][4] = x;
3604
- //a[i][7] = a[i][7] == 0? 0 : 3 - a[i][7]; // not sure if this line is needed
3605
- b.push(a[i]);
3606
- }
3607
- a = b;
3608
- }
3609
- for (var i = 0; i < a.length; ++i) {
3610
- if (!pseudo && a[i][2] == "exon") a[i][2] = "CDS";
3611
- a[i][3] += t[7] + 1;
3612
- a[i][4] += t[7];
3613
- a[i][8] = attr + ";frameshift=" + a[i][8];
3614
- print(a[i].join("\t"));
3615
- }
3616
- }
3617
- file.close();
3618
- buf.destroy();
3619
- }
3620
-
3621
- function paf_gff2junc(args) {
3622
- var c, feat = "CDS";
3623
- while ((c = getopt(args, "f:")) != null) {
3624
- if (c == 'f') feat = getopt.arg;
3625
- }
3626
- if (getopt.ind == args.length) {
3627
- print("Usage: paftools.js gff2junc [-f feature] <in.gff3>");
3628
- return;
3629
- }
3630
- var buf = new Bytes();
3631
- var file = args[getopt.ind] == "-"? new File() : new File(args[getopt.ind]);
3632
-
3633
- function process_a(a) {
3634
- if (a.length < 2) return;
3635
- a = a.sort(function(x, y) { return x[4] - y[4] });
3636
- for (var i = 1; i < a.length; ++i)
3637
- print([a[i][1], a[i-1][5], a[i][4], a[i][0], 0, a[i][7]].join("\t"));
3638
- }
3639
-
3640
- var a = [];
3641
- while (file.readline(buf) >= 0) {
3642
- var m, t = buf.toString().split("\t");
3643
- if (t[0][0] == '#') continue;
3644
- if (t[2].toLowerCase() != feat.toLowerCase()) continue;
3645
- //print(t.join("\t"));
3646
- if ((m = /\bParent=([^;]+)/.exec(t[8])) == null) {
3647
- warn("Can't find Parent");
3648
- continue;
3649
- }
3650
- t[3] = parseInt(t[3]) - 1;
3651
- t[4] = parseInt(t[4]);
3652
- t.unshift(m[1]);
3653
- if (a.length > 0 && a[0][0] != m[1]) {
3654
- process_a(a);
3655
- a.length = 0;
3656
- a.push(t);
3657
- } else a.push(t);
3658
- }
3659
- process_a(a);
3660
- file.close();
3661
- buf.destroy();
3662
- }
3663
-
3664
- /*************************
3665
- ***** main function *****
3666
- *************************/
3667
-
3668
- function main(args)
3669
- {
3670
- if (args.length == 0) {
3671
- print("Usage: paftools.js <command> [arguments]");
3672
- print("Commands:");
3673
- print(" view convert PAF to BLAST-like (for eyeballing) or MAF");
3674
- print(" splice2bed convert spliced alignment in PAF/SAM to BED12");
3675
- print(" sam2paf convert SAM to PAF");
3676
- print(" delta2paf convert MUMmer's delta to PAF");
3677
- print(" gff2bed convert GTF/GFF3 to BED12");
3678
- print(" gff2junc convert GFF3 to junction BED");
3679
- print(" longcs2seq convert long-cs PAF to sequences");
3680
- // print(" paf2gff convert PAF to GFF3 (tested for miniprot only)");
3681
- print("");
3682
- print(" stat collect basic mapping information in PAF/SAM");
3683
- print(" asmstat collect basic assembly information");
3684
- print(" asmgene evaluate gene completeness");
3685
- print(" misjoin evaluate large-scale misjoins");
3686
- print(" liftover simplistic liftOver");
3687
- print(" call call variants from asm-to-ref alignment with the cs tag");
3688
- print(" bedcov compute the number of bases covered");
3689
- print(" vcfstat VCF statistics");
3690
- print(" sveval compare two SV callsets in VCF");
3691
- print(" version print paftools.js version");
3692
- print("");
3693
- print(" mapeval evaluate mapping accuracy using mason2/PBSIM-simulated FASTQ");
3694
- print(" pafcmp compare two PAF files");
3695
- print(" mason2fq convert mason2-simulated SAM to FASTQ");
3696
- print(" pbsim2fq convert PBSIM-simulated MAF to FASTQ");
3697
- print(" junceval evaluate splice junction consistency with known annotations");
3698
- print(" exoneval evaluate exon-level consistency with known annotations");
3699
- print(" ov-eval evaluate read overlap sensitivity using read-to-ref mapping");
3700
- exit(1);
3701
- }
3702
-
3703
- var cmd = args.shift();
3704
- if (cmd == 'view') paf_view(args);
3705
- else if (cmd == 'sam2paf') paf_sam2paf(args);
3706
- else if (cmd == 'delta2paf') paf_delta2paf(args);
3707
- else if (cmd == 'splice2bed') paf_splice2bed(args);
3708
- else if (cmd == 'gff2bed') paf_gff2bed(args);
3709
- else if (cmd == 'gff2junc') paf_gff2junc(args);
3710
- else if (cmd == 'stat') paf_stat(args);
3711
- else if (cmd == 'asmstat') paf_asmstat(args);
3712
- else if (cmd == 'asmgene') paf_asmgene(args);
3713
- else if (cmd == 'misjoin') paf_misjoin(args);
3714
- else if (cmd == 'liftover' || cmd == 'liftOver') paf_liftover(args);
3715
- else if (cmd == 'vcfpair') paf_vcfpair(args);
3716
- else if (cmd == 'call') paf_call(args);
3717
- else if (cmd == 'mapeval') paf_mapeval(args);
3718
- else if (cmd == 'pafcmp') paf_pafcmp(args);
3719
- else if (cmd == 'bedcov') paf_bedcov(args);
3720
- else if (cmd == 'mason2fq') paf_mason2fq(args);
3721
- else if (cmd == 'pbsim2fq') paf_pbsim2fq(args);
3722
- else if (cmd == 'junceval') paf_junceval(args);
3723
- else if (cmd == 'exoneval') paf_exoneval(args);
3724
- else if (cmd == 'ov-eval') paf_ov_eval(args);
3725
- else if (cmd == 'vcfstat') paf_vcfstat(args);
3726
- else if (cmd == 'sveval') paf_sveval(args);
3727
- else if (cmd == 'vcfsel') paf_vcfsel(args);
3728
- else if (cmd == 'longcs2seq') paf_longcs2seq(args);
3729
- else if (cmd == 'paf2gff') paf_paf2gff(args);
3730
- else if (cmd == 'version') print(paftools_version);
3731
- else throw Error("unrecognized command: " + cmd);
3732
- }
3733
-
3734
- main(arguments);