@gmod/bam 2.0.4 → 3.0.1
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/CHANGELOG.md +10 -5
- package/README.md +10 -42
- package/dist/bai.js +7 -2
- package/dist/bai.js.map +1 -1
- package/dist/bamFile.js +14 -12
- package/dist/bamFile.js.map +1 -1
- package/dist/csi.d.ts +1 -1
- package/dist/csi.js +4 -2
- package/dist/csi.js.map +1 -1
- package/dist/htsget.js +1 -1
- package/dist/record.d.ts +43 -54
- package/dist/record.js +121 -206
- package/dist/record.js.map +1 -1
- package/dist/util.d.ts +0 -2
- package/dist/util.js +3 -10
- package/dist/util.js.map +1 -1
- package/esm/bai.js +7 -2
- package/esm/bai.js.map +1 -1
- package/esm/bamFile.js +13 -11
- package/esm/bamFile.js.map +1 -1
- package/esm/csi.d.ts +1 -1
- package/esm/csi.js +4 -2
- package/esm/csi.js.map +1 -1
- package/esm/record.d.ts +43 -54
- package/esm/record.js +121 -206
- package/esm/record.js.map +1 -1
- package/esm/util.d.ts +0 -2
- package/esm/util.js +3 -9
- package/esm/util.js.map +1 -1
- package/package.json +14 -15
- package/src/bai.ts +9 -3
- package/src/bamFile.ts +13 -11
- package/src/csi.ts +5 -3
- package/src/record.ts +138 -241
- package/src/util.ts +4 -15
package/src/bai.ts
CHANGED
|
@@ -22,7 +22,7 @@ function reg2bins(beg: number, end: number) {
|
|
|
22
22
|
[73 + (beg >> 20), 73 + (end >> 20)],
|
|
23
23
|
[585 + (beg >> 17), 585 + (end >> 17)],
|
|
24
24
|
[4681 + (beg >> 14), 4681 + (end >> 14)],
|
|
25
|
-
]
|
|
25
|
+
] as const
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export default class BAI extends IndexFile {
|
|
@@ -125,6 +125,7 @@ export default class BAI extends IndexFile {
|
|
|
125
125
|
const range = start !== undefined
|
|
126
126
|
const indexData = await this.parse(opts)
|
|
127
127
|
const seqIdx = indexData.indices[seqId]
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
128
129
|
if (!seqIdx) {
|
|
129
130
|
return []
|
|
130
131
|
}
|
|
@@ -167,10 +168,12 @@ export default class BAI extends IndexFile {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
const indexData = await this.parse(opts)
|
|
171
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
170
172
|
if (!indexData) {
|
|
171
173
|
return []
|
|
172
174
|
}
|
|
173
175
|
const ba = indexData.indices[refId]
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
174
177
|
if (!ba) {
|
|
175
178
|
return []
|
|
176
179
|
}
|
|
@@ -182,10 +185,11 @@ export default class BAI extends IndexFile {
|
|
|
182
185
|
// Find chunks in overlapping bins. Leaf bins (< 4681) are not pruned
|
|
183
186
|
for (const [start, end] of overlappingBins) {
|
|
184
187
|
for (let bin = start; bin <= end; bin++) {
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
185
189
|
if (ba.binIndex[bin]) {
|
|
186
190
|
const binChunks = ba.binIndex[bin]
|
|
187
191
|
for (const binChunk of binChunks) {
|
|
188
|
-
chunks.push(binChunk)
|
|
192
|
+
chunks.push(new Chunk(binChunk.minv, binChunk.maxv, bin))
|
|
189
193
|
}
|
|
190
194
|
}
|
|
191
195
|
}
|
|
@@ -199,6 +203,8 @@ export default class BAI extends IndexFile {
|
|
|
199
203
|
const maxLin = Math.min(max >> 14, nintv - 1)
|
|
200
204
|
for (let i = minLin; i <= maxLin; ++i) {
|
|
201
205
|
const vp = ba.linearIndex[i]
|
|
206
|
+
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
202
208
|
if (vp && (!lowest || vp.compareTo(lowest) < 0)) {
|
|
203
209
|
lowest = vp
|
|
204
210
|
}
|
|
@@ -209,7 +215,7 @@ export default class BAI extends IndexFile {
|
|
|
209
215
|
|
|
210
216
|
async parse(opts: BaseOpts = {}) {
|
|
211
217
|
if (!this.setupP) {
|
|
212
|
-
this.setupP = this._parse(opts).catch(e => {
|
|
218
|
+
this.setupP = this._parse(opts).catch((e: unknown) => {
|
|
213
219
|
this.setupP = undefined
|
|
214
220
|
throw e
|
|
215
221
|
})
|
package/src/bamFile.ts
CHANGED
|
@@ -178,7 +178,7 @@ export default class BamFile {
|
|
|
178
178
|
|
|
179
179
|
getHeader(opts?: BaseOpts) {
|
|
180
180
|
if (!this.headerP) {
|
|
181
|
-
this.headerP = this.getHeaderPre(opts).catch(e => {
|
|
181
|
+
this.headerP = this.getHeaderPre(opts).catch((e: unknown) => {
|
|
182
182
|
this.headerP = undefined
|
|
183
183
|
throw e
|
|
184
184
|
})
|
|
@@ -288,12 +288,12 @@ export default class BamFile {
|
|
|
288
288
|
|
|
289
289
|
const recs = [] as BAMFeature[]
|
|
290
290
|
for (const feature of records) {
|
|
291
|
-
if (feature.
|
|
292
|
-
if (feature.
|
|
291
|
+
if (feature.ref_id === chrId) {
|
|
292
|
+
if (feature.start >= max) {
|
|
293
293
|
// past end of range, can stop iterating
|
|
294
294
|
done = true
|
|
295
295
|
break
|
|
296
|
-
} else if (feature.
|
|
296
|
+
} else if (feature.end >= min) {
|
|
297
297
|
// must be in range
|
|
298
298
|
recs.push(feature)
|
|
299
299
|
}
|
|
@@ -319,8 +319,8 @@ export default class BamFile {
|
|
|
319
319
|
feats.map(ret => {
|
|
320
320
|
const readNames: Record<string, number> = {}
|
|
321
321
|
for (const element of ret) {
|
|
322
|
-
const name = element.name
|
|
323
|
-
const id = element.id
|
|
322
|
+
const name = element.name
|
|
323
|
+
const id = element.id
|
|
324
324
|
if (!readNames[name]) {
|
|
325
325
|
readNames[name] = 0
|
|
326
326
|
}
|
|
@@ -337,10 +337,10 @@ export default class BamFile {
|
|
|
337
337
|
const matePromises: Promise<Chunk[]>[] = []
|
|
338
338
|
feats.map(ret => {
|
|
339
339
|
for (const f of ret) {
|
|
340
|
-
const name = f.name
|
|
341
|
-
const start = f.
|
|
342
|
-
const pnext = f.
|
|
343
|
-
const rnext = f.
|
|
340
|
+
const name = f.name
|
|
341
|
+
const start = f.start
|
|
342
|
+
const pnext = f.next_pos
|
|
343
|
+
const rnext = f.next_refid
|
|
344
344
|
if (
|
|
345
345
|
this.index &&
|
|
346
346
|
unmatedPairs[name] &&
|
|
@@ -377,7 +377,7 @@ export default class BamFile {
|
|
|
377
377
|
dpositions,
|
|
378
378
|
chunk,
|
|
379
379
|
)) {
|
|
380
|
-
if (unmatedPairs[feature.
|
|
380
|
+
if (unmatedPairs[feature.name] && !readIds[feature.id]) {
|
|
381
381
|
mateRecs.push(feature)
|
|
382
382
|
}
|
|
383
383
|
}
|
|
@@ -430,6 +430,7 @@ export default class BamFile {
|
|
|
430
430
|
const blockEnd = blockStart + 4 + blockSize - 1
|
|
431
431
|
|
|
432
432
|
// increment position to the current decompressed status
|
|
433
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
433
434
|
if (dpositions) {
|
|
434
435
|
while (blockStart + chunk.minv.dataPosition >= dpositions[pos++]) {}
|
|
435
436
|
pos--
|
|
@@ -470,6 +471,7 @@ export default class BamFile {
|
|
|
470
471
|
chunk.minv.dataPosition +
|
|
471
472
|
1
|
|
472
473
|
: // must be slice, not subarray for buffer polyfill on web
|
|
474
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
473
475
|
crc32.signed(ba.slice(blockStart, blockEnd)),
|
|
474
476
|
})
|
|
475
477
|
|
package/src/csi.ts
CHANGED
|
@@ -159,7 +159,8 @@ export default class CSI extends IndexFile {
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
const indexData = await this.parse(opts)
|
|
162
|
-
const ba = indexData
|
|
162
|
+
const ba = indexData.indices[refId]
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
163
164
|
if (!ba) {
|
|
164
165
|
return []
|
|
165
166
|
}
|
|
@@ -173,6 +174,7 @@ export default class CSI extends IndexFile {
|
|
|
173
174
|
// Find chunks in overlapping bins. Leaf bins (< 4681) are not pruned
|
|
174
175
|
for (const [start, end] of overlappingBins) {
|
|
175
176
|
for (let bin = start; bin <= end; bin++) {
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
176
178
|
if (ba.binIndex[bin]) {
|
|
177
179
|
const binChunks = ba.binIndex[bin]
|
|
178
180
|
for (const c of binChunks) {
|
|
@@ -210,14 +212,14 @@ export default class CSI extends IndexFile {
|
|
|
210
212
|
`query ${beg}-${end} is too large for current binning scheme (shift ${this.minShift}, depth ${this.depth}), try a smaller query or a coarser index binning scheme`,
|
|
211
213
|
)
|
|
212
214
|
}
|
|
213
|
-
bins.push([b, e])
|
|
215
|
+
bins.push([b, e] as const)
|
|
214
216
|
}
|
|
215
217
|
return bins
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
async parse(opts: BaseOpts = {}) {
|
|
219
221
|
if (!this.setupP) {
|
|
220
|
-
this.setupP = this._parse(opts).catch(e => {
|
|
222
|
+
this.setupP = this._parse(opts).catch((e: unknown) => {
|
|
221
223
|
this.setupP = undefined
|
|
222
224
|
throw e
|
|
223
225
|
})
|
package/src/record.ts
CHANGED
|
@@ -1,202 +1,102 @@
|
|
|
1
1
|
import Constants from './constants'
|
|
2
|
+
import type { Buffer } from 'buffer'
|
|
2
3
|
|
|
3
4
|
const SEQRET_DECODER = '=ACMGRSVTWYHKDBN'.split('')
|
|
4
5
|
const CIGAR_DECODER = 'MIDNSHP=X???????'.split('')
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
interface Bytes {
|
|
8
|
+
start: number
|
|
9
|
+
end: number
|
|
10
|
+
byteArray: Buffer
|
|
11
|
+
}
|
|
9
12
|
export default class BamRecord {
|
|
10
|
-
|
|
11
|
-
private bytes:
|
|
12
|
-
private _id: number
|
|
13
|
-
private _tagOffset: number | undefined
|
|
14
|
-
private _tagList: string[] = []
|
|
15
|
-
private _allTagsParsed = false
|
|
16
|
-
|
|
17
|
-
public flags: any
|
|
18
|
-
public _refID: number
|
|
19
|
-
constructor(args: any) {
|
|
20
|
-
const { bytes, fileOffset } = args
|
|
21
|
-
const { byteArray, start } = bytes
|
|
22
|
-
this.data = { start: byteArray.readInt32LE(start + 8) }
|
|
23
|
-
this.bytes = bytes
|
|
24
|
-
this._id = fileOffset
|
|
25
|
-
this._refID = byteArray.readInt32LE(start + 4)
|
|
26
|
-
this.flags = (byteArray.readInt32LE(start + 16) & 0xffff0000) >> 16
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
get(field: string) {
|
|
30
|
-
//@ts-ignore
|
|
31
|
-
if (this[field]) {
|
|
32
|
-
//@ts-ignore
|
|
33
|
-
if (this.data[field]) {
|
|
34
|
-
return this.data[field]
|
|
35
|
-
}
|
|
36
|
-
//@ts-ignore
|
|
37
|
-
this.data[field] = this[field]()
|
|
38
|
-
return this.data[field]
|
|
39
|
-
}
|
|
40
|
-
return this._get(field.toLowerCase())
|
|
41
|
-
}
|
|
13
|
+
public fileOffset: number
|
|
14
|
+
private bytes: Bytes
|
|
42
15
|
|
|
43
|
-
|
|
44
|
-
|
|
16
|
+
constructor(args: { bytes: Bytes; fileOffset: number }) {
|
|
17
|
+
this.bytes = args.bytes
|
|
18
|
+
this.fileOffset = args.fileOffset
|
|
45
19
|
}
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
return this.
|
|
21
|
+
get byteArray() {
|
|
22
|
+
return this.bytes.byteArray
|
|
49
23
|
}
|
|
50
24
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.
|
|
58
|
-
return this.data[field]
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
_tags() {
|
|
62
|
-
this._parseAllTags()
|
|
63
|
-
|
|
64
|
-
let tags = ['seq']
|
|
65
|
-
|
|
66
|
-
if (!this.isSegmentUnmapped()) {
|
|
67
|
-
tags.push(
|
|
68
|
-
'start',
|
|
69
|
-
'end',
|
|
70
|
-
'strand',
|
|
71
|
-
'score',
|
|
72
|
-
'qual',
|
|
73
|
-
'MQ',
|
|
74
|
-
'CIGAR',
|
|
75
|
-
'length_on_ref',
|
|
76
|
-
'template_length',
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
if (this.isPaired()) {
|
|
80
|
-
tags.push('next_segment_position', 'pair_orientation')
|
|
81
|
-
}
|
|
82
|
-
tags = tags.concat(this._tagList || [])
|
|
83
|
-
|
|
84
|
-
for (const k of Object.keys(this.data)) {
|
|
85
|
-
if (!k.startsWith('_') && k !== 'next_seq_id') {
|
|
86
|
-
tags.push(k)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const seen: Record<string, boolean> = {}
|
|
91
|
-
return tags.filter(t => {
|
|
92
|
-
if (
|
|
93
|
-
(t in this.data && this.data[t] === undefined) ||
|
|
94
|
-
t === 'CG' ||
|
|
95
|
-
t === 'cg'
|
|
96
|
-
) {
|
|
97
|
-
return false
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const lt = t.toLowerCase()
|
|
101
|
-
const s = seen[lt]
|
|
102
|
-
seen[lt] = true
|
|
103
|
-
return !s
|
|
104
|
-
})
|
|
25
|
+
get flags() {
|
|
26
|
+
return (
|
|
27
|
+
(this.byteArray.readInt32LE(this.bytes.start + 16) & 0xffff0000) >> 16
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
get ref_id() {
|
|
31
|
+
return this.byteArray.readInt32LE(this.bytes.start + 4)
|
|
105
32
|
}
|
|
106
33
|
|
|
107
|
-
|
|
108
|
-
return
|
|
34
|
+
get start() {
|
|
35
|
+
return this.byteArray.readInt32LE(this.bytes.start + 8)
|
|
109
36
|
}
|
|
110
37
|
|
|
111
|
-
|
|
112
|
-
return this.
|
|
38
|
+
get end() {
|
|
39
|
+
return this.start + this.length_on_ref
|
|
113
40
|
}
|
|
114
41
|
|
|
115
|
-
id() {
|
|
116
|
-
return this.
|
|
42
|
+
get id() {
|
|
43
|
+
return this.fileOffset
|
|
117
44
|
}
|
|
118
45
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
* Mapping quality score.
|
|
122
|
-
*/
|
|
123
|
-
mq() {
|
|
124
|
-
const mq = (this.get('_bin_mq_nl') & 0xff00) >> 8
|
|
46
|
+
get mq() {
|
|
47
|
+
const mq = (this.bin_mq_nl & 0xff00) >> 8
|
|
125
48
|
return mq === 255 ? undefined : mq
|
|
126
49
|
}
|
|
127
50
|
|
|
128
|
-
score() {
|
|
129
|
-
return this.
|
|
51
|
+
get score() {
|
|
52
|
+
return this.mq
|
|
130
53
|
}
|
|
131
54
|
|
|
132
|
-
qual() {
|
|
133
|
-
return this.qualRaw
|
|
55
|
+
get qual() {
|
|
56
|
+
return this.qualRaw?.join(' ')
|
|
134
57
|
}
|
|
135
58
|
|
|
136
|
-
qualRaw() {
|
|
59
|
+
get qualRaw() {
|
|
137
60
|
if (this.isSegmentUnmapped()) {
|
|
138
61
|
return
|
|
139
62
|
}
|
|
140
63
|
|
|
141
|
-
const { start, byteArray } = this.bytes
|
|
142
64
|
const p =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
const lseq = this.get('seq_length')
|
|
149
|
-
return byteArray.subarray(p, p + lseq)
|
|
65
|
+
this.b0 +
|
|
66
|
+
this.read_name_length +
|
|
67
|
+
this.num_cigar_ops * 4 +
|
|
68
|
+
this.num_seq_bytes
|
|
69
|
+
return this.byteArray.subarray(p, p + this.seq_length)
|
|
150
70
|
}
|
|
151
71
|
|
|
152
|
-
strand() {
|
|
72
|
+
get strand() {
|
|
153
73
|
return this.isReverseComplemented() ? -1 : 1
|
|
154
74
|
}
|
|
155
75
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
159
|
-
}
|
|
160
|
-
return this.isMateReverseComplemented() ? -1 : 1
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
name() {
|
|
164
|
-
return this.get('_read_name')
|
|
76
|
+
get b0() {
|
|
77
|
+
return this.bytes.start + 36
|
|
165
78
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
79
|
+
get name() {
|
|
80
|
+
return this.byteArray.toString(
|
|
81
|
+
'ascii',
|
|
82
|
+
this.b0,
|
|
83
|
+
this.b0 + this.read_name_length - 1,
|
|
84
|
+
)
|
|
171
85
|
}
|
|
172
86
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
* Only called if we have not already parsed that field.
|
|
176
|
-
*/
|
|
177
|
-
_parseTag(tagName?: string) {
|
|
178
|
-
// if all of the tags have been parsed and we're still being
|
|
179
|
-
// called, we already know that we have no such tag, because
|
|
180
|
-
// it would already have been cached.
|
|
181
|
-
if (this._allTagsParsed) {
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const { byteArray, start } = this.bytes
|
|
87
|
+
get tags() {
|
|
88
|
+
const { byteArray } = this.bytes
|
|
186
89
|
let p =
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.get('_seq_bytes') +
|
|
193
|
-
this.get('seq_length')
|
|
90
|
+
this.b0 +
|
|
91
|
+
this.read_name_length +
|
|
92
|
+
this.num_cigar_ops * 4 +
|
|
93
|
+
this.num_seq_bytes +
|
|
94
|
+
this.seq_length
|
|
194
95
|
|
|
195
96
|
const blockEnd = this.bytes.end
|
|
196
|
-
|
|
197
|
-
while (p < blockEnd
|
|
97
|
+
const tags = {} as Record<string, unknown>
|
|
98
|
+
while (p < blockEnd) {
|
|
198
99
|
const tag = String.fromCharCode(byteArray[p], byteArray[p + 1])
|
|
199
|
-
lcTag = tag.toLowerCase()
|
|
200
100
|
const type = String.fromCharCode(byteArray[p + 2])
|
|
201
101
|
p += 3
|
|
202
102
|
|
|
@@ -353,31 +253,9 @@ export default class BamRecord {
|
|
|
353
253
|
} // stop parsing tags
|
|
354
254
|
}
|
|
355
255
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
this._tagList.push(tag)
|
|
359
|
-
if (lcTag === tagName) {
|
|
360
|
-
return value
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
this.data[lcTag] = value
|
|
256
|
+
tags[tag] = value
|
|
364
257
|
}
|
|
365
|
-
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
_parseAllTags() {
|
|
370
|
-
this._parseTag('')
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
_parseCigar(cigar: string) {
|
|
374
|
-
return (
|
|
375
|
-
//@ts-ignore
|
|
376
|
-
cigar
|
|
377
|
-
.match(/\d+\D/g)
|
|
378
|
-
//@ts-ignore
|
|
379
|
-
.map(op => [/\D/.exec(op)[0].toUpperCase(), Number.parseInt(op, 10)])
|
|
380
|
-
)
|
|
258
|
+
return tags
|
|
381
259
|
}
|
|
382
260
|
|
|
383
261
|
/**
|
|
@@ -443,42 +321,44 @@ export default class BamRecord {
|
|
|
443
321
|
return !!(this.flags & Constants.BAM_FSUPPLEMENTARY)
|
|
444
322
|
}
|
|
445
323
|
|
|
446
|
-
|
|
324
|
+
get cigarAndLength() {
|
|
447
325
|
if (this.isSegmentUnmapped()) {
|
|
448
|
-
return
|
|
326
|
+
return {
|
|
327
|
+
length_on_ref: 0,
|
|
328
|
+
CIGAR: '',
|
|
329
|
+
}
|
|
449
330
|
}
|
|
450
331
|
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
let
|
|
454
|
-
const seqLen = this.get('seq_length')
|
|
455
|
-
let cigar = ''
|
|
456
|
-
let lref = 0
|
|
332
|
+
const numCigarOps = this.num_cigar_ops
|
|
333
|
+
let p = this.b0 + this.read_name_length
|
|
334
|
+
let CIGAR = ''
|
|
457
335
|
|
|
458
|
-
// check for CG tag by inspecting whether the CIGAR field
|
|
459
|
-
//
|
|
460
|
-
let cigop = byteArray.readInt32LE(p)
|
|
336
|
+
// check for CG tag by inspecting whether the CIGAR field contains a clip
|
|
337
|
+
// that consumes entire seqLen
|
|
338
|
+
let cigop = this.byteArray.readInt32LE(p)
|
|
461
339
|
let lop = cigop >> 4
|
|
462
340
|
let op = CIGAR_DECODER[cigop & 0xf]
|
|
463
|
-
if (op === 'S' && lop ===
|
|
464
|
-
// if there is a CG the second CIGAR field will
|
|
465
|
-
//
|
|
341
|
+
if (op === 'S' && lop === this.seq_length) {
|
|
342
|
+
// if there is a CG the second CIGAR field will be a N tag the represents
|
|
343
|
+
// the length on ref
|
|
466
344
|
p += 4
|
|
467
|
-
cigop = byteArray.readInt32LE(p)
|
|
345
|
+
cigop = this.byteArray.readInt32LE(p)
|
|
468
346
|
lop = cigop >> 4
|
|
469
347
|
op = CIGAR_DECODER[cigop & 0xf]
|
|
470
348
|
if (op !== 'N') {
|
|
471
349
|
console.warn('CG tag with no N tag')
|
|
472
350
|
}
|
|
473
|
-
|
|
474
|
-
|
|
351
|
+
return {
|
|
352
|
+
CIGAR: this.tags.CG as string,
|
|
353
|
+
length_on_ref: lop,
|
|
354
|
+
}
|
|
475
355
|
} else {
|
|
356
|
+
let lref = 0
|
|
476
357
|
for (let c = 0; c < numCigarOps; ++c) {
|
|
477
|
-
cigop = byteArray.readInt32LE(p)
|
|
358
|
+
cigop = this.byteArray.readInt32LE(p)
|
|
478
359
|
lop = cigop >> 4
|
|
479
360
|
op = CIGAR_DECODER[cigop & 0xf]
|
|
480
|
-
|
|
481
|
-
|
|
361
|
+
CIGAR += lop + op
|
|
482
362
|
// soft clip, hard clip, and insertion don't count toward
|
|
483
363
|
// the length on the reference
|
|
484
364
|
if (op !== 'H' && op !== 'S' && op !== 'I') {
|
|
@@ -488,45 +368,38 @@ export default class BamRecord {
|
|
|
488
368
|
p += 4
|
|
489
369
|
}
|
|
490
370
|
|
|
491
|
-
|
|
492
|
-
|
|
371
|
+
return {
|
|
372
|
+
CIGAR,
|
|
373
|
+
length_on_ref: lref,
|
|
374
|
+
}
|
|
493
375
|
}
|
|
494
376
|
}
|
|
495
377
|
|
|
496
|
-
length_on_ref() {
|
|
497
|
-
|
|
498
|
-
return this.data.length_on_ref
|
|
499
|
-
} else {
|
|
500
|
-
this.get('cigar') // the length_on_ref is set as a side effect
|
|
501
|
-
return this.data.length_on_ref
|
|
502
|
-
}
|
|
378
|
+
get length_on_ref() {
|
|
379
|
+
return this.cigarAndLength.length_on_ref
|
|
503
380
|
}
|
|
504
381
|
|
|
505
|
-
|
|
506
|
-
return this.
|
|
382
|
+
get CIGAR() {
|
|
383
|
+
return this.cigarAndLength.CIGAR
|
|
507
384
|
}
|
|
508
385
|
|
|
509
|
-
|
|
510
|
-
return this.
|
|
386
|
+
get num_cigar_ops() {
|
|
387
|
+
return this.flag_nc & 0xffff
|
|
511
388
|
}
|
|
512
389
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
*/
|
|
516
|
-
_seq_bytes() {
|
|
517
|
-
return (this.get('seq_length') + 1) >> 1
|
|
390
|
+
get read_name_length() {
|
|
391
|
+
return this.bin_mq_nl & 0xff
|
|
518
392
|
}
|
|
519
393
|
|
|
520
|
-
|
|
521
|
-
return this.
|
|
394
|
+
get num_seq_bytes() {
|
|
395
|
+
return (this.seq_length + 1) >> 1
|
|
522
396
|
}
|
|
523
397
|
|
|
524
|
-
seq() {
|
|
525
|
-
const { byteArray
|
|
526
|
-
const p =
|
|
527
|
-
|
|
528
|
-
const
|
|
529
|
-
const len = this.get('seq_length')
|
|
398
|
+
get seq() {
|
|
399
|
+
const { byteArray } = this.bytes
|
|
400
|
+
const p = this.b0 + this.read_name_length + this.num_cigar_ops * 4
|
|
401
|
+
const seqBytes = this.num_seq_bytes
|
|
402
|
+
const len = this.seq_length
|
|
530
403
|
let buf = ''
|
|
531
404
|
let i = 0
|
|
532
405
|
for (let j = 0; j < seqBytes; ++j) {
|
|
@@ -542,11 +415,11 @@ export default class BamRecord {
|
|
|
542
415
|
}
|
|
543
416
|
|
|
544
417
|
// adapted from igv.js
|
|
545
|
-
|
|
418
|
+
get pair_orientation() {
|
|
546
419
|
if (
|
|
547
420
|
!this.isSegmentUnmapped() &&
|
|
548
421
|
!this.isMateUnmapped() &&
|
|
549
|
-
this.
|
|
422
|
+
this.ref_id === this.next_refid
|
|
550
423
|
) {
|
|
551
424
|
const s1 = this.isReverseComplemented() ? 'R' : 'F'
|
|
552
425
|
const s2 = this.isMateReverseComplemented() ? 'R' : 'F'
|
|
@@ -561,7 +434,7 @@ export default class BamRecord {
|
|
|
561
434
|
}
|
|
562
435
|
|
|
563
436
|
const tmp = []
|
|
564
|
-
const isize = this.template_length
|
|
437
|
+
const isize = this.template_length
|
|
565
438
|
if (isize > 0) {
|
|
566
439
|
tmp[0] = s1
|
|
567
440
|
tmp[1] = o1
|
|
@@ -578,28 +451,28 @@ export default class BamRecord {
|
|
|
578
451
|
return ''
|
|
579
452
|
}
|
|
580
453
|
|
|
581
|
-
|
|
582
|
-
return this.
|
|
454
|
+
get bin_mq_nl() {
|
|
455
|
+
return this.byteArray.readInt32LE(this.bytes.start + 12)
|
|
583
456
|
}
|
|
584
457
|
|
|
585
|
-
|
|
586
|
-
return this.
|
|
458
|
+
get flag_nc() {
|
|
459
|
+
return this.byteArray.readInt32LE(this.bytes.start + 16)
|
|
587
460
|
}
|
|
588
461
|
|
|
589
|
-
seq_length() {
|
|
590
|
-
return this.
|
|
462
|
+
get seq_length() {
|
|
463
|
+
return this.byteArray.readInt32LE(this.bytes.start + 20)
|
|
591
464
|
}
|
|
592
465
|
|
|
593
|
-
|
|
594
|
-
return this.
|
|
466
|
+
get next_refid() {
|
|
467
|
+
return this.byteArray.readInt32LE(this.bytes.start + 24)
|
|
595
468
|
}
|
|
596
469
|
|
|
597
|
-
|
|
598
|
-
return this.
|
|
470
|
+
get next_pos() {
|
|
471
|
+
return this.byteArray.readInt32LE(this.bytes.start + 28)
|
|
599
472
|
}
|
|
600
473
|
|
|
601
|
-
template_length() {
|
|
602
|
-
return this.
|
|
474
|
+
get template_length() {
|
|
475
|
+
return this.byteArray.readInt32LE(this.bytes.start + 32)
|
|
603
476
|
}
|
|
604
477
|
|
|
605
478
|
toJSON() {
|
|
@@ -615,3 +488,27 @@ export default class BamRecord {
|
|
|
615
488
|
return data
|
|
616
489
|
}
|
|
617
490
|
}
|
|
491
|
+
|
|
492
|
+
function cacheGetter<T>(ctor: { prototype: T }, prop: keyof T): void {
|
|
493
|
+
const desc = Object.getOwnPropertyDescriptor(ctor.prototype, prop)
|
|
494
|
+
if (!desc) {
|
|
495
|
+
throw new Error('OH NO, NO PROPERTY DESCRIPTOR')
|
|
496
|
+
}
|
|
497
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
498
|
+
const getter = desc.get
|
|
499
|
+
if (!getter) {
|
|
500
|
+
throw new Error('OH NO, NOT A GETTER')
|
|
501
|
+
}
|
|
502
|
+
Object.defineProperty(ctor.prototype, prop, {
|
|
503
|
+
get() {
|
|
504
|
+
const ret = getter.call(this)
|
|
505
|
+
Object.defineProperty(this, prop, { value: ret })
|
|
506
|
+
return ret
|
|
507
|
+
},
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
cacheGetter(BamRecord, 'tags')
|
|
512
|
+
cacheGetter(BamRecord, 'cigarAndLength')
|
|
513
|
+
cacheGetter(BamRecord, 'seq')
|
|
514
|
+
cacheGetter(BamRecord, 'qual')
|