@gmod/bam 2.0.3 → 3.0.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.
- package/CHANGELOG.md +9 -4
- package/README.md +10 -42
- package/dist/bai.js +6 -1
- package/dist/bai.js.map +1 -1
- package/dist/bamFile.js +16 -14
- 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 +45 -55
- package/dist/record.js +123 -208
- 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 +6 -1
- package/esm/bai.js.map +1 -1
- package/esm/bamFile.js +14 -12
- 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 +45 -55
- package/esm/record.js +123 -208
- 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 +15 -16
- package/src/bai.ts +7 -2
- package/src/bamFile.ts +14 -12
- package/src/csi.ts +5 -3
- package/src/record.ts +140 -243
- 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,6 +185,7 @@ 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) {
|
|
@@ -199,6 +203,7 @@ 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
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
202
207
|
if (vp && (!lowest || vp.compareTo(lowest) < 0)) {
|
|
203
208
|
lowest = vp
|
|
204
209
|
}
|
|
@@ -209,7 +214,7 @@ export default class BAI extends IndexFile {
|
|
|
209
214
|
|
|
210
215
|
async parse(opts: BaseOpts = {}) {
|
|
211
216
|
if (!this.setupP) {
|
|
212
|
-
this.setupP = this._parse(opts).catch(e => {
|
|
217
|
+
this.setupP = this._parse(opts).catch((e: unknown) => {
|
|
213
218
|
this.setupP = undefined
|
|
214
219
|
throw e
|
|
215
220
|
})
|
package/src/bamFile.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'buffer'
|
|
2
|
-
import crc32 from '
|
|
2
|
+
import crc32 from 'crc/crc32'
|
|
3
3
|
import { unzip, unzipChunkSlice } from '@gmod/bgzf-filehandle'
|
|
4
4
|
import { LocalFile, RemoteFile, GenericFilehandle } from 'generic-filehandle'
|
|
5
5
|
import AbortablePromiseCache from '@gmod/abortable-promise-cache'
|
|
@@ -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,203 +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 = {}
|
|
23
|
-
this.bytes = bytes
|
|
24
|
-
this._id = fileOffset
|
|
25
|
-
this._refID = byteArray.readInt32LE(start + 4)
|
|
26
|
-
this.data.start = byteArray.readInt32LE(start + 8)
|
|
27
|
-
this.flags = (byteArray.readInt32LE(start + 16) & 0xffff0000) >> 16
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get(field: string) {
|
|
31
|
-
//@ts-ignore
|
|
32
|
-
if (this[field]) {
|
|
33
|
-
//@ts-ignore
|
|
34
|
-
if (this.data[field]) {
|
|
35
|
-
return this.data[field]
|
|
36
|
-
}
|
|
37
|
-
//@ts-ignore
|
|
38
|
-
this.data[field] = this[field]()
|
|
39
|
-
return this.data[field]
|
|
40
|
-
}
|
|
41
|
-
return this._get(field.toLowerCase())
|
|
42
|
-
}
|
|
13
|
+
public fileOffset: number
|
|
14
|
+
private bytes: Bytes
|
|
43
15
|
|
|
44
|
-
|
|
45
|
-
|
|
16
|
+
constructor(args: { bytes: Bytes; fileOffset: number }) {
|
|
17
|
+
this.bytes = args.bytes
|
|
18
|
+
this.fileOffset = args.fileOffset
|
|
46
19
|
}
|
|
47
20
|
|
|
48
|
-
|
|
49
|
-
return this.
|
|
21
|
+
get byteArray() {
|
|
22
|
+
return this.bytes.byteArray
|
|
50
23
|
}
|
|
51
24
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.
|
|
59
|
-
return this.data[field]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
_tags() {
|
|
63
|
-
this._parseAllTags()
|
|
64
|
-
|
|
65
|
-
let tags = ['seq']
|
|
66
|
-
|
|
67
|
-
if (!this.isSegmentUnmapped()) {
|
|
68
|
-
tags.push(
|
|
69
|
-
'start',
|
|
70
|
-
'end',
|
|
71
|
-
'strand',
|
|
72
|
-
'score',
|
|
73
|
-
'qual',
|
|
74
|
-
'MQ',
|
|
75
|
-
'CIGAR',
|
|
76
|
-
'length_on_ref',
|
|
77
|
-
'template_length',
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
if (this.isPaired()) {
|
|
81
|
-
tags.push('next_segment_position', 'pair_orientation')
|
|
82
|
-
}
|
|
83
|
-
tags = tags.concat(this._tagList || [])
|
|
84
|
-
|
|
85
|
-
for (const k of Object.keys(this.data)) {
|
|
86
|
-
if (!k.startsWith('_') && k !== 'next_seq_id') {
|
|
87
|
-
tags.push(k)
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const seen: Record<string, boolean> = {}
|
|
92
|
-
return tags.filter(t => {
|
|
93
|
-
if (
|
|
94
|
-
(t in this.data && this.data[t] === undefined) ||
|
|
95
|
-
t === 'CG' ||
|
|
96
|
-
t === 'cg'
|
|
97
|
-
) {
|
|
98
|
-
return false
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const lt = t.toLowerCase()
|
|
102
|
-
const s = seen[lt]
|
|
103
|
-
seen[lt] = true
|
|
104
|
-
return !s
|
|
105
|
-
})
|
|
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)
|
|
106
32
|
}
|
|
107
33
|
|
|
108
|
-
|
|
109
|
-
return
|
|
34
|
+
get start() {
|
|
35
|
+
return this.byteArray.readInt32LE(this.bytes.start + 8)
|
|
110
36
|
}
|
|
111
37
|
|
|
112
|
-
|
|
113
|
-
return this.
|
|
38
|
+
get end() {
|
|
39
|
+
return this.start + this.length_on_ref
|
|
114
40
|
}
|
|
115
41
|
|
|
116
|
-
id() {
|
|
117
|
-
return this.
|
|
42
|
+
get id() {
|
|
43
|
+
return this.fileOffset
|
|
118
44
|
}
|
|
119
45
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* Mapping quality score.
|
|
123
|
-
*/
|
|
124
|
-
mq() {
|
|
125
|
-
const mq = (this.get('_bin_mq_nl') & 0xff00) >> 8
|
|
46
|
+
get mq() {
|
|
47
|
+
const mq = (this.bin_mq_nl & 0xff00) >> 8
|
|
126
48
|
return mq === 255 ? undefined : mq
|
|
127
49
|
}
|
|
128
50
|
|
|
129
|
-
score() {
|
|
130
|
-
return this.
|
|
51
|
+
get score() {
|
|
52
|
+
return this.mq
|
|
131
53
|
}
|
|
132
54
|
|
|
133
|
-
qual() {
|
|
134
|
-
return this.qualRaw
|
|
55
|
+
get qual() {
|
|
56
|
+
return this.qualRaw?.join(' ')
|
|
135
57
|
}
|
|
136
58
|
|
|
137
|
-
qualRaw() {
|
|
59
|
+
get qualRaw() {
|
|
138
60
|
if (this.isSegmentUnmapped()) {
|
|
139
61
|
return
|
|
140
62
|
}
|
|
141
63
|
|
|
142
|
-
const { start, byteArray } = this.bytes
|
|
143
64
|
const p =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
148
|
-
|
|
149
|
-
const lseq = this.get('seq_length')
|
|
150
|
-
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)
|
|
151
70
|
}
|
|
152
71
|
|
|
153
|
-
strand() {
|
|
72
|
+
get strand() {
|
|
154
73
|
return this.isReverseComplemented() ? -1 : 1
|
|
155
74
|
}
|
|
156
75
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return
|
|
160
|
-
}
|
|
161
|
-
return this.isMateReverseComplemented() ? -1 : 1
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
name() {
|
|
165
|
-
return this.get('_read_name')
|
|
76
|
+
get b0() {
|
|
77
|
+
return this.bytes.start + 36
|
|
166
78
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
79
|
+
get name() {
|
|
80
|
+
return this.byteArray.toString(
|
|
81
|
+
'ascii',
|
|
82
|
+
this.b0,
|
|
83
|
+
this.b0 + this.read_name_length - 1,
|
|
84
|
+
)
|
|
172
85
|
}
|
|
173
86
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
* Only called if we have not already parsed that field.
|
|
177
|
-
*/
|
|
178
|
-
_parseTag(tagName?: string) {
|
|
179
|
-
// if all of the tags have been parsed and we're still being
|
|
180
|
-
// called, we already know that we have no such tag, because
|
|
181
|
-
// it would already have been cached.
|
|
182
|
-
if (this._allTagsParsed) {
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const { byteArray, start } = this.bytes
|
|
87
|
+
get tags() {
|
|
88
|
+
const { byteArray } = this.bytes
|
|
187
89
|
let p =
|
|
188
|
-
this.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.get('_seq_bytes') +
|
|
194
|
-
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
|
|
195
95
|
|
|
196
96
|
const blockEnd = this.bytes.end
|
|
197
|
-
|
|
198
|
-
while (p < blockEnd
|
|
97
|
+
const tags = {} as Record<string, unknown>
|
|
98
|
+
while (p < blockEnd) {
|
|
199
99
|
const tag = String.fromCharCode(byteArray[p], byteArray[p + 1])
|
|
200
|
-
lcTag = tag.toLowerCase()
|
|
201
100
|
const type = String.fromCharCode(byteArray[p + 2])
|
|
202
101
|
p += 3
|
|
203
102
|
|
|
@@ -354,35 +253,14 @@ export default class BamRecord {
|
|
|
354
253
|
} // stop parsing tags
|
|
355
254
|
}
|
|
356
255
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this._tagList.push(tag)
|
|
360
|
-
if (lcTag === tagName) {
|
|
361
|
-
return value
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
this.data[lcTag] = value
|
|
256
|
+
tags[tag] = value
|
|
365
257
|
}
|
|
366
|
-
|
|
367
|
-
return
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
_parseAllTags() {
|
|
371
|
-
this._parseTag('')
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
_parseCigar(cigar: string) {
|
|
375
|
-
return (
|
|
376
|
-
//@ts-ignore
|
|
377
|
-
cigar
|
|
378
|
-
.match(/\d+\D/g)
|
|
379
|
-
//@ts-ignore
|
|
380
|
-
.map(op => [op.match(/\D/)[0].toUpperCase(), Number.parseInt(op, 10)])
|
|
381
|
-
)
|
|
258
|
+
return tags
|
|
382
259
|
}
|
|
383
260
|
|
|
384
261
|
/**
|
|
385
|
-
* @returns {boolean} true if the read is paired, regardless of whether both
|
|
262
|
+
* @returns {boolean} true if the read is paired, regardless of whether both
|
|
263
|
+
* segments are mapped
|
|
386
264
|
*/
|
|
387
265
|
isPaired() {
|
|
388
266
|
return !!(this.flags & Constants.BAM_FPAIRED)
|
|
@@ -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')
|