@jbrowse/cigar-utils 4.1.14 → 5.0.0-beta.2
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.
- package/README.md +75 -0
- package/esm/cigarConstants.d.ts +1 -0
- package/esm/cigarConstants.js +5 -0
- package/esm/cigarRenderedSegments.d.ts +3 -1
- package/esm/cigarRenderedSegments.js +14 -4
- package/esm/cigarReorient.js +28 -18
- package/esm/coarseCigar.d.ts +54 -0
- package/esm/coarseCigar.js +235 -0
- package/esm/index.d.ts +3 -1
- package/esm/index.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,43 @@ a caller may not have the assembly in hand; pass it when you do.
|
|
|
32
32
|
|
|
33
33
|
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/buildReadVsRefFeatures.ts)
|
|
34
34
|
|
|
35
|
+
### coarseCigarOwnAxis
|
|
36
|
+
|
|
37
|
+
The coarse CIGAR as a plain CIGAR over the row's own axis, for a walk that
|
|
38
|
+
places mismatches by reference position: a run keeps only its own length, a run
|
|
39
|
+
with no own length is the insertion it amounts to, and one with no mate length
|
|
40
|
+
the deletion. Kept indels are exact, so they land where the fine tier's would.
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
// type signature
|
|
44
|
+
(s: string) => string
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/coarseCigar.ts)
|
|
48
|
+
|
|
49
|
+
### coarsenCigar
|
|
50
|
+
|
|
51
|
+
Fold a CIGAR into a coarse CIGAR whose straight lines stay within `gap` of the
|
|
52
|
+
alignment's own path. An indel longer than half the gap keeps its letter and
|
|
53
|
+
length (D/N/I); a shorter one is absorbed into the match run around it, and a
|
|
54
|
+
run whose two axes advanced by different amounts is written `<own>:<mate>M`, a
|
|
55
|
+
square run `<n>M`. `=`/`X` count as M. `ownLen`/`mateLen` are the totals each
|
|
56
|
+
axis consumed, for the caller to check against the row's coordinate columns.
|
|
57
|
+
|
|
58
|
+
The bound: a run is closed before a folded indel would push its running skew
|
|
59
|
+
(own minus mate) past half the gap, and no folded indel is longer than that, so
|
|
60
|
+
inside a run the skew never exceeds half the gap and the line between the run's
|
|
61
|
+
corners is never more than the gap off the path. That is what lets a reader
|
|
62
|
+
interpolate within a run, and it costs a new run only where the two genomes'
|
|
63
|
+
small indels are lopsided.
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
// type signature
|
|
67
|
+
(cigar: string, gap: number) => CoarsenedCigar
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/coarseCigar.ts)
|
|
71
|
+
|
|
35
72
|
### featurizeSAEntries
|
|
36
73
|
|
|
37
74
|
featurizeSA over pre-split entries (see splitSA). Lets a caller filter the
|
|
@@ -45,6 +82,18 @@ segments — without paying to split and rejoin the tag around the filter.
|
|
|
45
82
|
|
|
46
83
|
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/mismatchParser.ts)
|
|
47
84
|
|
|
85
|
+
### flipCoarseCigar
|
|
86
|
+
|
|
87
|
+
The coarse CIGAR seen from the other axis on the minus strand: swapped as
|
|
88
|
+
`swapCoarseCigar` does, and in reverse op order. The twin of `flipCigar`.
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
// type signature
|
|
92
|
+
(s: string) => string
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/coarseCigar.ts)
|
|
96
|
+
|
|
48
97
|
### getLength
|
|
49
98
|
|
|
50
99
|
Length of the read sequence (sum of all ops except D/N).
|
|
@@ -152,6 +201,19 @@ single typed-array code path.
|
|
|
152
201
|
|
|
153
202
|
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/mismatchParser.ts)
|
|
154
203
|
|
|
204
|
+
### parseCoarseCigar
|
|
205
|
+
|
|
206
|
+
Parse a coarse CIGAR into the packed `(len << 4) | op` form
|
|
207
|
+
`visitCigarRenderedSegments` walks. A square run packs as one `CIGAR_M` word; an
|
|
208
|
+
unequal run as a `CIGAR_RUN` word pair, own axis first.
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
// type signature
|
|
212
|
+
(s: string) => Uint32Array<ArrayBuffer>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/coarseCigar.ts)
|
|
216
|
+
|
|
155
217
|
### splitSA
|
|
156
218
|
|
|
157
219
|
The `;`-separated alignment records of an SA tag, empties dropped — the input
|
|
@@ -164,4 +226,17 @@ The `;`-separated alignment records of an SA tag, empties dropped — the input
|
|
|
164
226
|
|
|
165
227
|
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/mismatchParser.ts)
|
|
166
228
|
|
|
229
|
+
### swapCoarseCigar
|
|
230
|
+
|
|
231
|
+
The coarse CIGAR seen from the other axis on the plus strand: the two lengths of
|
|
232
|
+
every run trade places and D<->I swap, op order untouched. The twin of
|
|
233
|
+
`swapIndelCigar`.
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
// type signature
|
|
237
|
+
(s: string) => string
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
[Source code](https://github.com/GMOD/jbrowse-components/blob/main/packages/cigar-utils/src/coarseCigar.ts)
|
|
241
|
+
|
|
167
242
|
<!-- API_DOCS_END -->
|
package/esm/cigarConstants.d.ts
CHANGED
package/esm/cigarConstants.js
CHANGED
|
@@ -13,3 +13,8 @@ export const CIGAR_X = 8;
|
|
|
13
13
|
export const CIGAR_M_EQ_MASK = 0b10000001; // (1<<0)|(1<<7) = 129
|
|
14
14
|
// Indel ops (I=1, D=2, N=3) — each consumes only one axis (a gap on the other)
|
|
15
15
|
export const CIGAR_INDEL_MASK = 0b00001110; // (1<<1)|(1<<2)|(1<<3) = 14
|
|
16
|
+
// Not a BAM op: a match run whose two axes advance by different lengths, the
|
|
17
|
+
// coarse tier's fold of the small indels between two kept gaps. Packed as two
|
|
18
|
+
// consecutive words, `(ownLen << 4) | CIGAR_RUN` then `(mateLen << 4) |
|
|
19
|
+
// CIGAR_RUN`. Only parseCoarseCigar writes it.
|
|
20
|
+
export const CIGAR_RUN = 9;
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Walks pre-parsed (packed int) CIGAR ops in bp-space and fires a callback
|
|
3
3
|
* for each rendered segment. Small indels (width < MIN_INDEL_PX) are merged
|
|
4
4
|
* into surrounding context; tiny M segments (both accumulators advance <
|
|
5
|
-
* bpPerPx) are accumulated before emitting.
|
|
5
|
+
* bpPerPx) are accumulated before emitting. A `CIGAR_RUN` word pair (the
|
|
6
|
+
* coarse tier's fold of a run with its small indels) advances each axis by its
|
|
7
|
+
* own length and is reported as CIGAR_M.
|
|
6
8
|
*
|
|
7
9
|
* Used by synteny and dotplot GPU renderers so both stay in sync.
|
|
8
10
|
* Re-exported via @jbrowse/synteny-core for consistent import paths.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CIGAR_D, CIGAR_EQ, CIGAR_I, CIGAR_M, CIGAR_N, CIGAR_X, } from './cigarConstants.js';
|
|
1
|
+
import { CIGAR_D, CIGAR_EQ, CIGAR_I, CIGAR_M, CIGAR_N, CIGAR_RUN, CIGAR_X, } from './cigarConstants.js';
|
|
2
2
|
// Indels narrower than this (in pixels) are merged into surrounding match
|
|
3
3
|
// context rather than rendered as separate quads. At 1px an indel resolving to
|
|
4
4
|
// a single pixel still renders — small on-screen detail is intentionally kept.
|
|
@@ -12,7 +12,9 @@ const MIN_INDEL_PX = 1;
|
|
|
12
12
|
* Walks pre-parsed (packed int) CIGAR ops in bp-space and fires a callback
|
|
13
13
|
* for each rendered segment. Small indels (width < MIN_INDEL_PX) are merged
|
|
14
14
|
* into surrounding context; tiny M segments (both accumulators advance <
|
|
15
|
-
* bpPerPx) are accumulated before emitting.
|
|
15
|
+
* bpPerPx) are accumulated before emitting. A `CIGAR_RUN` word pair (the
|
|
16
|
+
* coarse tier's fold of a run with its small indels) advances each axis by its
|
|
17
|
+
* own length and is reported as CIGAR_M.
|
|
16
18
|
*
|
|
17
19
|
* Used by synteny and dotplot GPU renderers so both stay in sync.
|
|
18
20
|
* Re-exported via @jbrowse/synteny-core for consistent import paths.
|
|
@@ -34,7 +36,11 @@ export function visitCigarRenderedSegments(cigar, startBp1, startBp2, bpPerPx0,
|
|
|
34
36
|
segBp1Start = bp1;
|
|
35
37
|
segBp2Start = bp2;
|
|
36
38
|
}
|
|
37
|
-
if (op ===
|
|
39
|
+
if (op === CIGAR_RUN) {
|
|
40
|
+
bp1 += len * rev1;
|
|
41
|
+
bp2 += (cigar[++j] >>> 4) * rev2;
|
|
42
|
+
}
|
|
43
|
+
else if (op === CIGAR_M || op === CIGAR_EQ || op === CIGAR_X) {
|
|
38
44
|
bp1 += len * rev1;
|
|
39
45
|
bp2 += len * rev2;
|
|
40
46
|
}
|
|
@@ -60,7 +66,11 @@ export function visitCigarRenderedSegments(cigar, startBp1, startBp2, bpPerPx0,
|
|
|
60
66
|
else {
|
|
61
67
|
const span1 = Math.abs(bp1 - segBp1Start);
|
|
62
68
|
const span2 = Math.abs(bp2 - segBp2Start);
|
|
63
|
-
const resolvedOp = span1 > bpPerPx0 || span2 > bpPerPx1
|
|
69
|
+
const resolvedOp = span1 > bpPerPx0 || span2 > bpPerPx1
|
|
70
|
+
? op === CIGAR_RUN
|
|
71
|
+
? CIGAR_M
|
|
72
|
+
: op
|
|
73
|
+
: CIGAR_M;
|
|
64
74
|
continuingFlag = false;
|
|
65
75
|
callback(resolvedOp, segBp1Start, bp1, segBp2Start, bp2);
|
|
66
76
|
}
|
package/esm/cigarReorient.js
CHANGED
|
@@ -1,29 +1,39 @@
|
|
|
1
|
+
// Both re-orientations keep every byte of the CIGAR and change only which
|
|
2
|
+
// letter an indel wears (and, for the flip, the op order), so each is written
|
|
3
|
+
// straight into a byte buffer the size of its input rather than a string built
|
|
4
|
+
// op by op: 2-3x faster on a multi-kb PAF CIGAR, which make-pif does twice a
|
|
5
|
+
// row and orientAlignment does per fetched row.
|
|
6
|
+
function swapIndelByte(code) {
|
|
7
|
+
return code === 68 ? 73 : code === 73 ? 68 : code;
|
|
8
|
+
}
|
|
1
9
|
// Reverse a CIGAR's op order and swap insertions<->deletions. Used to view an
|
|
2
10
|
// alignment from the opposite (query<->target) perspective on the reverse
|
|
3
11
|
// strand, where both the direction and the indel sense flip.
|
|
4
12
|
export function flipCigar(cigar) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const src = new TextEncoder().encode(cigar);
|
|
14
|
+
const n = src.length;
|
|
15
|
+
const out = new Uint8Array(n);
|
|
16
|
+
let pos = n;
|
|
17
|
+
let start = 0;
|
|
18
|
+
for (let i = 0; i < n; i++) {
|
|
19
|
+
const code = src[i];
|
|
20
|
+
if (code < 48 || code > 57) {
|
|
21
|
+
pos -= i + 1 - start;
|
|
22
|
+
for (let k = start, p = pos; k < i; k++, p++) {
|
|
23
|
+
out[p] = src[k];
|
|
24
|
+
}
|
|
25
|
+
out[pos + i - start] = swapIndelByte(code);
|
|
26
|
+
start = i + 1;
|
|
15
27
|
}
|
|
16
28
|
}
|
|
17
|
-
|
|
18
|
-
for (let i = ops.length - 1; i >= 0; i--) {
|
|
19
|
-
const [l, op] = ops[i];
|
|
20
|
-
result += l;
|
|
21
|
-
result += op === 'D' ? 'I' : op === 'I' ? 'D' : op;
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
29
|
+
return new TextDecoder().decode(out.subarray(pos));
|
|
24
30
|
}
|
|
25
31
|
// Swap insertions<->deletions in place, leaving op order untouched. This is the
|
|
26
32
|
// +-strand query<->target perspective flip, where only the indel sense reverses.
|
|
27
33
|
export function swapIndelCigar(cigar) {
|
|
28
|
-
|
|
34
|
+
const bytes = new TextEncoder().encode(cigar);
|
|
35
|
+
for (let i = 0, n = bytes.length; i < n; i++) {
|
|
36
|
+
bytes[i] = swapIndelByte(bytes[i]);
|
|
37
|
+
}
|
|
38
|
+
return new TextDecoder().decode(bytes);
|
|
29
39
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface CoarsenedCigar {
|
|
2
|
+
ops: string;
|
|
3
|
+
ownLen: number;
|
|
4
|
+
mateLen: number;
|
|
5
|
+
gapCount: number;
|
|
6
|
+
opCount: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* #api
|
|
10
|
+
* Fold a CIGAR into a coarse CIGAR whose straight lines stay within `gap` of
|
|
11
|
+
* the alignment's own path. An indel longer than half the gap keeps its letter
|
|
12
|
+
* and length (D/N/I); a shorter one is absorbed into the match run around it,
|
|
13
|
+
* and a run whose two axes advanced by different amounts is written
|
|
14
|
+
* `<own>:<mate>M`, a square run `<n>M`. `=`/`X` count as M. `ownLen`/`mateLen`
|
|
15
|
+
* are the totals each axis consumed, for the caller to check against the row's
|
|
16
|
+
* coordinate columns.
|
|
17
|
+
*
|
|
18
|
+
* The bound: a run is closed before a folded indel would push its running
|
|
19
|
+
* skew (own minus mate) past half the gap, and no folded indel is longer than
|
|
20
|
+
* that, so inside a run the skew never exceeds half the gap and the line
|
|
21
|
+
* between the run's corners is never more than the gap off the path. That is
|
|
22
|
+
* what lets a reader interpolate within a run, and it costs a new run only
|
|
23
|
+
* where the two genomes' small indels are lopsided.
|
|
24
|
+
*/
|
|
25
|
+
export declare function coarsenCigar(cigar: string, gap: number): CoarsenedCigar;
|
|
26
|
+
/**
|
|
27
|
+
* #api
|
|
28
|
+
* The coarse CIGAR seen from the other axis on the plus strand: the two lengths
|
|
29
|
+
* of every run trade places and D<->I swap, op order untouched. The twin of
|
|
30
|
+
* `swapIndelCigar`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function swapCoarseCigar(s: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* #api
|
|
35
|
+
* The coarse CIGAR seen from the other axis on the minus strand: swapped as
|
|
36
|
+
* `swapCoarseCigar` does, and in reverse op order. The twin of `flipCigar`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function flipCoarseCigar(s: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* #api
|
|
41
|
+
* The coarse CIGAR as a plain CIGAR over the row's own axis, for a walk that
|
|
42
|
+
* places mismatches by reference position: a run keeps only its own length, a
|
|
43
|
+
* run with no own length is the insertion it amounts to, and one with no mate
|
|
44
|
+
* length the deletion. Kept indels are exact, so they land where the fine
|
|
45
|
+
* tier's would.
|
|
46
|
+
*/
|
|
47
|
+
export declare function coarseCigarOwnAxis(s: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* #api
|
|
50
|
+
* Parse a coarse CIGAR into the packed `(len << 4) | op` form
|
|
51
|
+
* `visitCigarRenderedSegments` walks. A square run packs as one `CIGAR_M`
|
|
52
|
+
* word; an unequal run as a `CIGAR_RUN` word pair, own axis first.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseCoarseCigar(s: string): Uint32Array<ArrayBuffer>;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { CIGAR_D, CIGAR_I, CIGAR_M, CIGAR_N, CIGAR_RUN, } from './cigarConstants.js';
|
|
2
|
+
/**
|
|
3
|
+
* #api
|
|
4
|
+
* Fold a CIGAR into a coarse CIGAR whose straight lines stay within `gap` of
|
|
5
|
+
* the alignment's own path. An indel longer than half the gap keeps its letter
|
|
6
|
+
* and length (D/N/I); a shorter one is absorbed into the match run around it,
|
|
7
|
+
* and a run whose two axes advanced by different amounts is written
|
|
8
|
+
* `<own>:<mate>M`, a square run `<n>M`. `=`/`X` count as M. `ownLen`/`mateLen`
|
|
9
|
+
* are the totals each axis consumed, for the caller to check against the row's
|
|
10
|
+
* coordinate columns.
|
|
11
|
+
*
|
|
12
|
+
* The bound: a run is closed before a folded indel would push its running
|
|
13
|
+
* skew (own minus mate) past half the gap, and no folded indel is longer than
|
|
14
|
+
* that, so inside a run the skew never exceeds half the gap and the line
|
|
15
|
+
* between the run's corners is never more than the gap off the path. That is
|
|
16
|
+
* what lets a reader interpolate within a run, and it costs a new run only
|
|
17
|
+
* where the two genomes' small indels are lopsided.
|
|
18
|
+
*/
|
|
19
|
+
export function coarsenCigar(cigar, gap) {
|
|
20
|
+
const skewBound = gap / 2;
|
|
21
|
+
let ops = '';
|
|
22
|
+
let ownLen = 0;
|
|
23
|
+
let mateLen = 0;
|
|
24
|
+
let gapCount = 0;
|
|
25
|
+
let opCount = 0;
|
|
26
|
+
let runOwn = 0;
|
|
27
|
+
let runMate = 0;
|
|
28
|
+
// a run with nothing on one side is the indel it amounts to, and is written
|
|
29
|
+
// as one, so a reader never meets a zero-sided run
|
|
30
|
+
const flushRun = () => {
|
|
31
|
+
if (runOwn > 0 || runMate > 0) {
|
|
32
|
+
ops +=
|
|
33
|
+
runOwn === runMate
|
|
34
|
+
? `${runOwn}M`
|
|
35
|
+
: runOwn === 0
|
|
36
|
+
? `${runMate}I`
|
|
37
|
+
: runMate === 0
|
|
38
|
+
? `${runOwn}D`
|
|
39
|
+
: `${runOwn}:${runMate}M`;
|
|
40
|
+
opCount++;
|
|
41
|
+
runOwn = 0;
|
|
42
|
+
runMate = 0;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const keep = (len, op) => {
|
|
46
|
+
flushRun();
|
|
47
|
+
ops += `${len}${op}`;
|
|
48
|
+
gapCount++;
|
|
49
|
+
opCount++;
|
|
50
|
+
};
|
|
51
|
+
const fold = (own, mate) => {
|
|
52
|
+
if (Math.abs(runOwn + own - runMate - mate) > skewBound) {
|
|
53
|
+
flushRun();
|
|
54
|
+
}
|
|
55
|
+
runOwn += own;
|
|
56
|
+
runMate += mate;
|
|
57
|
+
};
|
|
58
|
+
let len = 0;
|
|
59
|
+
for (let i = 0, l = cigar.length; i < l; i++) {
|
|
60
|
+
const code = cigar.charCodeAt(i);
|
|
61
|
+
if (code >= 48 && code <= 57) {
|
|
62
|
+
len = len * 10 + code - 48;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const op = cigar[i];
|
|
66
|
+
if (op === 'M' || op === '=' || op === 'X') {
|
|
67
|
+
runOwn += len;
|
|
68
|
+
runMate += len;
|
|
69
|
+
ownLen += len;
|
|
70
|
+
mateLen += len;
|
|
71
|
+
}
|
|
72
|
+
else if (op === 'D' || op === 'N') {
|
|
73
|
+
ownLen += len;
|
|
74
|
+
if (len > skewBound) {
|
|
75
|
+
keep(len, op);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
fold(len, 0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (op === 'I') {
|
|
82
|
+
mateLen += len;
|
|
83
|
+
if (len > skewBound) {
|
|
84
|
+
keep(len, op);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
fold(0, len);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
len = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
flushRun();
|
|
94
|
+
return { ops, ownLen, mateLen, gapCount, opCount };
|
|
95
|
+
}
|
|
96
|
+
function parseCoarseOps(s) {
|
|
97
|
+
const out = [];
|
|
98
|
+
let len = 0;
|
|
99
|
+
let ownBeforeColon;
|
|
100
|
+
for (let i = 0, l = s.length; i < l; i++) {
|
|
101
|
+
const code = s.charCodeAt(i);
|
|
102
|
+
if (code >= 48 && code <= 57) {
|
|
103
|
+
len = len * 10 + code - 48;
|
|
104
|
+
}
|
|
105
|
+
else if (code === 58) {
|
|
106
|
+
ownBeforeColon = len;
|
|
107
|
+
len = 0;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// the alphabet is M I D N plus the run; a reader also takes =/X as M and
|
|
111
|
+
// steps over clips and padding, which a writer never emits
|
|
112
|
+
const op = s[i];
|
|
113
|
+
if (op === 'M' || op === '=' || op === 'X') {
|
|
114
|
+
out.push({
|
|
115
|
+
own: ownBeforeColon === undefined ? len : ownBeforeColon,
|
|
116
|
+
mate: len,
|
|
117
|
+
op: 'M',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else if (op === 'I') {
|
|
121
|
+
out.push({ own: 0, mate: len, op });
|
|
122
|
+
}
|
|
123
|
+
else if (op === 'D' || op === 'N') {
|
|
124
|
+
out.push({ own: len, mate: 0, op });
|
|
125
|
+
}
|
|
126
|
+
ownBeforeColon = undefined;
|
|
127
|
+
len = 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function serializeCoarseOps(ops) {
|
|
133
|
+
let s = '';
|
|
134
|
+
for (const { own, mate, op } of ops) {
|
|
135
|
+
s +=
|
|
136
|
+
op === 'M'
|
|
137
|
+
? own === mate
|
|
138
|
+
? `${own}M`
|
|
139
|
+
: `${own}:${mate}M`
|
|
140
|
+
: `${op === 'I' ? mate : own}${op}`;
|
|
141
|
+
}
|
|
142
|
+
return s;
|
|
143
|
+
}
|
|
144
|
+
// N keeps its axis as well as its letter, matching `swapIndelCigar`, which
|
|
145
|
+
// swaps only D<->I
|
|
146
|
+
function swapAxes({ own, mate, op }) {
|
|
147
|
+
return op === 'N'
|
|
148
|
+
? { own, mate, op }
|
|
149
|
+
: {
|
|
150
|
+
own: mate,
|
|
151
|
+
mate: own,
|
|
152
|
+
op: op === 'D' ? 'I' : op === 'I' ? 'D' : op,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* #api
|
|
157
|
+
* The coarse CIGAR seen from the other axis on the plus strand: the two lengths
|
|
158
|
+
* of every run trade places and D<->I swap, op order untouched. The twin of
|
|
159
|
+
* `swapIndelCigar`.
|
|
160
|
+
*/
|
|
161
|
+
export function swapCoarseCigar(s) {
|
|
162
|
+
return serializeCoarseOps(parseCoarseOps(s).map(swapAxes));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* #api
|
|
166
|
+
* The coarse CIGAR seen from the other axis on the minus strand: swapped as
|
|
167
|
+
* `swapCoarseCigar` does, and in reverse op order. The twin of `flipCigar`.
|
|
168
|
+
*/
|
|
169
|
+
export function flipCoarseCigar(s) {
|
|
170
|
+
return serializeCoarseOps(parseCoarseOps(s).map(swapAxes).reverse());
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* #api
|
|
174
|
+
* The coarse CIGAR as a plain CIGAR over the row's own axis, for a walk that
|
|
175
|
+
* places mismatches by reference position: a run keeps only its own length, a
|
|
176
|
+
* run with no own length is the insertion it amounts to, and one with no mate
|
|
177
|
+
* length the deletion. Kept indels are exact, so they land where the fine
|
|
178
|
+
* tier's would.
|
|
179
|
+
*/
|
|
180
|
+
export function coarseCigarOwnAxis(s) {
|
|
181
|
+
let out = '';
|
|
182
|
+
for (const { own, mate, op } of parseCoarseOps(s)) {
|
|
183
|
+
out +=
|
|
184
|
+
op === 'M'
|
|
185
|
+
? own === 0
|
|
186
|
+
? `${mate}I`
|
|
187
|
+
: mate === 0
|
|
188
|
+
? `${own}D`
|
|
189
|
+
: `${own}M`
|
|
190
|
+
: `${op === 'I' ? mate : own}${op}`;
|
|
191
|
+
}
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
// The packed form keeps 28 bits of length, so a longer op is written as several
|
|
195
|
+
// words that sum to it. A run is split with both axes in proportion.
|
|
196
|
+
const MAX_PACKED_LEN = 2 ** 28 - 1;
|
|
197
|
+
function pushPacked(out, len, code) {
|
|
198
|
+
for (let rest = len; rest > 0; rest -= MAX_PACKED_LEN) {
|
|
199
|
+
out.push(((Math.min(rest, MAX_PACKED_LEN) << 4) | code) >>> 0);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function pushRun(out, own, mate) {
|
|
203
|
+
if (own === mate) {
|
|
204
|
+
pushPacked(out, own, CIGAR_M);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const chunks = Math.ceil(Math.max(own, mate) / MAX_PACKED_LEN);
|
|
208
|
+
for (let i = 0; i < chunks; i++) {
|
|
209
|
+
const o = Math.floor((own * (i + 1)) / chunks) - Math.floor((own * i) / chunks);
|
|
210
|
+
const m = Math.floor((mate * (i + 1)) / chunks) - Math.floor((mate * i) / chunks);
|
|
211
|
+
out.push(((o << 4) | CIGAR_RUN) >>> 0, ((m << 4) | CIGAR_RUN) >>> 0);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* #api
|
|
217
|
+
* Parse a coarse CIGAR into the packed `(len << 4) | op` form
|
|
218
|
+
* `visitCigarRenderedSegments` walks. A square run packs as one `CIGAR_M`
|
|
219
|
+
* word; an unequal run as a `CIGAR_RUN` word pair, own axis first.
|
|
220
|
+
*/
|
|
221
|
+
export function parseCoarseCigar(s) {
|
|
222
|
+
const out = [];
|
|
223
|
+
for (const { own, mate, op } of parseCoarseOps(s)) {
|
|
224
|
+
if (op === 'M') {
|
|
225
|
+
pushRun(out, own, mate);
|
|
226
|
+
}
|
|
227
|
+
else if (op === 'I') {
|
|
228
|
+
pushPacked(out, mate, CIGAR_I);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
pushPacked(out, own, op === 'N' ? CIGAR_N : CIGAR_D);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return Uint32Array.from(out);
|
|
235
|
+
}
|
package/esm/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { CIGAR_D, CIGAR_EQ, CIGAR_H, CIGAR_I, CIGAR_INDEL_MASK, CIGAR_M, CIGAR_M_EQ_MASK, CIGAR_N, CIGAR_P, CIGAR_S, CIGAR_X, } from './cigarConstants.ts';
|
|
1
|
+
export { CIGAR_D, CIGAR_EQ, CIGAR_H, CIGAR_I, CIGAR_INDEL_MASK, CIGAR_M, CIGAR_M_EQ_MASK, CIGAR_N, CIGAR_P, CIGAR_RUN, CIGAR_S, CIGAR_X, } from './cigarConstants.ts';
|
|
2
|
+
export { coarseCigarOwnAxis, coarsenCigar, flipCoarseCigar, parseCoarseCigar, swapCoarseCigar, } from './coarseCigar.ts';
|
|
3
|
+
export type { CoarsenedCigar } from './coarseCigar.ts';
|
|
2
4
|
export { CHAR_FROM_CODE, SEQRET, SEQRET_NUMERIC_DECODER, } from './bamSeqDecoder.ts';
|
|
3
5
|
export { encodeSeqNumeric } from './bamSeqEncoder.ts';
|
|
4
6
|
export { flipCigar, swapIndelCigar } from './cigarReorient.ts';
|
package/esm/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { CIGAR_D, CIGAR_EQ, CIGAR_H, CIGAR_I, CIGAR_INDEL_MASK, CIGAR_M, CIGAR_M_EQ_MASK, CIGAR_N, CIGAR_P, CIGAR_S, CIGAR_X, } from './cigarConstants.js';
|
|
1
|
+
export { CIGAR_D, CIGAR_EQ, CIGAR_H, CIGAR_I, CIGAR_INDEL_MASK, CIGAR_M, CIGAR_M_EQ_MASK, CIGAR_N, CIGAR_P, CIGAR_RUN, CIGAR_S, CIGAR_X, } from './cigarConstants.js';
|
|
2
|
+
export { coarseCigarOwnAxis, coarsenCigar, flipCoarseCigar, parseCoarseCigar, swapCoarseCigar, } from './coarseCigar.js';
|
|
2
3
|
export { CHAR_FROM_CODE, SEQRET, SEQRET_NUMERIC_DECODER, } from './bamSeqDecoder.js';
|
|
3
4
|
export { encodeSeqNumeric } from './bamSeqEncoder.js';
|
|
4
5
|
// The BAM mismatch walk and its packed reference come from `@gmod/bam`:
|