@jbrowse/cigar-utils 4.1.14

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 (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +167 -0
  3. package/esm/bamSeqDecoder.d.ts +3 -0
  4. package/esm/bamSeqDecoder.js +10 -0
  5. package/esm/bamSeqEncoder.d.ts +8 -0
  6. package/esm/bamSeqEncoder.js +33 -0
  7. package/esm/buildReadVsRefFeatures.d.ts +57 -0
  8. package/esm/buildReadVsRefFeatures.js +66 -0
  9. package/esm/cigarConstants.d.ts +11 -0
  10. package/esm/cigarConstants.js +15 -0
  11. package/esm/cigarRenderedSegments.d.ts +13 -0
  12. package/esm/cigarRenderedSegments.js +84 -0
  13. package/esm/cigarReorient.d.ts +2 -0
  14. package/esm/cigarReorient.js +29 -0
  15. package/esm/cigarToMismatches2.d.ts +2 -0
  16. package/esm/cigarToMismatches2.js +90 -0
  17. package/esm/cigarWalkStart.d.ts +4 -0
  18. package/esm/cigarWalkStart.js +54 -0
  19. package/esm/csOps.d.ts +47 -0
  20. package/esm/csOps.js +152 -0
  21. package/esm/csToCigar.d.ts +1 -0
  22. package/esm/csToCigar.js +43 -0
  23. package/esm/getNextRefPos.d.ts +31 -0
  24. package/esm/getNextRefPos.js +69 -0
  25. package/esm/index.d.ts +24 -0
  26. package/esm/index.js +24 -0
  27. package/esm/mdToMismatches2.d.ts +2 -0
  28. package/esm/mdToMismatches2.js +108 -0
  29. package/esm/mismatchCallback.d.ts +29 -0
  30. package/esm/mismatchCallback.js +20 -0
  31. package/esm/mismatchParser.d.ts +79 -0
  32. package/esm/mismatchParser.js +238 -0
  33. package/esm/mismatchTypes.d.ts +27 -0
  34. package/esm/mismatchTypes.js +1 -0
  35. package/esm/numericCigarHasSkip.d.ts +1 -0
  36. package/esm/numericCigarHasSkip.js +11 -0
  37. package/esm/numericCigarToString.d.ts +1 -0
  38. package/esm/numericCigarToString.js +13 -0
  39. package/esm/orientAlignment.d.ts +9 -0
  40. package/esm/orientAlignment.js +26 -0
  41. package/esm/pafIdentity.d.ts +20 -0
  42. package/esm/pafIdentity.js +31 -0
  43. package/esm/readEndpoints.d.ts +18 -0
  44. package/esm/readEndpoints.js +69 -0
  45. package/esm/samFlags.d.ts +50 -0
  46. package/esm/samFlags.js +89 -0
  47. package/package.json +42 -0
package/esm/index.js ADDED
@@ -0,0 +1,24 @@
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';
2
+ export { CHAR_FROM_CODE, SEQRET, SEQRET_NUMERIC_DECODER, } from './bamSeqDecoder.js';
3
+ export { encodeSeqNumeric } from './bamSeqEncoder.js';
4
+ // The BAM mismatch walk and its packed reference come from `@gmod/bam`:
5
+ // `forEachMismatchNumeric`, `packReference`, `PackedReference`. One
6
+ // implementation of the format's trickiest walk, in the library that owns the
7
+ // format.
8
+ export { flipCigar, swapIndelCigar } from './cigarReorient.js';
9
+ export { csToCigar } from './csToCigar.js';
10
+ export { flipCs, forEachCsMismatch, forEachCsOp } from './csOps.js';
11
+ export { orientAlignment } from './orientAlignment.js';
12
+ export { pafIdentity } from './pafIdentity.js';
13
+ export { DELETION_TYPE, HARDCLIP_TYPE, INSERTION_TYPE, MISMATCH_TYPE, SKIP_TYPE, SOFTCLIP_TYPE, } from './mismatchCallback.js';
14
+ export { visitCigarRenderedSegments } from './cigarRenderedSegments.js';
15
+ export { cigarWalkBp1, cigarWalkBp2, cigarWalkRev1, cigarWalkRev2, } from './cigarWalkStart.js';
16
+ export { cigarToMismatches2 } from './cigarToMismatches2.js';
17
+ export { mdToMismatches2 } from './mdToMismatches2.js';
18
+ export { getNextRefPos } from './getNextRefPos.js';
19
+ export { numericCigarHasSkip } from './numericCigarHasSkip.js';
20
+ export { numericCigarToString } from './numericCigarToString.js';
21
+ export { connectionEndpointBps, readLeadingBodyDir, readLeadingBp, readTrailingBodyDir, readTrailingBp, } from './readEndpoints.js';
22
+ export { clipLengthAtStartOfReadNumeric, featurizeSA, featurizeSAEntries, getClip, getLength, getLengthOnRef, getLengthSansClipping, getMismatches, parseCigar2, parseCigar2Typed, parseCigar, splitSA, } from './mismatchParser.js';
23
+ export { buildReadVsRefFeatures } from './buildReadVsRefFeatures.js';
24
+ export { SAM_FLAG_DUPLICATE, SAM_FLAG_FAILS_QC, SAM_FLAG_FIRST_IN_PAIR, SAM_FLAG_MATE_REVERSE, SAM_FLAG_MATE_UNMAPPED, SAM_FLAG_PAIRED, SAM_FLAG_PROPER_PAIR, SAM_FLAG_REVERSE, SAM_FLAG_SECONDARY, SAM_FLAG_SECOND_IN_PAIR, SAM_FLAG_SUPPLEMENTARY, SAM_FLAG_UNMAPPED, samFlagDescriptions, samFlagLabels, samFlagNames, } from './samFlags.js';
@@ -0,0 +1,2 @@
1
+ import type { Mismatch } from './mismatchTypes.ts';
2
+ export declare function mdToMismatches2(mdstring: string, ops: ArrayLike<number> | undefined, cigarMismatches: Mismatch[], seq: string, qual?: Uint8Array): Mismatch[];
@@ -0,0 +1,108 @@
1
+ import { CIGAR_D, CIGAR_H, CIGAR_I, CIGAR_N, CIGAR_P, CIGAR_S, } from './cigarConstants.js';
2
+ // Handles packed NUMERIC_CIGAR format from @gmod/bam
3
+ // Format: Uint32Array where each value is (length << 4) | opIndex
4
+ // opIndex is 0-8: M=0, I=1, D=2, N=3, S=4, H=5, P=6, ==7, X=8
5
+ export function mdToMismatches2(mdstring, ops, cigarMismatches, seq, qual) {
6
+ if (!ops) {
7
+ return [];
8
+ }
9
+ const mismatchRecords = [];
10
+ const opsLength = ops.length;
11
+ const seqLength = seq.length;
12
+ const hasQual = qual !== undefined;
13
+ // only check for skips if cigarMismatches has any
14
+ const cigarLength = cigarMismatches.length;
15
+ let hasSkips = false;
16
+ for (let k = 0; k < cigarLength; k++) {
17
+ if (cigarMismatches[k].type === 'skip') {
18
+ hasSkips = true;
19
+ break;
20
+ }
21
+ }
22
+ let currStart = 0;
23
+ let lastCigar = 0;
24
+ let lastTemplateOffset = 0;
25
+ let lastRefOffset = 0;
26
+ let lastSkipPos = 0;
27
+ // parse the MD string manually for better performance
28
+ let i = 0;
29
+ const len = mdstring.length;
30
+ while (i < len) {
31
+ const char = mdstring.charCodeAt(i);
32
+ if (char >= 48 && char <= 57) {
33
+ // digit (0-9)
34
+ let num = 0;
35
+ while (i < len) {
36
+ const c = mdstring.charCodeAt(i);
37
+ if (c >= 48 && c <= 57) {
38
+ num = num * 10 + (c - 48);
39
+ i++;
40
+ }
41
+ else {
42
+ break;
43
+ }
44
+ }
45
+ currStart += num;
46
+ }
47
+ else if (char === 94) {
48
+ // '^' - deletion
49
+ i++;
50
+ while (i < len && mdstring.charCodeAt(i) >= 65) {
51
+ // skip deleted bases (A-Z, a-z)
52
+ i++;
53
+ currStart++;
54
+ }
55
+ }
56
+ else if (char >= 65) {
57
+ // letter (mismatch)
58
+ const letter = mdstring[i];
59
+ i++;
60
+ // handle skips in cigar
61
+ if (hasSkips && cigarLength > 0) {
62
+ for (let k = lastSkipPos; k < cigarLength; k++) {
63
+ const mismatch = cigarMismatches[k];
64
+ if (mismatch.type === 'skip' && currStart >= mismatch.start) {
65
+ currStart += mismatch.length;
66
+ lastSkipPos = k + 1;
67
+ }
68
+ }
69
+ }
70
+ // find position in read that corresponds to currStart
71
+ let templateOffset = lastTemplateOffset;
72
+ let refOffset = lastRefOffset;
73
+ // Packed format: each element is (length << 4) | opIndex
74
+ for (let j = lastCigar; j < opsLength && refOffset <= currStart; j++, lastCigar = j) {
75
+ const packed = ops[j];
76
+ const len = packed >>> 4;
77
+ const op = packed & 0xf;
78
+ if (op === CIGAR_S || op === CIGAR_I) {
79
+ templateOffset += len;
80
+ }
81
+ else if (op === CIGAR_D || op === CIGAR_P || op === CIGAR_N) {
82
+ refOffset += len;
83
+ }
84
+ else if (op !== CIGAR_H) {
85
+ templateOffset += len;
86
+ refOffset += len;
87
+ }
88
+ }
89
+ lastTemplateOffset = templateOffset;
90
+ lastRefOffset = refOffset;
91
+ const s = templateOffset - (refOffset - currStart);
92
+ const base = s < seqLength ? seq[s] : 'X';
93
+ mismatchRecords.push({
94
+ start: currStart,
95
+ base,
96
+ qual: hasQual ? qual[s] : undefined,
97
+ altbase: letter,
98
+ length: 1,
99
+ type: 'mismatch',
100
+ });
101
+ currStart++;
102
+ }
103
+ else {
104
+ i++;
105
+ }
106
+ }
107
+ return mismatchRecords;
108
+ }
@@ -0,0 +1,29 @@
1
+ export declare const MISMATCH_TYPE = 88;
2
+ export declare const INSERTION_TYPE = 73;
3
+ export declare const DELETION_TYPE = 68;
4
+ export declare const SKIP_TYPE = 78;
5
+ export declare const SOFTCLIP_TYPE = 83;
6
+ export declare const HARDCLIP_TYPE = 72;
7
+ /**
8
+ * Identical to `@gmod/bam`'s and `@gmod/cram`'s `MismatchCallback`, so a
9
+ * consumer's callback goes straight to either walk with nothing in between.
10
+ *
11
+ * The last three are numbers rather than `number | undefined`: an absent
12
+ * quality is -1, and an absent reference base or clip length is 0. That is
13
+ * what both libraries emit, and it saves every consumer a non-null assertion.
14
+ */
15
+ export type MismatchCallback = (type: number, start: number, length: number, base: string, qual: number, altbase: number, cliplen: number) => void;
16
+ /**
17
+ * The genomic viewport a `forEachMismatch` walk reports within: 0-based
18
+ * half-open, and absolute even where the walk reports read-relative positions,
19
+ * since it describes a region of the reference rather than a position in the
20
+ * output.
21
+ *
22
+ * The narrow half of `@gmod/bam`'s and `@gmod/cram`'s `MismatchOptions` — the
23
+ * part every alignment feature can honour, without this package having to know
24
+ * about a packed reference or an origin.
25
+ */
26
+ export interface MismatchWindow {
27
+ start?: number;
28
+ end?: number;
29
+ }
@@ -0,0 +1,20 @@
1
+ // Shared types for callback-based mismatch iteration
2
+ // Each adapter implements forEachMismatch using its optimal internal structure
3
+ //
4
+ // The values are the ASCII codes of the CIGAR/CRAM letters they name, which is
5
+ // what lets `@gmod/cram`'s own forEachMismatch emit straight into one of these
6
+ // callbacks: it reports a difference by its CRAM feature code, so a CRAM
7
+ // adapter that agrees on the numbering needs no translating callback in
8
+ // between, and that indirect call was 17% of the walk. See @gmod/cram ADR 0008
9
+ // and CramSlightlyLazyFeature.forEachMismatch. `cramCodes.test.ts` pins the
10
+ // agreement.
11
+ //
12
+ // Nothing depends on them being 0..5 — every consumer compares symbolically
13
+ // and none of them is serialized — but do not renumber them without checking
14
+ // that again.
15
+ export const MISMATCH_TYPE = 0x58; // 'X'
16
+ export const INSERTION_TYPE = 0x49; // 'I'
17
+ export const DELETION_TYPE = 0x44; // 'D'
18
+ export const SKIP_TYPE = 0x4e; // 'N'
19
+ export const SOFTCLIP_TYPE = 0x53; // 'S'
20
+ export const HARDCLIP_TYPE = 0x48; // 'H'
@@ -0,0 +1,79 @@
1
+ /**
2
+ * #api
3
+ * Parses a CIGAR string to an alternating `[length, op, ...]` string array.
4
+ */
5
+ export declare function parseCigar(s?: string): string[];
6
+ /**
7
+ * #api
8
+ * Parses a CIGAR string to a packed number array where each value is
9
+ * `(length << 4) | opIndex`.
10
+ */
11
+ export declare function parseCigar2(s?: string): number[];
12
+ /**
13
+ * #api
14
+ * Same encoding as `parseCigar2` but writes into a packed `Uint32Array` —
15
+ * matches the NUMERIC_CIGAR format that BAM/CRAM adapters emit, so consumers can
16
+ * use a single typed-array code path.
17
+ */
18
+ export declare function parseCigar2Typed(s?: string): Uint32Array<ArrayBuffer>;
19
+ /**
20
+ * #api
21
+ * Computes the list of mismatches (SNVs, indels, clips, skips) for a read from
22
+ * its CIGAR, optional MD tag, sequence, reference, and quality.
23
+ */
24
+ export declare function getMismatches(cigar?: string, md?: string, seq?: string, ref?: string, qual?: Uint8Array): import("./mismatchTypes.ts").Mismatch[];
25
+ /**
26
+ * #api
27
+ * Length the read spans on the reference (sum of M/=/X/D/N ops).
28
+ */
29
+ export declare function getLengthOnRef(cigar: string): number;
30
+ /**
31
+ * #api
32
+ * Length of the read sequence (sum of all ops except D/N).
33
+ */
34
+ export declare function getLength(cigar: string): number;
35
+ export declare function getLengthSansClipping(cigar: string): number;
36
+ export declare function getClip(cigar: string, strand: number): number;
37
+ export declare function clipLengthAtStartOfReadNumeric(cigar: ArrayLike<number>, strand: number): number;
38
+ export declare function featurizeSA(SA: string | undefined, id: string, strand: number | undefined, readName: string | undefined, normalize?: boolean): {
39
+ refName: string;
40
+ start: number;
41
+ end: number;
42
+ seqLength: number;
43
+ clipLengthAtStartOfRead: number;
44
+ CIGAR: string;
45
+ strand: -1 | 1;
46
+ uniqueId: string;
47
+ mate: {
48
+ start: number;
49
+ end: number;
50
+ refName: string | undefined;
51
+ };
52
+ }[];
53
+ /**
54
+ * #api
55
+ * The `;`-separated alignment records of an SA tag, empties dropped — the input
56
+ * `featurizeSAEntries` expects.
57
+ */
58
+ export declare function splitSA(SA: string): string[];
59
+ /**
60
+ * #api
61
+ * featurizeSA over pre-split entries (see splitSA). Lets a caller filter the
62
+ * entries first — e.g. deduplicating the records repeated across a split read's
63
+ * segments — without paying to split and rejoin the tag around the filter.
64
+ */
65
+ export declare function featurizeSAEntries(entries: string[], id: string, strand: number | undefined, readName: string | undefined, normalize?: boolean): {
66
+ refName: string;
67
+ start: number;
68
+ end: number;
69
+ seqLength: number;
70
+ clipLengthAtStartOfRead: number;
71
+ CIGAR: string;
72
+ strand: -1 | 1;
73
+ uniqueId: string;
74
+ mate: {
75
+ start: number;
76
+ end: number;
77
+ refName: string | undefined;
78
+ };
79
+ }[];
@@ -0,0 +1,238 @@
1
+ import { CIGAR_D, CIGAR_H, CIGAR_I, CIGAR_N, CIGAR_S, } from './cigarConstants.js';
2
+ import { cigarToMismatches2 } from './cigarToMismatches2.js';
3
+ import { mdToMismatches2 } from './mdToMismatches2.js';
4
+ // CIGAR operation char codes to indices (from BAM spec)
5
+ const CIGAR_CODE_TO_INDEX = {
6
+ 77: 0, // M
7
+ 73: 1, // I
8
+ 68: 2, // D
9
+ 78: 3, // N
10
+ 83: 4, // S
11
+ 72: 5, // H
12
+ 80: 6, // P
13
+ 61: 7, // =
14
+ 88: 8, // X
15
+ };
16
+ /**
17
+ * #api
18
+ * Parses a CIGAR string to an alternating `[length, op, ...]` string array.
19
+ */
20
+ export function parseCigar(s = '') {
21
+ let currLen = '';
22
+ const ret = [];
23
+ for (let i = 0, l = s.length; i < l; i++) {
24
+ const c = s[i];
25
+ if (c >= '0' && c <= '9') {
26
+ currLen = currLen + c;
27
+ }
28
+ else {
29
+ ret.push(currLen, c);
30
+ currLen = '';
31
+ }
32
+ }
33
+ return ret;
34
+ }
35
+ /**
36
+ * #api
37
+ * Parses a CIGAR string to a packed number array where each value is
38
+ * `(length << 4) | opIndex`.
39
+ */
40
+ export function parseCigar2(s = '') {
41
+ let currLen = 0;
42
+ const ret = [];
43
+ for (let i = 0, l = s.length; i < l; i++) {
44
+ const code = s.charCodeAt(i);
45
+ if (code >= 48 && code <= 57) {
46
+ currLen = currLen * 10 + (code - 48);
47
+ }
48
+ else {
49
+ const opIndex = CIGAR_CODE_TO_INDEX[code];
50
+ ret.push((currLen << 4) | opIndex);
51
+ currLen = 0;
52
+ }
53
+ }
54
+ return ret;
55
+ }
56
+ /**
57
+ * #api
58
+ * Same encoding as `parseCigar2` but writes into a packed `Uint32Array` —
59
+ * matches the NUMERIC_CIGAR format that BAM/CRAM adapters emit, so consumers can
60
+ * use a single typed-array code path.
61
+ */
62
+ export function parseCigar2Typed(s = '') {
63
+ let opCount = 0;
64
+ for (let i = 0, l = s.length; i < l; i++) {
65
+ const code = s.charCodeAt(i);
66
+ if (code < 48 || code > 57) {
67
+ opCount++;
68
+ }
69
+ }
70
+ const out = new Uint32Array(opCount);
71
+ let currLen = 0;
72
+ let j = 0;
73
+ for (let i = 0, l = s.length; i < l; i++) {
74
+ const code = s.charCodeAt(i);
75
+ if (code >= 48 && code <= 57) {
76
+ currLen = currLen * 10 + (code - 48);
77
+ }
78
+ else {
79
+ out[j++] = (currLen << 4) | CIGAR_CODE_TO_INDEX[code];
80
+ currLen = 0;
81
+ }
82
+ }
83
+ return out;
84
+ }
85
+ /**
86
+ * #api
87
+ * Computes the list of mismatches (SNVs, indels, clips, skips) for a read from
88
+ * its CIGAR, optional MD tag, sequence, reference, and quality.
89
+ */
90
+ export function getMismatches(cigar, md, seq, ref, qual) {
91
+ const ops = parseCigar2(cigar);
92
+ const mismatches = cigar ? cigarToMismatches2(ops, seq, ref, qual) : [];
93
+ if (md && seq) {
94
+ return mismatches.concat(mdToMismatches2(md, ops, mismatches, seq, qual));
95
+ }
96
+ return mismatches;
97
+ }
98
+ // All three lengths in one pass over already-parsed ops: callers that want more
99
+ // than one (featurizeSA wants all three) would otherwise re-scan and re-allocate
100
+ // the op array per length.
101
+ function cigarLengths(ops) {
102
+ let lengthOnRef = 0;
103
+ let length = 0;
104
+ let lengthSansClipping = 0;
105
+ for (let i = 0, l = ops.length; i < l; i++) {
106
+ const op = ops[i];
107
+ const opIdx = op & 0xf;
108
+ const len = op >>> 4;
109
+ const clipping = opIdx === CIGAR_H || opIdx === CIGAR_S;
110
+ const refOnly = opIdx === CIGAR_D || opIdx === CIGAR_N;
111
+ if (!clipping && opIdx !== CIGAR_I) {
112
+ lengthOnRef += len;
113
+ }
114
+ if (!refOnly) {
115
+ length += len;
116
+ }
117
+ if (!clipping && !refOnly) {
118
+ lengthSansClipping += len;
119
+ }
120
+ }
121
+ return { lengthOnRef, length, lengthSansClipping };
122
+ }
123
+ /**
124
+ * #api
125
+ * Length the read spans on the reference (sum of M/=/X/D/N ops).
126
+ */
127
+ export function getLengthOnRef(cigar) {
128
+ return cigarLengths(parseCigar2(cigar)).lengthOnRef;
129
+ }
130
+ /**
131
+ * #api
132
+ * Length of the read sequence (sum of all ops except D/N).
133
+ */
134
+ export function getLength(cigar) {
135
+ return cigarLengths(parseCigar2(cigar)).length;
136
+ }
137
+ export function getLengthSansClipping(cigar) {
138
+ return cigarLengths(parseCigar2(cigar)).lengthSansClipping;
139
+ }
140
+ // clip at the end of the CIGAR string = start of a reverse-strand read,
141
+ // clip at the start of the CIGAR string = start of a forward-strand read
142
+ export function getClip(cigar, strand) {
143
+ if (!cigar) {
144
+ return 0;
145
+ }
146
+ if (strand === -1) {
147
+ const last = cigar.length - 1;
148
+ const op = cigar[last];
149
+ if (op === 'S' || op === 'H') {
150
+ let i = last;
151
+ while (i > 0 && cigar[i - 1] >= '0' && cigar[i - 1] <= '9') {
152
+ i--;
153
+ }
154
+ return +cigar.slice(i, last);
155
+ }
156
+ return 0;
157
+ }
158
+ else {
159
+ let i = 0;
160
+ while (cigar[i] >= '0' && cigar[i] <= '9') {
161
+ i++;
162
+ }
163
+ const op = cigar[i];
164
+ return (op === 'S' || op === 'H') && i > 0 ? +cigar.slice(0, i) : 0;
165
+ }
166
+ }
167
+ // numeric analog of getClip: reads the start-clip length straight off a packed
168
+ // NUMERIC_CIGAR (each entry = (length << 4) | opIndex) without serializing to a
169
+ // CIGAR string. Reverse-strand reads clip at the CIGAR tail, forward at the head.
170
+ export function clipLengthAtStartOfReadNumeric(cigar, strand) {
171
+ const len = cigar.length;
172
+ if (len === 0) {
173
+ return 0;
174
+ }
175
+ const packed = strand === -1 ? cigar[len - 1] : cigar[0];
176
+ const op = packed & 0xf;
177
+ return op === CIGAR_S || op === CIGAR_H ? packed >>> 4 : 0;
178
+ }
179
+ // produces a list of "feature-like" object from parsing supplementary
180
+ // alignments in the SA tag
181
+ //
182
+ // @param normalize - used specifically in the linear-read-vs-ref context, it
183
+ // flips features around relative to the original feature. other contexts of
184
+ // usage can keep this false
185
+ export function featurizeSA(SA, id, strand, readName, normalize) {
186
+ return SA === undefined
187
+ ? []
188
+ : featurizeSAEntries(splitSA(SA), id, strand, readName, normalize);
189
+ }
190
+ /**
191
+ * #api
192
+ * The `;`-separated alignment records of an SA tag, empties dropped — the input
193
+ * `featurizeSAEntries` expects.
194
+ */
195
+ export function splitSA(SA) {
196
+ return SA.split(';').filter(aln => !!aln);
197
+ }
198
+ /**
199
+ * #api
200
+ * featurizeSA over pre-split entries (see splitSA). Lets a caller filter the
201
+ * entries first — e.g. deduplicating the records repeated across a split read's
202
+ * segments — without paying to split and rejoin the tag around the filter.
203
+ */
204
+ export function featurizeSAEntries(entries, id, strand, readName, normalize) {
205
+ const strandNum = strand ?? 1;
206
+ return entries.map((aln, index) => {
207
+ const ret = aln.split(',');
208
+ const saRef = ret[0];
209
+ const saStart = ret[1];
210
+ const saStrand = ret[2];
211
+ const saCigar = ret[3];
212
+ const saOps = parseCigar2(saCigar);
213
+ const { lengthOnRef: saLengthOnRef, length: saLength, lengthSansClipping: saLengthSansClipping, } = cigarLengths(saOps);
214
+ const saStrandNormalized = saStrand === '-' ? -1 : 1;
215
+ const effectiveStrand = normalize
216
+ ? strandNum === saStrandNormalized
217
+ ? 1
218
+ : -1
219
+ : saStrandNormalized;
220
+ const saClipPos = clipLengthAtStartOfReadNumeric(saOps, effectiveStrand);
221
+ const saRealStart = +saStart - 1;
222
+ return {
223
+ refName: saRef,
224
+ start: saRealStart,
225
+ end: saRealStart + saLengthOnRef,
226
+ seqLength: saLength,
227
+ clipLengthAtStartOfRead: saClipPos,
228
+ CIGAR: saCigar,
229
+ strand: effectiveStrand,
230
+ uniqueId: `${id}_SA${index}`,
231
+ mate: {
232
+ start: saClipPos,
233
+ end: saClipPos + saLengthSansClipping,
234
+ refName: readName,
235
+ },
236
+ };
237
+ });
238
+ }
@@ -0,0 +1,27 @@
1
+ interface BaseMismatch {
2
+ start: number;
3
+ length: number;
4
+ }
5
+ export interface SNPMismatch extends BaseMismatch {
6
+ type: 'mismatch';
7
+ base: string;
8
+ qual?: number;
9
+ altbase?: string;
10
+ }
11
+ export interface InsertionMismatch extends BaseMismatch {
12
+ type: 'insertion';
13
+ insertlen: number;
14
+ insertedBases?: string;
15
+ }
16
+ export interface DeletionMismatch extends BaseMismatch {
17
+ type: 'deletion';
18
+ }
19
+ export interface SkipMismatch extends BaseMismatch {
20
+ type: 'skip';
21
+ }
22
+ export interface ClipMismatch extends BaseMismatch {
23
+ type: 'softclip' | 'hardclip';
24
+ cliplen: number;
25
+ }
26
+ export type Mismatch = SNPMismatch | InsertionMismatch | DeletionMismatch | SkipMismatch | ClipMismatch;
27
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function numericCigarHasSkip(cigar: ArrayLike<number>): boolean;
@@ -0,0 +1,11 @@
1
+ import { CIGAR_N } from './cigarConstants.js';
2
+ // Whether a packed CIGAR (`len << 4 | op`) carries a reference skip — the `N`
3
+ // a spliced aligner writes across an intron.
4
+ export function numericCigarHasSkip(cigar) {
5
+ for (let i = 0; i < cigar.length; i++) {
6
+ if ((cigar[i] & 0xf) === CIGAR_N) {
7
+ return true;
8
+ }
9
+ }
10
+ return false;
11
+ }
@@ -0,0 +1 @@
1
+ export declare function numericCigarToString(cigar: ArrayLike<number>): string;
@@ -0,0 +1,13 @@
1
+ // Packed CIGAR op index (packed & 0xf) to op char, indices per BAM spec:
2
+ // M=0 I=1 D=2 N=3 S=4 H=5 P=6 ==7 X=8
3
+ const CIGAR_CHARS = 'MIDNSHP=X';
4
+ // serialize a packed NUMERIC_CIGAR (each entry = (length << 4) | opIndex) back
5
+ // to a CIGAR string; inverse of the numeric packing produced across adapters
6
+ export function numericCigarToString(cigar) {
7
+ let result = '';
8
+ for (let i = 0, l = cigar.length; i < l; i++) {
9
+ const packed = cigar[i];
10
+ result += (packed >>> 4) + CIGAR_CHARS[packed & 0xf];
11
+ }
12
+ return result;
13
+ }
@@ -0,0 +1,9 @@
1
+ export declare function orientAlignment({ cg, cs, flip, strand, }: {
2
+ cg: string | undefined;
3
+ cs: string | undefined;
4
+ flip: boolean;
5
+ strand: number;
6
+ }): {
7
+ CIGAR: string | undefined;
8
+ cs: string | undefined;
9
+ };
@@ -0,0 +1,26 @@
1
+ import { flipCigar, swapIndelCigar } from './cigarReorient.js';
2
+ import { flipCs } from './csOps.js';
3
+ import { csToCigar } from './csToCigar.js';
4
+ // Resolve a PAF row's alignment detail into the perspective the view is anchored
5
+ // on. `flip` is set when viewing the query assembly. A cg CIGAR is used when
6
+ // present, otherwise one is derived from cs. cs (real per-base diffs) maps
7
+ // directly in the target perspective; in the query perspective it is flipped,
8
+ // except reverse-strand which additionally needs a reverse-complement we don't
9
+ // do — there cs is dropped and the flipped CIGAR carries mismatch positions.
10
+ // flipCs drops it too when the row states an intron, which is a reference gap
11
+ // that the query perspective has no way to write.
12
+ export function orientAlignment({ cg, cs, flip, strand, }) {
13
+ let CIGAR = cg ?? (cs ? csToCigar(cs) : undefined);
14
+ let orientedCs = cs;
15
+ if (CIGAR && flip) {
16
+ if (strand === -1) {
17
+ CIGAR = flipCigar(CIGAR);
18
+ orientedCs = undefined;
19
+ }
20
+ else {
21
+ CIGAR = swapIndelCigar(CIGAR);
22
+ orientedCs = cs ? flipCs(cs) : undefined;
23
+ }
24
+ }
25
+ return { CIGAR, cs: orientedCs };
26
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Identity in [0,1] for one PAF row, from whichever of its tags/columns carries
3
+ * it. Prefers the `de:f:` tag (minimap2 gap-compressed divergence) since it is
4
+ * computed from the actual CIGAR — the same quantity rustybam's `rb stats --paf`
5
+ * writes and SVbyEye computes per-bin. Falls back to odgi untangle's `id:f:` tag
6
+ * (a percentage or a fraction), then to residue matches over block length.
7
+ *
8
+ * Lives here, in a package with no framework deps, because two independent
9
+ * pieces of code must agree on it to the digit: the synteny adapters read it at
10
+ * render time, and `jbrowse make-pif` writes the coarse LOD tier's `de:f:` as
11
+ * `1 - pafIdentity(row)`. If the two chains diverge on any rung, identity
12
+ * coloring jumps at the zoom where the view switches tiers — which is exactly
13
+ * what happened when make-pif's copy skipped the `id:f:` rung.
14
+ */
15
+ export declare function pafIdentity(row: {
16
+ de?: string | number;
17
+ id?: string | number;
18
+ numMatches?: string | number;
19
+ blockLen?: string | number;
20
+ }): number;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Identity in [0,1] for one PAF row, from whichever of its tags/columns carries
3
+ * it. Prefers the `de:f:` tag (minimap2 gap-compressed divergence) since it is
4
+ * computed from the actual CIGAR — the same quantity rustybam's `rb stats --paf`
5
+ * writes and SVbyEye computes per-bin. Falls back to odgi untangle's `id:f:` tag
6
+ * (a percentage or a fraction), then to residue matches over block length.
7
+ *
8
+ * Lives here, in a package with no framework deps, because two independent
9
+ * pieces of code must agree on it to the digit: the synteny adapters read it at
10
+ * render time, and `jbrowse make-pif` writes the coarse LOD tier's `de:f:` as
11
+ * `1 - pafIdentity(row)`. If the two chains diverge on any rung, identity
12
+ * coloring jumps at the zoom where the view switches tiers — which is exactly
13
+ * what happened when make-pif's copy skipped the `id:f:` rung.
14
+ */
15
+ export function pafIdentity(row) {
16
+ if (row.de !== undefined) {
17
+ const d = +row.de;
18
+ if (Number.isFinite(d) && d >= 0 && d <= 1) {
19
+ return 1 - d;
20
+ }
21
+ }
22
+ if (row.id !== undefined) {
23
+ const v = +row.id;
24
+ if (Number.isFinite(v)) {
25
+ return v > 1 ? v / 100 : v;
26
+ }
27
+ }
28
+ const matches = +(row.numMatches ?? 0);
29
+ const blockLen = +(row.blockLen ?? 0);
30
+ return blockLen > 0 ? matches / blockLen : 0;
31
+ }