@gmod/bam 7.2.4 → 7.3.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/README.md +28 -1
- package/dist/bamFile.d.ts +3 -2
- package/dist/bamFile.js +32 -19
- package/dist/bamFile.js.map +1 -1
- package/dist/htsget.js +1 -0
- package/dist/htsget.js.map +1 -1
- package/dist/record.d.ts +1 -1
- package/dist/record.js +111 -162
- package/dist/record.js.map +1 -1
- package/dist/util.d.ts +8 -0
- package/dist/util.js +6 -2
- package/dist/util.js.map +1 -1
- package/esm/bamFile.d.ts +3 -2
- package/esm/bamFile.js +32 -19
- package/esm/bamFile.js.map +1 -1
- package/esm/htsget.js +1 -0
- package/esm/htsget.js.map +1 -1
- package/esm/record.d.ts +1 -1
- package/esm/record.js +111 -162
- package/esm/record.js.map +1 -1
- package/esm/util.d.ts +8 -0
- package/esm/util.js +6 -2
- package/esm/util.js.map +1 -1
- package/package.json +9 -9
- package/src/bamFile.ts +34 -23
- package/src/htsget.ts +1 -0
- package/src/record.ts +128 -145
- package/src/util.ts +13 -2
package/src/bamFile.ts
CHANGED
|
@@ -203,14 +203,20 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
|
|
|
203
203
|
return this.header
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
+
// Resolve a reference name to its numeric id, ensuring the header (which
|
|
207
|
+
// populates chrToIndex) has been parsed first.
|
|
208
|
+
private async getSeqId(seqName: string, opts?: BaseOpts) {
|
|
209
|
+
await this.getHeader(opts)
|
|
210
|
+
return this.chrToIndex?.[seqName]
|
|
211
|
+
}
|
|
212
|
+
|
|
206
213
|
async getRecordsForRange(
|
|
207
214
|
chr: string,
|
|
208
215
|
min: number,
|
|
209
216
|
max: number,
|
|
210
217
|
opts?: BamOpts,
|
|
211
218
|
) {
|
|
212
|
-
await this.
|
|
213
|
-
const chrId = this.chrToIndex?.[chr]
|
|
219
|
+
const chrId = await this.getSeqId(chr, opts)
|
|
214
220
|
if (chrId === undefined || !this.index) {
|
|
215
221
|
return []
|
|
216
222
|
}
|
|
@@ -234,9 +240,16 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
|
|
|
234
240
|
max: number,
|
|
235
241
|
opts: BamOpts = {},
|
|
236
242
|
) {
|
|
237
|
-
const { viewAsPairs, filterBy } = opts
|
|
243
|
+
const { viewAsPairs, filterBy, onProgress } = opts
|
|
238
244
|
const result: T[] = []
|
|
239
245
|
|
|
246
|
+
let totalBytes = 0
|
|
247
|
+
for (let ci = 0, cl = chunks.length; ci < cl; ci++) {
|
|
248
|
+
totalBytes += chunks[ci]!.fetchedSize()
|
|
249
|
+
}
|
|
250
|
+
let downloadedBytes = 0
|
|
251
|
+
onProgress?.(0, totalBytes)
|
|
252
|
+
|
|
240
253
|
for (let ci = 0, cl = chunks.length; ci < cl; ci++) {
|
|
241
254
|
const chunk = chunks[ci]!
|
|
242
255
|
const cacheKey = chunkCacheKey(chunk, filterBy)
|
|
@@ -258,6 +271,8 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
|
|
|
258
271
|
})
|
|
259
272
|
}
|
|
260
273
|
|
|
274
|
+
downloadedBytes += chunk.fetchedSize()
|
|
275
|
+
onProgress?.(downloadedBytes, totalBytes)
|
|
261
276
|
appendInRange(records, chrId, min, max, result)
|
|
262
277
|
}
|
|
263
278
|
|
|
@@ -335,11 +350,14 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
|
|
|
335
350
|
}
|
|
336
351
|
|
|
337
352
|
async _readChunkFeatures(chunk: Chunk, opts: BaseOpts) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
353
|
+
// Don't forward onProgress to the inner read: getRecordsForRange already
|
|
354
|
+
// reports progress at chunk granularity (downloadedBytes/totalBytes). If the
|
|
355
|
+
// filehandle's own streaming onProgress also fired this callback it would
|
|
356
|
+
// report a different `total` (this chunk's size, not the whole query),
|
|
357
|
+
// making the determinate bar jump around.
|
|
358
|
+
const buf = await this.bam.read(chunk.fetchedSize(), chunk.minv.blockPosition, {
|
|
359
|
+
signal: opts.signal,
|
|
360
|
+
})
|
|
343
361
|
const {
|
|
344
362
|
buffer: data,
|
|
345
363
|
cpositions,
|
|
@@ -397,20 +415,20 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
|
|
|
397
415
|
return sink
|
|
398
416
|
}
|
|
399
417
|
|
|
400
|
-
async hasRefSeq(seqName: string) {
|
|
401
|
-
const seqId = this.
|
|
418
|
+
async hasRefSeq(seqName: string, opts?: BaseOpts) {
|
|
419
|
+
const seqId = await this.getSeqId(seqName, opts)
|
|
402
420
|
return !this.index || seqId === undefined
|
|
403
421
|
? false
|
|
404
422
|
: this.index.hasRefSeq(seqId)
|
|
405
423
|
}
|
|
406
424
|
|
|
407
|
-
async lineCount(seqName: string) {
|
|
408
|
-
const seqId = this.
|
|
425
|
+
async lineCount(seqName: string, opts?: BaseOpts) {
|
|
426
|
+
const seqId = await this.getSeqId(seqName, opts)
|
|
409
427
|
return !this.index || seqId === undefined ? 0 : this.index.lineCount(seqId)
|
|
410
428
|
}
|
|
411
429
|
|
|
412
430
|
async indexCov(seqName: string, start?: number, end?: number) {
|
|
413
|
-
const seqId = this.
|
|
431
|
+
const seqId = await this.getSeqId(seqName)
|
|
414
432
|
return !this.index || seqId === undefined
|
|
415
433
|
? []
|
|
416
434
|
: this.index.indexCov(seqId, start, end)
|
|
@@ -422,7 +440,7 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
|
|
|
422
440
|
end: number,
|
|
423
441
|
opts?: BaseOpts,
|
|
424
442
|
) {
|
|
425
|
-
const seqId = this.
|
|
443
|
+
const seqId = await this.getSeqId(seqName, opts)
|
|
426
444
|
return !this.index || seqId === undefined
|
|
427
445
|
? []
|
|
428
446
|
: this.index.blocksForRange(seqId, start, end, opts)
|
|
@@ -440,16 +458,9 @@ export default class BamFile<T extends BamRecordLike = BAMFeature> {
|
|
|
440
458
|
return 0
|
|
441
459
|
}
|
|
442
460
|
await this.getHeader(opts)
|
|
443
|
-
const chrToIndex = this.chrToIndex
|
|
444
|
-
if (!chrToIndex) {
|
|
445
|
-
throw new Error('Header not yet parsed')
|
|
446
|
-
}
|
|
447
461
|
const mapped = regions.flatMap(r => {
|
|
448
|
-
const refId = chrToIndex[r.refName]
|
|
449
|
-
|
|
450
|
-
return []
|
|
451
|
-
}
|
|
452
|
-
return [{ refId, start: r.start, end: r.end }]
|
|
462
|
+
const refId = this.chrToIndex?.[r.refName]
|
|
463
|
+
return refId === undefined ? [] : [{ refId, start: r.start, end: r.end }]
|
|
453
464
|
})
|
|
454
465
|
return this.index.estimatedBytesForRegions(mapped, opts)
|
|
455
466
|
}
|
package/src/htsget.ts
CHANGED
|
@@ -64,6 +64,7 @@ export default class HtsgetFile<
|
|
|
64
64
|
max: number,
|
|
65
65
|
opts?: BamOpts,
|
|
66
66
|
) {
|
|
67
|
+
await this.getHeader(opts)
|
|
67
68
|
const base = `${this.baseUrl}/${this.trackId}`
|
|
68
69
|
const url = `${base}?referenceName=${chr}&start=${min}&end=${max}&format=BAM`
|
|
69
70
|
const chrId = this.chrToIndex?.[chr]
|
package/src/record.ts
CHANGED
|
@@ -29,6 +29,104 @@ export interface Bytes {
|
|
|
29
29
|
byteArray: Uint8Array
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
type BArrayValue =
|
|
33
|
+
| Int8Array
|
|
34
|
+
| Uint8Array
|
|
35
|
+
| Int16Array
|
|
36
|
+
| Uint16Array
|
|
37
|
+
| Int32Array
|
|
38
|
+
| Uint32Array
|
|
39
|
+
| Float32Array
|
|
40
|
+
| number[]
|
|
41
|
+
|
|
42
|
+
// Decode a 'B' (array) tag value starting at `p` (the byte after type+subtype+
|
|
43
|
+
// count). When the data is naturally aligned we return a typed-array view over
|
|
44
|
+
// the underlying buffer (zero-copy); otherwise we copy element-by-element since
|
|
45
|
+
// typed-array views require alignment. Shared by getTag and the full-tag parse.
|
|
46
|
+
function decodeBArrayTag(
|
|
47
|
+
ba: Uint8Array,
|
|
48
|
+
dataView: DataView,
|
|
49
|
+
p: number,
|
|
50
|
+
Btype: number,
|
|
51
|
+
limit: number,
|
|
52
|
+
): BArrayValue | undefined {
|
|
53
|
+
const absOffset = ba.byteOffset + p
|
|
54
|
+
switch (Btype) {
|
|
55
|
+
case 0x69: {
|
|
56
|
+
// 'i'
|
|
57
|
+
if (absOffset % 4 === 0) {
|
|
58
|
+
return new Int32Array(ba.buffer, absOffset, limit)
|
|
59
|
+
}
|
|
60
|
+
const arr = new Array<number>(limit)
|
|
61
|
+
for (let i = 0; i < limit; i++) {
|
|
62
|
+
arr[i] = dataView.getInt32(p + i * 4, true)
|
|
63
|
+
}
|
|
64
|
+
return arr
|
|
65
|
+
}
|
|
66
|
+
case 0x49: {
|
|
67
|
+
// 'I'
|
|
68
|
+
if (absOffset % 4 === 0) {
|
|
69
|
+
return new Uint32Array(ba.buffer, absOffset, limit)
|
|
70
|
+
}
|
|
71
|
+
const arr = new Array<number>(limit)
|
|
72
|
+
for (let i = 0; i < limit; i++) {
|
|
73
|
+
arr[i] = dataView.getUint32(p + i * 4, true)
|
|
74
|
+
}
|
|
75
|
+
return arr
|
|
76
|
+
}
|
|
77
|
+
case 0x73: {
|
|
78
|
+
// 's'
|
|
79
|
+
if (absOffset % 2 === 0) {
|
|
80
|
+
return new Int16Array(ba.buffer, absOffset, limit)
|
|
81
|
+
}
|
|
82
|
+
const arr = new Array<number>(limit)
|
|
83
|
+
for (let i = 0; i < limit; i++) {
|
|
84
|
+
arr[i] = dataView.getInt16(p + i * 2, true)
|
|
85
|
+
}
|
|
86
|
+
return arr
|
|
87
|
+
}
|
|
88
|
+
case 0x53: {
|
|
89
|
+
// 'S'
|
|
90
|
+
if (absOffset % 2 === 0) {
|
|
91
|
+
return new Uint16Array(ba.buffer, absOffset, limit)
|
|
92
|
+
}
|
|
93
|
+
const arr = new Array<number>(limit)
|
|
94
|
+
for (let i = 0; i < limit; i++) {
|
|
95
|
+
arr[i] = dataView.getUint16(p + i * 2, true)
|
|
96
|
+
}
|
|
97
|
+
return arr
|
|
98
|
+
}
|
|
99
|
+
case 0x63: // 'c'
|
|
100
|
+
return new Int8Array(ba.buffer, absOffset, limit)
|
|
101
|
+
case 0x43: // 'C'
|
|
102
|
+
return new Uint8Array(ba.buffer, absOffset, limit)
|
|
103
|
+
case 0x66: {
|
|
104
|
+
// 'f'
|
|
105
|
+
if (absOffset % 4 === 0) {
|
|
106
|
+
return new Float32Array(ba.buffer, absOffset, limit)
|
|
107
|
+
}
|
|
108
|
+
const arr = new Array<number>(limit)
|
|
109
|
+
for (let i = 0; i < limit; i++) {
|
|
110
|
+
arr[i] = dataView.getFloat32(p + i * 4, true)
|
|
111
|
+
}
|
|
112
|
+
return arr
|
|
113
|
+
}
|
|
114
|
+
default:
|
|
115
|
+
return undefined
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Byte span of a 'B' tag's element payload, for advancing the cursor past it.
|
|
120
|
+
function bArrayByteLength(Btype: number, limit: number) {
|
|
121
|
+
if (Btype === 0x69 || Btype === 0x49 || Btype === 0x66) {
|
|
122
|
+
return limit << 2
|
|
123
|
+
} else if (Btype === 0x73 || Btype === 0x53) {
|
|
124
|
+
return limit << 1
|
|
125
|
+
} else {
|
|
126
|
+
return limit
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
32
130
|
export default class BamRecord {
|
|
33
131
|
public fileOffset: number
|
|
34
132
|
private _byteArray: Uint8Array
|
|
@@ -56,7 +154,10 @@ export default class BamRecord {
|
|
|
56
154
|
}
|
|
57
155
|
|
|
58
156
|
get flags() {
|
|
59
|
-
|
|
157
|
+
// FLAG is the high 16 bits of flag_nc (byte offset 18). Read it directly as
|
|
158
|
+
// a uint16 — masking flag_nc and arithmetic-shifting would sign-extend any
|
|
159
|
+
// value >= 0x8000.
|
|
160
|
+
return this._dataView.getUint16(this._start + 18, true)
|
|
60
161
|
}
|
|
61
162
|
|
|
62
163
|
get ref_id() {
|
|
@@ -83,11 +184,14 @@ export default class BamRecord {
|
|
|
83
184
|
return this.mq
|
|
84
185
|
}
|
|
85
186
|
|
|
187
|
+
// QUAL is present whenever the record has bases — independent of the unmapped
|
|
188
|
+
// flag (unmapped reads routinely carry SEQ/QUAL). A zero-length SEQ means
|
|
189
|
+
// there is no quality to return.
|
|
86
190
|
get qual() {
|
|
87
|
-
|
|
191
|
+
const seqLen = this.seq_length
|
|
192
|
+
if (seqLen === 0) {
|
|
88
193
|
return null
|
|
89
194
|
} else {
|
|
90
|
-
const seqLen = this.seq_length
|
|
91
195
|
const p = this.seqStart + ((seqLen + 1) >> 1)
|
|
92
196
|
return this._byteArray.subarray(p, p + seqLen)
|
|
93
197
|
}
|
|
@@ -236,78 +340,20 @@ export default class BamRecord {
|
|
|
236
340
|
}
|
|
237
341
|
case 0x42: {
|
|
238
342
|
// 'B'
|
|
239
|
-
const Btype = ba[p++]
|
|
343
|
+
const Btype = ba[p++]!
|
|
240
344
|
const limit = this._dataView.getInt32(p, true)
|
|
241
345
|
p += 4
|
|
242
|
-
const absOffset = ba.byteOffset + p
|
|
243
346
|
if (isMatch) {
|
|
244
|
-
|
|
245
|
-
// 'i'
|
|
246
|
-
if (absOffset % 4 === 0) {
|
|
247
|
-
return new Int32Array(ba.buffer, absOffset, limit)
|
|
248
|
-
}
|
|
249
|
-
const arr: number[] = new Array(limit)
|
|
250
|
-
for (let i = 0; i < limit; i++) {
|
|
251
|
-
arr[i] = this._dataView.getInt32(p + i * 4, true)
|
|
252
|
-
}
|
|
253
|
-
return arr
|
|
254
|
-
} else if (Btype === 0x49) {
|
|
255
|
-
// 'I'
|
|
256
|
-
if (absOffset % 4 === 0) {
|
|
257
|
-
return new Uint32Array(ba.buffer, absOffset, limit)
|
|
258
|
-
}
|
|
259
|
-
const arr: number[] = new Array(limit)
|
|
260
|
-
for (let i = 0; i < limit; i++) {
|
|
261
|
-
arr[i] = this._dataView.getUint32(p + i * 4, true)
|
|
262
|
-
}
|
|
263
|
-
return arr
|
|
264
|
-
} else if (Btype === 0x73) {
|
|
265
|
-
// 's'
|
|
266
|
-
if (absOffset % 2 === 0) {
|
|
267
|
-
return new Int16Array(ba.buffer, absOffset, limit)
|
|
268
|
-
}
|
|
269
|
-
const arr: number[] = new Array(limit)
|
|
270
|
-
for (let i = 0; i < limit; i++) {
|
|
271
|
-
arr[i] = this._dataView.getInt16(p + i * 2, true)
|
|
272
|
-
}
|
|
273
|
-
return arr
|
|
274
|
-
} else if (Btype === 0x53) {
|
|
275
|
-
// 'S'
|
|
276
|
-
if (absOffset % 2 === 0) {
|
|
277
|
-
return new Uint16Array(ba.buffer, absOffset, limit)
|
|
278
|
-
}
|
|
279
|
-
const arr: number[] = new Array(limit)
|
|
280
|
-
for (let i = 0; i < limit; i++) {
|
|
281
|
-
arr[i] = this._dataView.getUint16(p + i * 2, true)
|
|
282
|
-
}
|
|
283
|
-
return arr
|
|
284
|
-
} else if (Btype === 0x63) {
|
|
285
|
-
// 'c'
|
|
286
|
-
return new Int8Array(ba.buffer, absOffset, limit)
|
|
287
|
-
} else if (Btype === 0x43) {
|
|
288
|
-
// 'C'
|
|
289
|
-
return new Uint8Array(ba.buffer, absOffset, limit)
|
|
290
|
-
} else if (Btype === 0x66) {
|
|
291
|
-
// 'f'
|
|
292
|
-
if (absOffset % 4 === 0) {
|
|
293
|
-
return new Float32Array(ba.buffer, absOffset, limit)
|
|
294
|
-
}
|
|
295
|
-
const arr: number[] = new Array(limit)
|
|
296
|
-
for (let i = 0; i < limit; i++) {
|
|
297
|
-
arr[i] = this._dataView.getFloat32(p + i * 4, true)
|
|
298
|
-
}
|
|
299
|
-
return arr
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
if (Btype === 0x69 || Btype === 0x49 || Btype === 0x66) {
|
|
303
|
-
p += limit << 2
|
|
304
|
-
} else if (Btype === 0x73 || Btype === 0x53) {
|
|
305
|
-
p += limit << 1
|
|
306
|
-
} else if (Btype === 0x63 || Btype === 0x43) {
|
|
307
|
-
p += limit
|
|
347
|
+
return decodeBArrayTag(ba, this._dataView, p, Btype, limit)
|
|
308
348
|
}
|
|
349
|
+
p += bArrayByteLength(Btype, limit)
|
|
309
350
|
break
|
|
310
351
|
}
|
|
352
|
+
default:
|
|
353
|
+
if (type !== undefined) {
|
|
354
|
+
console.error('Unknown BAM tag type', type)
|
|
355
|
+
}
|
|
356
|
+
break
|
|
311
357
|
}
|
|
312
358
|
}
|
|
313
359
|
return undefined
|
|
@@ -370,79 +416,11 @@ export default class BamRecord {
|
|
|
370
416
|
}
|
|
371
417
|
case 0x42: {
|
|
372
418
|
// 'B'
|
|
373
|
-
const Btype = ba[p++]
|
|
419
|
+
const Btype = ba[p++]!
|
|
374
420
|
const limit = this._dataView.getInt32(p, true)
|
|
375
421
|
p += 4
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// 'i'
|
|
379
|
-
if (absOffset % 4 === 0) {
|
|
380
|
-
tags[tag] = new Int32Array(ba.buffer, absOffset, limit)
|
|
381
|
-
} else {
|
|
382
|
-
const arr: number[] = new Array(limit)
|
|
383
|
-
for (let i = 0; i < limit; i++) {
|
|
384
|
-
arr[i] = this._dataView.getInt32(p + i * 4, true)
|
|
385
|
-
}
|
|
386
|
-
tags[tag] = arr
|
|
387
|
-
}
|
|
388
|
-
p += limit << 2
|
|
389
|
-
} else if (Btype === 0x49) {
|
|
390
|
-
// 'I'
|
|
391
|
-
if (absOffset % 4 === 0) {
|
|
392
|
-
tags[tag] = new Uint32Array(ba.buffer, absOffset, limit)
|
|
393
|
-
} else {
|
|
394
|
-
const arr: number[] = new Array(limit)
|
|
395
|
-
for (let i = 0; i < limit; i++) {
|
|
396
|
-
arr[i] = this._dataView.getUint32(p + i * 4, true)
|
|
397
|
-
}
|
|
398
|
-
tags[tag] = arr
|
|
399
|
-
}
|
|
400
|
-
p += limit << 2
|
|
401
|
-
} else if (Btype === 0x73) {
|
|
402
|
-
// 's'
|
|
403
|
-
if (absOffset % 2 === 0) {
|
|
404
|
-
tags[tag] = new Int16Array(ba.buffer, absOffset, limit)
|
|
405
|
-
} else {
|
|
406
|
-
const arr: number[] = new Array(limit)
|
|
407
|
-
for (let i = 0; i < limit; i++) {
|
|
408
|
-
arr[i] = this._dataView.getInt16(p + i * 2, true)
|
|
409
|
-
}
|
|
410
|
-
tags[tag] = arr
|
|
411
|
-
}
|
|
412
|
-
p += limit << 1
|
|
413
|
-
} else if (Btype === 0x53) {
|
|
414
|
-
// 'S'
|
|
415
|
-
if (absOffset % 2 === 0) {
|
|
416
|
-
tags[tag] = new Uint16Array(ba.buffer, absOffset, limit)
|
|
417
|
-
} else {
|
|
418
|
-
const arr: number[] = new Array(limit)
|
|
419
|
-
for (let i = 0; i < limit; i++) {
|
|
420
|
-
arr[i] = this._dataView.getUint16(p + i * 2, true)
|
|
421
|
-
}
|
|
422
|
-
tags[tag] = arr
|
|
423
|
-
}
|
|
424
|
-
p += limit << 1
|
|
425
|
-
} else if (Btype === 0x63) {
|
|
426
|
-
// 'c'
|
|
427
|
-
tags[tag] = new Int8Array(ba.buffer, absOffset, limit)
|
|
428
|
-
p += limit
|
|
429
|
-
} else if (Btype === 0x43) {
|
|
430
|
-
// 'C'
|
|
431
|
-
tags[tag] = new Uint8Array(ba.buffer, absOffset, limit)
|
|
432
|
-
p += limit
|
|
433
|
-
} else if (Btype === 0x66) {
|
|
434
|
-
// 'f'
|
|
435
|
-
if (absOffset % 4 === 0) {
|
|
436
|
-
tags[tag] = new Float32Array(ba.buffer, absOffset, limit)
|
|
437
|
-
} else {
|
|
438
|
-
const arr: number[] = new Array(limit)
|
|
439
|
-
for (let i = 0; i < limit; i++) {
|
|
440
|
-
arr[i] = this._dataView.getFloat32(p + i * 4, true)
|
|
441
|
-
}
|
|
442
|
-
tags[tag] = arr
|
|
443
|
-
}
|
|
444
|
-
p += limit << 2
|
|
445
|
-
}
|
|
422
|
+
tags[tag] = decodeBArrayTag(ba, this._dataView, p, Btype, limit)
|
|
423
|
+
p += bArrayByteLength(Btype, limit)
|
|
446
424
|
break
|
|
447
425
|
}
|
|
448
426
|
default:
|
|
@@ -521,9 +499,14 @@ export default class BamRecord {
|
|
|
521
499
|
// Uint32Array view only for large aligned CIGARs.
|
|
522
500
|
|
|
523
501
|
// CG tag pattern: first op is soft-clip consuming entire sequence, second op is N encoding length-on-ref
|
|
524
|
-
private _isCGTagPattern(p: number) {
|
|
525
|
-
|
|
526
|
-
|
|
502
|
+
private _isCGTagPattern(p: number, numCigarOps: number) {
|
|
503
|
+
// htslib stores the placeholder as exactly two ops: <seqlen>S<reflen>N.
|
|
504
|
+
if (numCigarOps === 2) {
|
|
505
|
+
const cigop = this._dataView.getInt32(p, true)
|
|
506
|
+
return (cigop & 0xf) === CIGAR_SOFT_CLIP && cigop >> 4 === this.seq_length
|
|
507
|
+
} else {
|
|
508
|
+
return false
|
|
509
|
+
}
|
|
527
510
|
}
|
|
528
511
|
|
|
529
512
|
private _computeLengthOnRef(): number {
|
|
@@ -535,7 +518,7 @@ export default class BamRecord {
|
|
|
535
518
|
const numCigarOps = flag_nc & 0xffff
|
|
536
519
|
const p = this.b0 + this.read_name_length
|
|
537
520
|
|
|
538
|
-
if (this._isCGTagPattern(p)) {
|
|
521
|
+
if (this._isCGTagPattern(p, numCigarOps)) {
|
|
539
522
|
const cigop2 = this._dataView.getInt32(p + 4, true)
|
|
540
523
|
if ((cigop2 & 0xf) !== CIGAR_REF_SKIP) {
|
|
541
524
|
console.warn('CG tag with no N tag')
|
|
@@ -576,7 +559,7 @@ export default class BamRecord {
|
|
|
576
559
|
const numCigarOps = flag_nc & 0xffff
|
|
577
560
|
const p = this.b0 + this.read_name_length
|
|
578
561
|
|
|
579
|
-
if (this._isCGTagPattern(p)) {
|
|
562
|
+
if (this._isCGTagPattern(p, numCigarOps)) {
|
|
580
563
|
return (
|
|
581
564
|
(this.tags.CG as Uint32Array | number[] | undefined) ??
|
|
582
565
|
new Uint32Array(0)
|
package/src/util.ts
CHANGED
|
@@ -20,6 +20,14 @@ export interface BamOpts {
|
|
|
20
20
|
maxInsertSize?: number
|
|
21
21
|
signal?: AbortSignal
|
|
22
22
|
filterBy?: FilterBy
|
|
23
|
+
/**
|
|
24
|
+
* Called as the BGZF blocks covering the query are fetched, with cumulative
|
|
25
|
+
* downloaded bytes and the total to fetch. Reported at block granularity (one
|
|
26
|
+
* tick per chunk, including instant ticks for cache hits) since chunk byte
|
|
27
|
+
* sizes are known up front from the index. Lets callers render a determinate
|
|
28
|
+
* download progress bar.
|
|
29
|
+
*/
|
|
30
|
+
onProgress?: (bytesDownloaded: number, totalBytes: number) => void
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
export interface BaseOpts {
|
|
@@ -245,8 +253,9 @@ interface Positioned {
|
|
|
245
253
|
}
|
|
246
254
|
|
|
247
255
|
// Append records overlapping [min, max) on `chrId` into `out` (or a fresh
|
|
248
|
-
// array if omitted). Records are assumed sorted by
|
|
249
|
-
//
|
|
256
|
+
// array if omitted). Records are assumed coordinate-sorted (by ref_id, then
|
|
257
|
+
// start), so we stop scanning once we pass `max` within `chrId` or move past
|
|
258
|
+
// `chrId` entirely. Returns the populated array.
|
|
250
259
|
export function appendInRange<T extends Positioned>(
|
|
251
260
|
records: T[],
|
|
252
261
|
chrId: number,
|
|
@@ -262,6 +271,8 @@ export function appendInRange<T extends Positioned>(
|
|
|
262
271
|
} else if (r.end >= min) {
|
|
263
272
|
out.push(r)
|
|
264
273
|
}
|
|
274
|
+
} else if (r.ref_id > chrId) {
|
|
275
|
+
break
|
|
265
276
|
}
|
|
266
277
|
}
|
|
267
278
|
return out
|