@gmod/bam 4.0.0 → 5.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 +4 -4
- package/README.md +7 -11
- package/dist/bai.d.ts +1 -1
- package/dist/bai.js +151 -169
- package/dist/bai.js.map +1 -1
- package/dist/bamFile.d.ts +4 -5
- package/dist/bamFile.js +271 -350
- package/dist/bamFile.js.map +1 -1
- package/dist/chunk.d.ts +1 -1
- package/dist/chunk.js +5 -0
- package/dist/chunk.js.map +1 -1
- package/dist/csi.d.ts +1 -1
- package/dist/csi.js +140 -145
- package/dist/csi.js.map +1 -1
- package/dist/htsget.d.ts +1 -2
- package/dist/htsget.js +131 -161
- package/dist/htsget.js.map +1 -1
- package/dist/indexFile.d.ts +1 -1
- package/dist/indexFile.js +2 -0
- package/dist/indexFile.js.map +1 -1
- package/dist/nullIndex.js +2 -13
- package/dist/nullIndex.js.map +1 -1
- package/dist/record.d.ts +5 -5
- package/dist/record.js +44 -37
- package/dist/record.js.map +1 -1
- package/dist/util.d.ts +4 -2
- package/dist/util.js +25 -15
- package/dist/util.js.map +1 -1
- package/dist/virtualOffset.d.ts +1 -1
- package/dist/virtualOffset.js +2 -0
- package/dist/virtualOffset.js.map +1 -1
- package/esm/bai.d.ts +1 -1
- package/esm/bai.js +13 -13
- package/esm/bai.js.map +1 -1
- package/esm/bamFile.d.ts +4 -5
- package/esm/bamFile.js +49 -50
- package/esm/bamFile.js.map +1 -1
- package/esm/chunk.d.ts +1 -1
- package/esm/chunk.js +5 -0
- package/esm/chunk.js.map +1 -1
- package/esm/csi.d.ts +1 -1
- package/esm/csi.js +26 -28
- package/esm/csi.js.map +1 -1
- package/esm/htsget.d.ts +1 -2
- package/esm/htsget.js +21 -11
- package/esm/htsget.js.map +1 -1
- package/esm/indexFile.d.ts +1 -1
- package/esm/indexFile.js +2 -0
- package/esm/indexFile.js.map +1 -1
- package/esm/record.d.ts +5 -5
- package/esm/record.js +44 -37
- package/esm/record.js.map +1 -1
- package/esm/util.d.ts +4 -2
- package/esm/util.js +20 -1
- package/esm/util.js.map +1 -1
- package/esm/virtualOffset.d.ts +1 -1
- package/esm/virtualOffset.js +2 -0
- package/esm/virtualOffset.js.map +1 -1
- package/package.json +6 -6
- package/src/bai.ts +11 -8
- package/src/bamFile.ts +22 -41
- package/src/chunk.ts +1 -1
- package/src/csi.ts +22 -19
- package/src/htsget.ts +18 -9
- package/src/indexFile.ts +1 -1
- package/src/record.ts +44 -43
- package/src/util.ts +23 -3
- package/src/virtualOffset.ts +1 -1
package/src/bamFile.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Buffer } from 'buffer'
|
|
2
1
|
import crc32 from 'crc/crc32'
|
|
3
2
|
import { unzip, unzipChunkSlice } from '@gmod/bgzf-filehandle'
|
|
4
|
-
import { LocalFile, RemoteFile, GenericFilehandle } from 'generic-
|
|
3
|
+
import { LocalFile, RemoteFile, GenericFilehandle } from 'generic-filehandle2'
|
|
5
4
|
import AbortablePromiseCache from '@gmod/abortable-promise-cache'
|
|
6
5
|
import QuickLRU from 'quick-lru'
|
|
7
6
|
|
|
@@ -148,23 +147,21 @@ export default class BamFile {
|
|
|
148
147
|
let buffer
|
|
149
148
|
if (ret) {
|
|
150
149
|
const s = ret + blockLen
|
|
151
|
-
|
|
152
|
-
if (!res.bytesRead) {
|
|
153
|
-
throw new Error('Error reading header')
|
|
154
|
-
}
|
|
155
|
-
buffer = res.buffer.subarray(0, Math.min(res.bytesRead, ret))
|
|
150
|
+
buffer = await this.bam.read(s, 0)
|
|
156
151
|
} else {
|
|
157
152
|
buffer = await this.bam.readFile(opts)
|
|
158
153
|
}
|
|
159
154
|
|
|
160
155
|
const uncba = await unzip(buffer)
|
|
156
|
+
const dataView = new DataView(uncba.buffer)
|
|
161
157
|
|
|
162
|
-
if (
|
|
158
|
+
if (dataView.getInt32(0, true) !== BAM_MAGIC) {
|
|
163
159
|
throw new Error('Not a BAM file')
|
|
164
160
|
}
|
|
165
|
-
const headLen =
|
|
161
|
+
const headLen = dataView.getInt32(4, true)
|
|
166
162
|
|
|
167
|
-
|
|
163
|
+
const decoder = new TextDecoder('utf8')
|
|
164
|
+
this.header = decoder.decode(uncba.subarray(8, 8 + headLen))
|
|
168
165
|
const { chrToIndex, indexToChr } = await this._readRefSeqs(
|
|
169
166
|
headLen + 8,
|
|
170
167
|
65535,
|
|
@@ -204,30 +201,21 @@ export default class BamFile {
|
|
|
204
201
|
if (start > refSeqBytes) {
|
|
205
202
|
return this._readRefSeqs(start, refSeqBytes * 2, opts)
|
|
206
203
|
}
|
|
207
|
-
const size = refSeqBytes + blockLen
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
0,
|
|
213
|
-
opts,
|
|
214
|
-
)
|
|
215
|
-
if (!bytesRead) {
|
|
216
|
-
throw new Error('Error reading refseqs from header')
|
|
217
|
-
}
|
|
218
|
-
const uncba = await unzip(
|
|
219
|
-
buffer.subarray(0, Math.min(bytesRead, refSeqBytes)),
|
|
220
|
-
)
|
|
221
|
-
const nRef = uncba.readInt32LE(start)
|
|
204
|
+
// const size = refSeqBytes + blockLen <-- use this?
|
|
205
|
+
const buffer = await this.bam.read(refSeqBytes, 0, opts)
|
|
206
|
+
const uncba = await unzip(buffer)
|
|
207
|
+
const dataView = new DataView(uncba.buffer)
|
|
208
|
+
const nRef = dataView.getInt32(start, true)
|
|
222
209
|
let p = start + 4
|
|
223
210
|
const chrToIndex: Record<string, number> = {}
|
|
224
211
|
const indexToChr: { refName: string; length: number }[] = []
|
|
212
|
+
const decoder = new TextDecoder('utf8')
|
|
225
213
|
for (let i = 0; i < nRef; i += 1) {
|
|
226
|
-
const lName =
|
|
214
|
+
const lName = dataView.getInt32(p, true)
|
|
227
215
|
const refName = this.renameRefSeq(
|
|
228
|
-
uncba.
|
|
216
|
+
decoder.decode(uncba.subarray(p + 4, p + 4 + lName - 1)),
|
|
229
217
|
)
|
|
230
|
-
const lRef =
|
|
218
|
+
const lRef = dataView.getInt32(p + lName + 4, true)
|
|
231
219
|
|
|
232
220
|
chrToIndex[refName] = i
|
|
233
221
|
indexToChr.push({ refName, length: lRef })
|
|
@@ -388,15 +376,7 @@ export default class BamFile {
|
|
|
388
376
|
}
|
|
389
377
|
|
|
390
378
|
async _readRegion(position: number, size: number, opts: BaseOpts = {}) {
|
|
391
|
-
|
|
392
|
-
Buffer.alloc(size),
|
|
393
|
-
0,
|
|
394
|
-
size,
|
|
395
|
-
position,
|
|
396
|
-
opts,
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
return buffer.subarray(0, Math.min(bytesRead, size))
|
|
379
|
+
return this.bam.read(size, position, opts)
|
|
400
380
|
}
|
|
401
381
|
|
|
402
382
|
async _readChunk({ chunk, opts }: { chunk: Chunk; opts: BaseOpts }) {
|
|
@@ -415,7 +395,7 @@ export default class BamFile {
|
|
|
415
395
|
}
|
|
416
396
|
|
|
417
397
|
async readBamFeatures(
|
|
418
|
-
ba:
|
|
398
|
+
ba: Uint8Array,
|
|
419
399
|
cpositions: number[],
|
|
420
400
|
dpositions: number[],
|
|
421
401
|
chunk: Chunk,
|
|
@@ -425,8 +405,9 @@ export default class BamFile {
|
|
|
425
405
|
let pos = 0
|
|
426
406
|
let last = +Date.now()
|
|
427
407
|
|
|
408
|
+
const dataView = new DataView(ba.buffer)
|
|
428
409
|
while (blockStart + 4 < ba.length) {
|
|
429
|
-
const blockSize =
|
|
410
|
+
const blockSize = dataView.getInt32(blockStart, true)
|
|
430
411
|
const blockEnd = blockStart + 4 + blockSize - 1
|
|
431
412
|
|
|
432
413
|
// increment position to the current decompressed status
|
|
@@ -471,8 +452,8 @@ export default class BamFile {
|
|
|
471
452
|
chunk.minv.dataPosition +
|
|
472
453
|
1
|
|
473
454
|
: // must be slice, not subarray for buffer polyfill on web
|
|
474
|
-
//
|
|
475
|
-
crc32.signed(ba.
|
|
455
|
+
// @ts-expect-error
|
|
456
|
+
crc32.signed(ba.subarray(blockStart, blockEnd)),
|
|
476
457
|
})
|
|
477
458
|
|
|
478
459
|
sink.push(feature)
|
package/src/chunk.ts
CHANGED
package/src/csi.ts
CHANGED
|
@@ -37,8 +37,9 @@ export default class CSI extends IndexFile {
|
|
|
37
37
|
return []
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
parseAuxData(bytes:
|
|
41
|
-
const
|
|
40
|
+
parseAuxData(bytes: Uint8Array, offset: number) {
|
|
41
|
+
const dataView = new DataView(bytes.buffer)
|
|
42
|
+
const formatFlags = dataView.getUint32(offset, true)
|
|
42
43
|
const coordinateType =
|
|
43
44
|
formatFlags & 0x10000 ? 'zero-based-half-open' : '1-based-closed'
|
|
44
45
|
const format = (
|
|
@@ -48,14 +49,14 @@ export default class CSI extends IndexFile {
|
|
|
48
49
|
throw new Error(`invalid Tabix preset format flags ${formatFlags}`)
|
|
49
50
|
}
|
|
50
51
|
const columnNumbers = {
|
|
51
|
-
ref:
|
|
52
|
-
start:
|
|
53
|
-
end:
|
|
52
|
+
ref: dataView.getInt32(offset + 4, true),
|
|
53
|
+
start: dataView.getInt32(offset + 8, true),
|
|
54
|
+
end: dataView.getInt32(offset + 12, true),
|
|
54
55
|
}
|
|
55
|
-
const metaValue =
|
|
56
|
+
const metaValue = dataView.getInt32(offset + 16, true)
|
|
56
57
|
const metaChar = metaValue ? String.fromCharCode(metaValue) : ''
|
|
57
|
-
const skipLines =
|
|
58
|
-
const nameSectionLength =
|
|
58
|
+
const skipLines = dataView.getInt32(offset + 20, true)
|
|
59
|
+
const nameSectionLength = dataView.getInt32(offset + 24, true)
|
|
59
60
|
|
|
60
61
|
return {
|
|
61
62
|
columnNumbers,
|
|
@@ -77,23 +78,25 @@ export default class CSI extends IndexFile {
|
|
|
77
78
|
const buffer = await this.filehandle.readFile(opts)
|
|
78
79
|
const bytes = await unzip(buffer)
|
|
79
80
|
|
|
81
|
+
const dataView = new DataView(bytes.buffer)
|
|
80
82
|
let csiVersion
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
const magic = dataView.getUint32(0, true)
|
|
84
|
+
|
|
85
|
+
if (magic === CSI1_MAGIC) {
|
|
83
86
|
csiVersion = 1
|
|
84
|
-
} else if (
|
|
87
|
+
} else if (magic === CSI2_MAGIC) {
|
|
85
88
|
csiVersion = 2
|
|
86
89
|
} else {
|
|
87
|
-
throw new Error(
|
|
90
|
+
throw new Error(`Not a CSI file ${magic}`)
|
|
88
91
|
// TODO: do we need to support big-endian CSI files?
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
this.minShift =
|
|
92
|
-
this.depth =
|
|
94
|
+
this.minShift = dataView.getInt32(4, true)
|
|
95
|
+
this.depth = dataView.getInt32(8, true)
|
|
93
96
|
this.maxBinNumber = ((1 << ((this.depth + 1) * 3)) - 1) / 7
|
|
94
|
-
const auxLength =
|
|
97
|
+
const auxLength = dataView.getInt32(12, true)
|
|
95
98
|
const aux = auxLength >= 30 ? this.parseAuxData(bytes, 16) : undefined
|
|
96
|
-
const refCount =
|
|
99
|
+
const refCount = dataView.getInt32(16 + auxLength, true)
|
|
97
100
|
|
|
98
101
|
type BinIndex = Record<string, Chunk[]>
|
|
99
102
|
|
|
@@ -106,12 +109,12 @@ export default class CSI extends IndexFile {
|
|
|
106
109
|
}>(refCount)
|
|
107
110
|
for (let i = 0; i < refCount; i++) {
|
|
108
111
|
// the binning index
|
|
109
|
-
const binCount =
|
|
112
|
+
const binCount = dataView.getInt32(curr, true)
|
|
110
113
|
curr += 4
|
|
111
114
|
const binIndex: Record<string, Chunk[]> = {}
|
|
112
115
|
let stats // < provided by parsing a pseudo-bin, if present
|
|
113
116
|
for (let j = 0; j < binCount; j++) {
|
|
114
|
-
const bin =
|
|
117
|
+
const bin = dataView.getUint32(curr, true)
|
|
115
118
|
curr += 4
|
|
116
119
|
if (bin > this.maxBinNumber) {
|
|
117
120
|
stats = parsePseudoBin(bytes, curr + 28)
|
|
@@ -119,7 +122,7 @@ export default class CSI extends IndexFile {
|
|
|
119
122
|
} else {
|
|
120
123
|
firstDataLine = findFirstData(firstDataLine, fromBytes(bytes, curr))
|
|
121
124
|
curr += 8
|
|
122
|
-
const chunkCount =
|
|
125
|
+
const chunkCount = dataView.getInt32(curr, true)
|
|
123
126
|
curr += 4
|
|
124
127
|
const chunks = new Array<Chunk>(chunkCount)
|
|
125
128
|
for (let k = 0; k < chunkCount; k += 1) {
|
package/src/htsget.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { unzip } from '@gmod/bgzf-filehandle'
|
|
2
|
-
import {
|
|
3
|
-
import { BaseOpts, BamOpts } from './util'
|
|
2
|
+
import { BaseOpts, BamOpts, concatUint8Array } from './util'
|
|
4
3
|
import BamFile, { BAM_MAGIC } from './bamFile'
|
|
5
4
|
import Chunk from './chunk'
|
|
6
5
|
import { parseHeaderText } from './sam'
|
|
@@ -14,7 +13,8 @@ async function concat(arr: HtsgetChunk[], opts?: Record<string, any>) {
|
|
|
14
13
|
arr.map(async chunk => {
|
|
15
14
|
const { url, headers } = chunk
|
|
16
15
|
if (url.startsWith('data:')) {
|
|
17
|
-
|
|
16
|
+
// @ts-expect-error
|
|
17
|
+
return Uint8Array.fromBase64(url.split(',')[1], 'base64') as Uint8Array
|
|
18
18
|
} else {
|
|
19
19
|
//remove referer header, it is not even allowed to be specified
|
|
20
20
|
// @ts-expect-error
|
|
@@ -29,12 +29,12 @@ async function concat(arr: HtsgetChunk[], opts?: Record<string, any>) {
|
|
|
29
29
|
`HTTP ${res.status} fetching ${url}: ${await res.text()}`,
|
|
30
30
|
)
|
|
31
31
|
}
|
|
32
|
-
return
|
|
32
|
+
return new Uint8Array(await res.arrayBuffer())
|
|
33
33
|
}
|
|
34
34
|
}),
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
return
|
|
37
|
+
return concatUint8Array(await Promise.all(res.map(elt => unzip(elt))))
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export default class HtsgetFile extends BamFile {
|
|
@@ -108,11 +108,17 @@ export default class HtsgetFile extends BamFile {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// @ts-expect-error
|
|
111
112
|
async _readChunk({ chunk }: { chunk: Chunk; opts: BaseOpts }) {
|
|
112
113
|
if (!chunk.buffer) {
|
|
113
114
|
throw new Error('expected chunk.buffer in htsget')
|
|
114
115
|
}
|
|
115
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
data: chunk.buffer,
|
|
118
|
+
cpositions: [],
|
|
119
|
+
dpositions: [],
|
|
120
|
+
chunk,
|
|
121
|
+
}
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
async getHeader(opts: BaseOpts = {}) {
|
|
@@ -125,12 +131,15 @@ export default class HtsgetFile extends BamFile {
|
|
|
125
131
|
}
|
|
126
132
|
const data = await result.json()
|
|
127
133
|
const uncba = await concat(data.htsget.urls, opts)
|
|
134
|
+
const dataView = new DataView(uncba.buffer)
|
|
128
135
|
|
|
129
|
-
if (
|
|
136
|
+
if (dataView.getInt32(0, true) !== BAM_MAGIC) {
|
|
130
137
|
throw new Error('Not a BAM file')
|
|
131
138
|
}
|
|
132
|
-
const headLen =
|
|
133
|
-
|
|
139
|
+
const headLen = dataView.getInt32(4, true)
|
|
140
|
+
|
|
141
|
+
const decoder = new TextDecoder('utf8')
|
|
142
|
+
const headerText = decoder.decode(uncba.subarray(8, 8 + headLen))
|
|
134
143
|
const samHeader = parseHeaderText(headerText)
|
|
135
144
|
|
|
136
145
|
// use the @SQ lines in the header to figure out the
|
package/src/indexFile.ts
CHANGED
package/src/record.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import Constants from './constants'
|
|
2
|
-
import type { Buffer } from 'buffer'
|
|
3
2
|
|
|
4
3
|
const SEQRET_DECODER = '=ACMGRSVTWYHKDBN'.split('')
|
|
5
4
|
const CIGAR_DECODER = 'MIDNSHP=X???????'.split('')
|
|
@@ -7,15 +6,18 @@ const CIGAR_DECODER = 'MIDNSHP=X???????'.split('')
|
|
|
7
6
|
interface Bytes {
|
|
8
7
|
start: number
|
|
9
8
|
end: number
|
|
10
|
-
byteArray:
|
|
9
|
+
byteArray: Uint8Array
|
|
11
10
|
}
|
|
11
|
+
|
|
12
12
|
export default class BamRecord {
|
|
13
13
|
public fileOffset: number
|
|
14
14
|
private bytes: Bytes
|
|
15
|
+
#dataView: DataView
|
|
15
16
|
|
|
16
17
|
constructor(args: { bytes: Bytes; fileOffset: number }) {
|
|
17
18
|
this.bytes = args.bytes
|
|
18
19
|
this.fileOffset = args.fileOffset
|
|
20
|
+
this.#dataView = new DataView(this.bytes.byteArray.buffer)
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
get byteArray() {
|
|
@@ -24,15 +26,15 @@ export default class BamRecord {
|
|
|
24
26
|
|
|
25
27
|
get flags() {
|
|
26
28
|
return (
|
|
27
|
-
(this.
|
|
29
|
+
(this.#dataView.getInt32(this.bytes.start + 16, true) & 0xffff0000) >> 16
|
|
28
30
|
)
|
|
29
31
|
}
|
|
30
32
|
get ref_id() {
|
|
31
|
-
return this.
|
|
33
|
+
return this.#dataView.getInt32(this.bytes.start + 4, true)
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
get start() {
|
|
35
|
-
return this.
|
|
37
|
+
return this.#dataView.getInt32(this.bytes.start + 8, true)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
get end() {
|
|
@@ -73,15 +75,14 @@ export default class BamRecord {
|
|
|
73
75
|
return this.bytes.start + 36
|
|
74
76
|
}
|
|
75
77
|
get name() {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
this.b0
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
let str = ''
|
|
79
|
+
for (let i = 0; i < this.read_name_length - 1; i++) {
|
|
80
|
+
str += String.fromCharCode(this.byteArray[this.b0 + i])
|
|
81
|
+
}
|
|
82
|
+
return str
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
get tags() {
|
|
84
|
-
const { byteArray } = this.bytes
|
|
85
86
|
let p =
|
|
86
87
|
this.b0 +
|
|
87
88
|
this.read_name_length +
|
|
@@ -92,38 +93,38 @@ export default class BamRecord {
|
|
|
92
93
|
const blockEnd = this.bytes.end
|
|
93
94
|
const tags = {} as Record<string, unknown>
|
|
94
95
|
while (p < blockEnd) {
|
|
95
|
-
const tag = String.fromCharCode(byteArray[p], byteArray[p + 1])
|
|
96
|
-
const type = String.fromCharCode(byteArray[p + 2])
|
|
96
|
+
const tag = String.fromCharCode(this.byteArray[p], this.byteArray[p + 1])
|
|
97
|
+
const type = String.fromCharCode(this.byteArray[p + 2])
|
|
97
98
|
p += 3
|
|
98
99
|
|
|
99
100
|
if (type === 'A') {
|
|
100
|
-
tags[tag] = String.fromCharCode(byteArray[p])
|
|
101
|
+
tags[tag] = String.fromCharCode(this.byteArray[p])
|
|
101
102
|
p += 1
|
|
102
103
|
} else if (type === 'i') {
|
|
103
|
-
tags[tag] =
|
|
104
|
+
tags[tag] = this.#dataView.getInt32(p, true)
|
|
104
105
|
p += 4
|
|
105
106
|
} else if (type === 'I') {
|
|
106
|
-
tags[tag] =
|
|
107
|
+
tags[tag] = this.#dataView.getUint32(p, true)
|
|
107
108
|
p += 4
|
|
108
109
|
} else if (type === 'c') {
|
|
109
|
-
tags[tag] =
|
|
110
|
+
tags[tag] = this.#dataView.getInt8(p)
|
|
110
111
|
p += 1
|
|
111
112
|
} else if (type === 'C') {
|
|
112
|
-
tags[tag] =
|
|
113
|
+
tags[tag] = this.#dataView.getUint8(p)
|
|
113
114
|
p += 1
|
|
114
115
|
} else if (type === 's') {
|
|
115
|
-
tags[tag] =
|
|
116
|
+
tags[tag] = this.#dataView.getInt16(p, true)
|
|
116
117
|
p += 2
|
|
117
118
|
} else if (type === 'S') {
|
|
118
|
-
tags[tag] =
|
|
119
|
+
tags[tag] = this.#dataView.getUint16(p, true)
|
|
119
120
|
p += 2
|
|
120
121
|
} else if (type === 'f') {
|
|
121
|
-
tags[tag] =
|
|
122
|
+
tags[tag] = this.#dataView.getFloat32(p, true)
|
|
122
123
|
p += 4
|
|
123
124
|
} else if (type === 'Z' || type === 'H') {
|
|
124
125
|
const value = []
|
|
125
126
|
while (p <= blockEnd) {
|
|
126
|
-
const cc = byteArray[p++]
|
|
127
|
+
const cc = this.byteArray[p++]
|
|
127
128
|
if (cc !== 0) {
|
|
128
129
|
value.push(String.fromCharCode(cc))
|
|
129
130
|
} else {
|
|
@@ -132,15 +133,15 @@ export default class BamRecord {
|
|
|
132
133
|
}
|
|
133
134
|
tags[tag] = value.join('')
|
|
134
135
|
} else if (type === 'B') {
|
|
135
|
-
const cc = byteArray[p++]
|
|
136
|
+
const cc = this.byteArray[p++]
|
|
136
137
|
const Btype = String.fromCharCode(cc)
|
|
137
|
-
const limit =
|
|
138
|
+
const limit = this.#dataView.getInt32(p, true)
|
|
138
139
|
p += 4
|
|
139
140
|
if (Btype === 'i') {
|
|
140
141
|
if (tag === 'CG') {
|
|
141
142
|
const value = []
|
|
142
143
|
for (let k = 0; k < limit; k++) {
|
|
143
|
-
const cigop =
|
|
144
|
+
const cigop = this.#dataView.getInt32(p, true)
|
|
144
145
|
const lop = cigop >> 4
|
|
145
146
|
const op = CIGAR_DECODER[cigop & 0xf]
|
|
146
147
|
value.push(lop + op)
|
|
@@ -150,7 +151,7 @@ export default class BamRecord {
|
|
|
150
151
|
} else {
|
|
151
152
|
const value = []
|
|
152
153
|
for (let k = 0; k < limit; k++) {
|
|
153
|
-
value.push(
|
|
154
|
+
value.push(this.#dataView.getInt32(p, true))
|
|
154
155
|
p += 4
|
|
155
156
|
}
|
|
156
157
|
tags[tag] = value
|
|
@@ -159,7 +160,7 @@ export default class BamRecord {
|
|
|
159
160
|
if (tag === 'CG') {
|
|
160
161
|
const value = []
|
|
161
162
|
for (let k = 0; k < limit; k++) {
|
|
162
|
-
const cigop =
|
|
163
|
+
const cigop = this.#dataView.getUint32(p, true)
|
|
163
164
|
const lop = cigop >> 4
|
|
164
165
|
const op = CIGAR_DECODER[cigop & 0xf]
|
|
165
166
|
value.push(lop + op)
|
|
@@ -169,7 +170,7 @@ export default class BamRecord {
|
|
|
169
170
|
} else {
|
|
170
171
|
const value = []
|
|
171
172
|
for (let k = 0; k < limit; k++) {
|
|
172
|
-
value.push(
|
|
173
|
+
value.push(this.#dataView.getUint32(p, true))
|
|
173
174
|
p += 4
|
|
174
175
|
}
|
|
175
176
|
tags[tag] = value
|
|
@@ -177,35 +178,35 @@ export default class BamRecord {
|
|
|
177
178
|
} else if (Btype === 's') {
|
|
178
179
|
const value = []
|
|
179
180
|
for (let k = 0; k < limit; k++) {
|
|
180
|
-
value.push(
|
|
181
|
+
value.push(this.#dataView.getInt16(p, true))
|
|
181
182
|
p += 2
|
|
182
183
|
}
|
|
183
184
|
tags[tag] = value
|
|
184
185
|
} else if (Btype === 'S') {
|
|
185
186
|
const value = []
|
|
186
187
|
for (let k = 0; k < limit; k++) {
|
|
187
|
-
value.push(
|
|
188
|
+
value.push(this.#dataView.getUint16(p, true))
|
|
188
189
|
p += 2
|
|
189
190
|
}
|
|
190
191
|
tags[tag] = value
|
|
191
192
|
} else if (Btype === 'c') {
|
|
192
193
|
const value = []
|
|
193
194
|
for (let k = 0; k < limit; k++) {
|
|
194
|
-
value.push(
|
|
195
|
+
value.push(this.#dataView.getInt8(p))
|
|
195
196
|
p += 1
|
|
196
197
|
}
|
|
197
198
|
tags[tag] = value
|
|
198
199
|
} else if (Btype === 'C') {
|
|
199
200
|
const value = []
|
|
200
201
|
for (let k = 0; k < limit; k++) {
|
|
201
|
-
value.push(
|
|
202
|
+
value.push(this.#dataView.getUint8(p))
|
|
202
203
|
p += 1
|
|
203
204
|
}
|
|
204
205
|
tags[tag] = value
|
|
205
206
|
} else if (Btype === 'f') {
|
|
206
207
|
const value = []
|
|
207
208
|
for (let k = 0; k < limit; k++) {
|
|
208
|
-
value.push(
|
|
209
|
+
value.push(this.#dataView.getFloat32(p, true))
|
|
209
210
|
p += 4
|
|
210
211
|
}
|
|
211
212
|
tags[tag] = value
|
|
@@ -295,14 +296,14 @@ export default class BamRecord {
|
|
|
295
296
|
|
|
296
297
|
// check for CG tag by inspecting whether the CIGAR field contains a clip
|
|
297
298
|
// that consumes entire seqLen
|
|
298
|
-
let cigop = this.
|
|
299
|
+
let cigop = this.#dataView.getInt32(p, true)
|
|
299
300
|
let lop = cigop >> 4
|
|
300
301
|
let op = CIGAR_DECODER[cigop & 0xf]
|
|
301
302
|
if (op === 'S' && lop === this.seq_length) {
|
|
302
303
|
// if there is a CG the second CIGAR field will be a N tag the represents
|
|
303
304
|
// the length on ref
|
|
304
305
|
p += 4
|
|
305
|
-
cigop = this.
|
|
306
|
+
cigop = this.#dataView.getInt32(p, true)
|
|
306
307
|
lop = cigop >> 4
|
|
307
308
|
op = CIGAR_DECODER[cigop & 0xf]
|
|
308
309
|
if (op !== 'N') {
|
|
@@ -315,7 +316,7 @@ export default class BamRecord {
|
|
|
315
316
|
} else {
|
|
316
317
|
let lref = 0
|
|
317
318
|
for (let c = 0; c < numCigarOps; ++c) {
|
|
318
|
-
cigop = this.
|
|
319
|
+
cigop = this.#dataView.getInt32(p, true)
|
|
319
320
|
lop = cigop >> 4
|
|
320
321
|
op = CIGAR_DECODER[cigop & 0xf]
|
|
321
322
|
CIGAR.push(lop + op)
|
|
@@ -408,31 +409,31 @@ export default class BamRecord {
|
|
|
408
409
|
}
|
|
409
410
|
return tmp.join('')
|
|
410
411
|
}
|
|
411
|
-
return
|
|
412
|
+
return undefined
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
get bin_mq_nl() {
|
|
415
|
-
return this.
|
|
416
|
+
return this.#dataView.getInt32(this.bytes.start + 12, true)
|
|
416
417
|
}
|
|
417
418
|
|
|
418
419
|
get flag_nc() {
|
|
419
|
-
return this.
|
|
420
|
+
return this.#dataView.getInt32(this.bytes.start + 16, true)
|
|
420
421
|
}
|
|
421
422
|
|
|
422
423
|
get seq_length() {
|
|
423
|
-
return this.
|
|
424
|
+
return this.#dataView.getInt32(this.bytes.start + 20, true)
|
|
424
425
|
}
|
|
425
426
|
|
|
426
427
|
get next_refid() {
|
|
427
|
-
return this.
|
|
428
|
+
return this.#dataView.getInt32(this.bytes.start + 24, true)
|
|
428
429
|
}
|
|
429
430
|
|
|
430
431
|
get next_pos() {
|
|
431
|
-
return this.
|
|
432
|
+
return this.#dataView.getInt32(this.bytes.start + 28, true)
|
|
432
433
|
}
|
|
433
434
|
|
|
434
435
|
get template_length() {
|
|
435
|
-
return this.
|
|
436
|
+
return this.#dataView.getInt32(this.bytes.start + 32, true)
|
|
436
437
|
}
|
|
437
438
|
|
|
438
439
|
toJSON() {
|
package/src/util.ts
CHANGED
|
@@ -102,7 +102,7 @@ export function optimizeChunks(chunks: Chunk[], lowest?: VirtualOffset) {
|
|
|
102
102
|
return mergedChunks
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
export function parsePseudoBin(bytes:
|
|
105
|
+
export function parsePseudoBin(bytes: Uint8Array, offset: number) {
|
|
106
106
|
return {
|
|
107
107
|
lineCount: Long.fromBytesLE(
|
|
108
108
|
Array.prototype.slice.call(bytes, offset, offset + 8),
|
|
@@ -123,7 +123,7 @@ export function findFirstData(
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
export function parseNameBytes(
|
|
126
|
-
namesBytes:
|
|
126
|
+
namesBytes: Uint8Array,
|
|
127
127
|
renameRefSeq: (arg: string) => string = s => s,
|
|
128
128
|
) {
|
|
129
129
|
let currRefId = 0
|
|
@@ -133,7 +133,10 @@ export function parseNameBytes(
|
|
|
133
133
|
for (let i = 0; i < namesBytes.length; i += 1) {
|
|
134
134
|
if (!namesBytes[i]) {
|
|
135
135
|
if (currNameStart < i) {
|
|
136
|
-
let refName =
|
|
136
|
+
let refName = ''
|
|
137
|
+
for (let j = currNameStart; j < i; j++) {
|
|
138
|
+
refName += String.fromCharCode(namesBytes[j])
|
|
139
|
+
}
|
|
137
140
|
refName = renameRefSeq(refName)
|
|
138
141
|
refIdToName[currRefId] = refName
|
|
139
142
|
refNameToId[refName] = currRefId
|
|
@@ -144,3 +147,20 @@ export function parseNameBytes(
|
|
|
144
147
|
}
|
|
145
148
|
return { refNameToId, refIdToName }
|
|
146
149
|
}
|
|
150
|
+
|
|
151
|
+
export function sum(array: Uint8Array[]) {
|
|
152
|
+
let sum = 0
|
|
153
|
+
for (const entry of array) {
|
|
154
|
+
sum += entry.length
|
|
155
|
+
}
|
|
156
|
+
return sum
|
|
157
|
+
}
|
|
158
|
+
export function concatUint8Array(args: Uint8Array[]) {
|
|
159
|
+
const mergedArray = new Uint8Array(sum(args))
|
|
160
|
+
let offset = 0
|
|
161
|
+
for (const entry of args) {
|
|
162
|
+
mergedArray.set(entry, offset)
|
|
163
|
+
offset += entry.length
|
|
164
|
+
}
|
|
165
|
+
return mergedArray
|
|
166
|
+
}
|
package/src/virtualOffset.ts
CHANGED
|
@@ -30,7 +30,7 @@ export default class VirtualOffset {
|
|
|
30
30
|
return min
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
export function fromBytes(bytes:
|
|
33
|
+
export function fromBytes(bytes: Uint8Array, offset = 0, bigendian = false) {
|
|
34
34
|
if (bigendian) {
|
|
35
35
|
throw new Error('big-endian virtual file offsets not implemented')
|
|
36
36
|
}
|