@gmod/bbi 8.1.1 → 9.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 -0
- package/README.md +49 -53
- package/dist/array-feature-view.d.ts +4 -1
- package/dist/array-feature-view.js.map +1 -1
- package/dist/bbi.d.ts +5 -25
- package/dist/bbi.js +31 -133
- package/dist/bbi.js.map +1 -1
- package/dist/bigbed.d.ts +0 -1
- package/dist/bigbed.js +19 -51
- package/dist/bigbed.js.map +1 -1
- package/dist/bigwig.d.ts +1 -2
- package/dist/bigwig.js +1 -2
- package/dist/bigwig.js.map +1 -1
- package/dist/block-view.d.ts +7 -16
- package/dist/block-view.js +370 -440
- package/dist/block-view.js.map +1 -1
- package/dist/parse-bigwig.d.ts +2 -2
- package/dist/parse-bigwig.js +5 -5
- package/dist/parse-bigwig.js.map +1 -1
- package/dist/range.d.ts +1 -15
- package/dist/range.js +16 -49
- package/dist/range.js.map +1 -1
- package/dist/util.d.ts +0 -22
- package/dist/util.js +0 -46
- package/dist/util.js.map +1 -1
- package/dist/wasm/inflate-wasm-inlined.js +85 -86
- package/dist/wasm/inflate-wasm-inlined.js.map +1 -1
- package/dist/wasm/inflate_wasm.d.ts +1 -1
- package/dist/wasm/inflate_wasm.js +10 -4
- package/dist/wasm/inflate_wasm.js.map +1 -1
- package/dist/wasm/inflate_wasm_bg.d.ts +2 -2
- package/dist/wasm/inflate_wasm_bg.js +85 -86
- package/dist/wasm/inflate_wasm_bg.js.map +1 -1
- package/esm/array-feature-view.d.ts +4 -1
- package/esm/array-feature-view.js.map +1 -1
- package/esm/bbi.d.ts +5 -25
- package/esm/bbi.js +31 -133
- package/esm/bbi.js.map +1 -1
- package/esm/bigbed.d.ts +0 -1
- package/esm/bigbed.js +19 -50
- package/esm/bigbed.js.map +1 -1
- package/esm/bigwig.d.ts +1 -2
- package/esm/bigwig.js +1 -2
- package/esm/bigwig.js.map +1 -1
- package/esm/block-view.d.ts +7 -16
- package/esm/block-view.js +370 -440
- package/esm/block-view.js.map +1 -1
- package/esm/parse-bigwig.d.ts +2 -2
- package/esm/parse-bigwig.js +5 -5
- package/esm/parse-bigwig.js.map +1 -1
- package/esm/range.d.ts +1 -15
- package/esm/range.js +15 -48
- package/esm/range.js.map +1 -1
- package/esm/util.d.ts +0 -22
- package/esm/util.js +0 -42
- package/esm/util.js.map +1 -1
- package/esm/wasm/inflate-wasm-inlined.js +85 -86
- package/esm/wasm/inflate-wasm-inlined.js.map +1 -1
- package/esm/wasm/inflate_wasm.d.ts +1 -1
- package/esm/wasm/inflate_wasm.js +2 -1
- package/esm/wasm/inflate_wasm.js.map +1 -1
- package/esm/wasm/inflate_wasm_bg.d.ts +2 -2
- package/esm/wasm/inflate_wasm_bg.js +83 -84
- package/esm/wasm/inflate_wasm_bg.js.map +1 -1
- package/package.json +14 -16
- package/src/array-feature-view.ts +8 -0
- package/src/bbi.ts +50 -153
- package/src/bigbed.ts +22 -55
- package/src/bigwig.ts +1 -3
- package/src/block-view.ts +526 -634
- package/src/parse-bigwig.ts +7 -9
- package/src/range.ts +19 -58
- package/src/util.ts +0 -46
- package/src/wasm/inflate-wasm-inlined.js +101 -102
- package/src/wasm/inflate_wasm.js +7 -2
- package/src/wasm/inflate_wasm_bg.js +99 -100
- package/src/wasm/inflate_wasm_bg.wasm +0 -0
package/src/block-view.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import AbortablePromiseCache from '@gmod/abortable-promise-cache'
|
|
2
2
|
import QuickLRU from '@jbrowse/quick-lru'
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import { mergeRanges } from './range.ts'
|
|
5
5
|
import {
|
|
6
6
|
decompressAndParseBigWigBlocks,
|
|
7
7
|
decompressAndParseSummaryBlocks,
|
|
@@ -12,9 +12,9 @@ import { groupBlocks } from './util.ts'
|
|
|
12
12
|
import type { Feature } from './types.ts'
|
|
13
13
|
import type { BigWigFeatureArrays, SummaryFeatureArrays } from './unzip.ts'
|
|
14
14
|
import type { GenericFilehandle } from 'generic-filehandle2'
|
|
15
|
-
import type { Observer } from 'rxjs'
|
|
16
15
|
|
|
17
16
|
const decoder = new TextDecoder('utf8')
|
|
17
|
+
const CIR_TREE_MAGIC = 0x2468ace0
|
|
18
18
|
|
|
19
19
|
interface CoordRequest {
|
|
20
20
|
chrId: number
|
|
@@ -36,6 +36,268 @@ function coordFilter(s1: number, e1: number, s2: number, e2: number): boolean {
|
|
|
36
36
|
return s1 < e2 && e1 >= s2
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function parseSummaryBlock(
|
|
40
|
+
b: Uint8Array,
|
|
41
|
+
startOffset: number,
|
|
42
|
+
request?: CoordRequest,
|
|
43
|
+
) {
|
|
44
|
+
const features: Feature[] = []
|
|
45
|
+
let offset = startOffset
|
|
46
|
+
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
47
|
+
while (offset < b.byteLength) {
|
|
48
|
+
const chromId = dataView.getUint32(offset, true)
|
|
49
|
+
offset += 4
|
|
50
|
+
const start = dataView.getUint32(offset, true)
|
|
51
|
+
offset += 4
|
|
52
|
+
const end = dataView.getUint32(offset, true)
|
|
53
|
+
offset += 4
|
|
54
|
+
const validCnt = dataView.getUint32(offset, true)
|
|
55
|
+
offset += 4
|
|
56
|
+
const minScore = dataView.getFloat32(offset, true)
|
|
57
|
+
offset += 4
|
|
58
|
+
const maxScore = dataView.getFloat32(offset, true)
|
|
59
|
+
offset += 4
|
|
60
|
+
const sumData = dataView.getFloat32(offset, true)
|
|
61
|
+
offset += 8
|
|
62
|
+
|
|
63
|
+
if (
|
|
64
|
+
!request ||
|
|
65
|
+
(chromId === request.chrId &&
|
|
66
|
+
coordFilter(start, end, request.start, request.end))
|
|
67
|
+
) {
|
|
68
|
+
features.push({
|
|
69
|
+
start,
|
|
70
|
+
end,
|
|
71
|
+
maxScore,
|
|
72
|
+
minScore,
|
|
73
|
+
summary: true,
|
|
74
|
+
score: sumData / (validCnt || 1),
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return features
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseBigBedBlock(
|
|
82
|
+
data: Uint8Array,
|
|
83
|
+
startOffset: number,
|
|
84
|
+
offset: number,
|
|
85
|
+
request?: CoordRequest,
|
|
86
|
+
) {
|
|
87
|
+
const items: Feature[] = []
|
|
88
|
+
let currOffset = startOffset
|
|
89
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.length)
|
|
90
|
+
while (currOffset < data.byteLength) {
|
|
91
|
+
const c2 = currOffset
|
|
92
|
+
const chromId = dataView.getUint32(currOffset, true)
|
|
93
|
+
currOffset += 4
|
|
94
|
+
const start = dataView.getInt32(currOffset, true)
|
|
95
|
+
currOffset += 4
|
|
96
|
+
const end = dataView.getInt32(currOffset, true)
|
|
97
|
+
currOffset += 4
|
|
98
|
+
let i = currOffset
|
|
99
|
+
for (; i < data.length; i++) {
|
|
100
|
+
if (data[i] === 0) {
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const b = data.subarray(currOffset, i)
|
|
105
|
+
const rest = decoder.decode(b)
|
|
106
|
+
currOffset = i + 1
|
|
107
|
+
if (
|
|
108
|
+
!request ||
|
|
109
|
+
(chromId === request.chrId &&
|
|
110
|
+
coordFilter(start, end, request.start, request.end))
|
|
111
|
+
) {
|
|
112
|
+
items.push({
|
|
113
|
+
start,
|
|
114
|
+
end,
|
|
115
|
+
rest,
|
|
116
|
+
uniqueId: `bb-${offset + c2}`,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return items
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function parseBigWigBlock(
|
|
124
|
+
buffer: Uint8Array,
|
|
125
|
+
startOffset: number,
|
|
126
|
+
req?: CoordRequest,
|
|
127
|
+
) {
|
|
128
|
+
const b = buffer.subarray(startOffset)
|
|
129
|
+
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
130
|
+
const blockStart = dataView.getInt32(4, true)
|
|
131
|
+
const itemStep = dataView.getUint32(12, true)
|
|
132
|
+
const itemSpan = dataView.getUint32(16, true)
|
|
133
|
+
const blockType = dataView.getUint8(20)
|
|
134
|
+
const itemCount = dataView.getUint16(22, true)
|
|
135
|
+
let offset = 24
|
|
136
|
+
const items: Feature[] = []
|
|
137
|
+
switch (blockType) {
|
|
138
|
+
case 1: {
|
|
139
|
+
for (let i = 0; i < itemCount; i++) {
|
|
140
|
+
const start = dataView.getInt32(offset, true)
|
|
141
|
+
offset += 4
|
|
142
|
+
const end = dataView.getInt32(offset, true)
|
|
143
|
+
offset += 4
|
|
144
|
+
const score = dataView.getFloat32(offset, true)
|
|
145
|
+
offset += 4
|
|
146
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
147
|
+
items.push({ start, end, score })
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
case 2: {
|
|
153
|
+
for (let i = 0; i < itemCount; i++) {
|
|
154
|
+
const start = dataView.getInt32(offset, true)
|
|
155
|
+
offset += 4
|
|
156
|
+
const score = dataView.getFloat32(offset, true)
|
|
157
|
+
offset += 4
|
|
158
|
+
const end = start + itemSpan
|
|
159
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
160
|
+
items.push({ score, start, end })
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
break
|
|
164
|
+
}
|
|
165
|
+
case 3: {
|
|
166
|
+
for (let i = 0; i < itemCount; i++) {
|
|
167
|
+
const score = dataView.getFloat32(offset, true)
|
|
168
|
+
offset += 4
|
|
169
|
+
const start = blockStart + i * itemStep
|
|
170
|
+
const end = start + itemSpan
|
|
171
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
172
|
+
items.push({ score, start, end })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
break
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return items
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function parseBigWigBlockAsArrays(
|
|
182
|
+
buffer: Uint8Array,
|
|
183
|
+
startOffset: number,
|
|
184
|
+
req?: CoordRequest,
|
|
185
|
+
): { starts: Int32Array; ends: Int32Array; scores: Float32Array } {
|
|
186
|
+
const dataView = new DataView(
|
|
187
|
+
buffer.buffer,
|
|
188
|
+
buffer.byteOffset + startOffset,
|
|
189
|
+
buffer.length - startOffset,
|
|
190
|
+
)
|
|
191
|
+
const blockStart = dataView.getInt32(4, true)
|
|
192
|
+
const itemStep = dataView.getUint32(12, true)
|
|
193
|
+
const itemSpan = dataView.getUint32(16, true)
|
|
194
|
+
const blockType = dataView.getUint8(20)
|
|
195
|
+
const itemCount = dataView.getUint16(22, true)
|
|
196
|
+
|
|
197
|
+
const starts = new Int32Array(itemCount)
|
|
198
|
+
const ends = new Int32Array(itemCount)
|
|
199
|
+
const scores = new Float32Array(itemCount)
|
|
200
|
+
|
|
201
|
+
if (!req) {
|
|
202
|
+
switch (blockType) {
|
|
203
|
+
case 1: {
|
|
204
|
+
let offset = 24
|
|
205
|
+
for (let i = 0; i < itemCount; i++) {
|
|
206
|
+
starts[i] = dataView.getInt32(offset, true)
|
|
207
|
+
ends[i] = dataView.getInt32(offset + 4, true)
|
|
208
|
+
scores[i] = dataView.getFloat32(offset + 8, true)
|
|
209
|
+
offset += 12
|
|
210
|
+
}
|
|
211
|
+
return { starts, ends, scores }
|
|
212
|
+
}
|
|
213
|
+
case 2: {
|
|
214
|
+
let offset = 24
|
|
215
|
+
for (let i = 0; i < itemCount; i++) {
|
|
216
|
+
const start = dataView.getInt32(offset, true)
|
|
217
|
+
starts[i] = start
|
|
218
|
+
ends[i] = start + itemSpan
|
|
219
|
+
scores[i] = dataView.getFloat32(offset + 4, true)
|
|
220
|
+
offset += 8
|
|
221
|
+
}
|
|
222
|
+
return { starts, ends, scores }
|
|
223
|
+
}
|
|
224
|
+
case 3: {
|
|
225
|
+
let offset = 24
|
|
226
|
+
for (let i = 0; i < itemCount; i++) {
|
|
227
|
+
const start = blockStart + i * itemStep
|
|
228
|
+
starts[i] = start
|
|
229
|
+
ends[i] = start + itemSpan
|
|
230
|
+
scores[i] = dataView.getFloat32(offset, true)
|
|
231
|
+
offset += 4
|
|
232
|
+
}
|
|
233
|
+
return { starts, ends, scores }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { starts, ends, scores }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const reqStart = req.start
|
|
240
|
+
const reqEnd = req.end
|
|
241
|
+
let idx = 0
|
|
242
|
+
|
|
243
|
+
switch (blockType) {
|
|
244
|
+
case 1: {
|
|
245
|
+
let offset = 24
|
|
246
|
+
for (let i = 0; i < itemCount; i++) {
|
|
247
|
+
const start = dataView.getInt32(offset, true)
|
|
248
|
+
const end = dataView.getInt32(offset + 4, true)
|
|
249
|
+
if (start < reqEnd && end >= reqStart) {
|
|
250
|
+
starts[idx] = start
|
|
251
|
+
ends[idx] = end
|
|
252
|
+
scores[idx] = dataView.getFloat32(offset + 8, true)
|
|
253
|
+
idx++
|
|
254
|
+
}
|
|
255
|
+
offset += 12
|
|
256
|
+
}
|
|
257
|
+
break
|
|
258
|
+
}
|
|
259
|
+
case 2: {
|
|
260
|
+
let offset = 24
|
|
261
|
+
for (let i = 0; i < itemCount; i++) {
|
|
262
|
+
const start = dataView.getInt32(offset, true)
|
|
263
|
+
const end = start + itemSpan
|
|
264
|
+
if (start < reqEnd && end >= reqStart) {
|
|
265
|
+
starts[idx] = start
|
|
266
|
+
ends[idx] = end
|
|
267
|
+
scores[idx] = dataView.getFloat32(offset + 4, true)
|
|
268
|
+
idx++
|
|
269
|
+
}
|
|
270
|
+
offset += 8
|
|
271
|
+
}
|
|
272
|
+
break
|
|
273
|
+
}
|
|
274
|
+
case 3: {
|
|
275
|
+
let offset = 24
|
|
276
|
+
for (let i = 0; i < itemCount; i++) {
|
|
277
|
+
const start = blockStart + i * itemStep
|
|
278
|
+
const end = start + itemSpan
|
|
279
|
+
if (start < reqEnd && end >= reqStart) {
|
|
280
|
+
starts[idx] = start
|
|
281
|
+
ends[idx] = end
|
|
282
|
+
scores[idx] = dataView.getFloat32(offset, true)
|
|
283
|
+
idx++
|
|
284
|
+
}
|
|
285
|
+
offset += 4
|
|
286
|
+
}
|
|
287
|
+
break
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (idx < itemCount) {
|
|
292
|
+
return {
|
|
293
|
+
starts: starts.subarray(0, idx),
|
|
294
|
+
ends: ends.subarray(0, idx),
|
|
295
|
+
scores: scores.subarray(0, idx),
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { starts, ends, scores }
|
|
299
|
+
}
|
|
300
|
+
|
|
39
301
|
/**
|
|
40
302
|
* View into a subset of the data in a BigWig file.
|
|
41
303
|
*
|
|
@@ -70,574 +332,216 @@ export class BlockView {
|
|
|
70
332
|
}
|
|
71
333
|
}
|
|
72
334
|
|
|
73
|
-
|
|
335
|
+
private async _collectBlocks(
|
|
74
336
|
chrName: string,
|
|
75
337
|
start: number,
|
|
76
338
|
end: number,
|
|
77
|
-
observer: Observer<Feature[]>,
|
|
78
339
|
opts?: Options,
|
|
79
|
-
) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
340
|
+
): Promise<{ blocks: ReadData[]; chrId: number } | undefined> {
|
|
341
|
+
const chrId = this.refsByName[chrName]
|
|
342
|
+
if (chrId === undefined) {
|
|
343
|
+
return undefined
|
|
344
|
+
}
|
|
345
|
+
if (!this.rTreePromise) {
|
|
346
|
+
this.rTreePromise = this.bbi.read(48, this.rTreeOffset, opts)
|
|
347
|
+
}
|
|
348
|
+
const buffer = await this.rTreePromise
|
|
349
|
+
const dataView = new DataView(
|
|
350
|
+
buffer.buffer,
|
|
351
|
+
buffer.byteOffset,
|
|
352
|
+
buffer.length,
|
|
353
|
+
)
|
|
354
|
+
const magic = dataView.getUint32(0, true)
|
|
355
|
+
if (magic !== CIR_TREE_MAGIC) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`invalid cirTree magic: 0x${magic.toString(16)} (expected 0x${CIR_TREE_MAGIC.toString(16)}) at offset ${this.rTreeOffset}, file may be corrupt or unsupported`,
|
|
95
358
|
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
offset: blockOffset,
|
|
126
|
-
length: blockSize,
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// R-tree non-leaf nodes contain pointers to child nodes
|
|
133
|
-
const processNonLeafNode = (
|
|
134
|
-
dataView: DataView,
|
|
135
|
-
startOffset: number,
|
|
136
|
-
count: number,
|
|
137
|
-
level: number,
|
|
138
|
-
) => {
|
|
139
|
-
const recurOffsets = []
|
|
140
|
-
let offset = startOffset
|
|
141
|
-
for (let i = 0; i < count; i++) {
|
|
142
|
-
const startChrom = dataView.getUint32(offset, true)
|
|
143
|
-
offset += 4
|
|
144
|
-
const startBase = dataView.getUint32(offset, true)
|
|
145
|
-
offset += 4
|
|
146
|
-
const endChrom = dataView.getUint32(offset, true)
|
|
147
|
-
offset += 4
|
|
148
|
-
const endBase = dataView.getUint32(offset, true)
|
|
149
|
-
offset += 4
|
|
150
|
-
const blockOffset = Number(dataView.getBigUint64(offset, true))
|
|
151
|
-
offset += 8
|
|
152
|
-
if (
|
|
153
|
-
blockIntersectsQuery({ startChrom, startBase, endChrom, endBase })
|
|
154
|
-
) {
|
|
155
|
-
recurOffsets.push(blockOffset)
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (recurOffsets.length > 0) {
|
|
159
|
-
traverseRTree(recurOffsets, level + 1)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const processRTreeNode = (
|
|
164
|
-
rTreeBlockData: Uint8Array,
|
|
165
|
-
offset2: number,
|
|
166
|
-
level: number,
|
|
167
|
-
) => {
|
|
168
|
-
try {
|
|
169
|
-
const data = rTreeBlockData.subarray(offset2)
|
|
170
|
-
const dataView = new DataView(
|
|
171
|
-
data.buffer,
|
|
172
|
-
data.byteOffset,
|
|
173
|
-
data.length,
|
|
174
|
-
)
|
|
175
|
-
let offset = 0
|
|
176
|
-
|
|
177
|
-
const isLeaf = dataView.getUint8(offset)
|
|
178
|
-
offset += 2 // 1 skip for reserved byte
|
|
179
|
-
const count = dataView.getUint16(offset, true)
|
|
180
|
-
offset += 2
|
|
181
|
-
|
|
182
|
-
if (isLeaf === 1) {
|
|
183
|
-
processLeafNode(dataView, offset, count)
|
|
184
|
-
} else if (isLeaf === 0) {
|
|
185
|
-
processNonLeafNode(dataView, offset, count, level)
|
|
186
|
-
}
|
|
187
|
-
} catch (e) {
|
|
188
|
-
observer.error(e)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const blockIntersectsQuery = (b: {
|
|
193
|
-
startChrom: number
|
|
194
|
-
startBase: number
|
|
195
|
-
endChrom: number
|
|
196
|
-
endBase: number
|
|
197
|
-
}) => {
|
|
198
|
-
const { startChrom, startBase, endChrom, endBase } = b
|
|
199
|
-
return (
|
|
200
|
-
(startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
|
|
201
|
-
(endChrom > chrId || (endChrom === chrId && endBase >= start))
|
|
359
|
+
}
|
|
360
|
+
const rTreeBlockSize = dataView.getUint32(4, true)
|
|
361
|
+
// Upper bound on size, based on a completely full leaf node.
|
|
362
|
+
const maxRTreeBlockSpan = 4 + rTreeBlockSize * 32
|
|
363
|
+
|
|
364
|
+
const blockIntersectsQuery = (
|
|
365
|
+
startChrom: number,
|
|
366
|
+
startBase: number,
|
|
367
|
+
endChrom: number,
|
|
368
|
+
endBase: number,
|
|
369
|
+
) =>
|
|
370
|
+
(startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
|
|
371
|
+
(endChrom > chrId || (endChrom === chrId && endBase >= start))
|
|
372
|
+
|
|
373
|
+
const blocks: ReadData[] = []
|
|
374
|
+
let currentOffsets = [this.rTreeOffset + 48]
|
|
375
|
+
|
|
376
|
+
while (currentOffsets.length > 0) {
|
|
377
|
+
const spans = mergeRanges(
|
|
378
|
+
currentOffsets.map(o => ({ min: o, max: o + maxRTreeBlockSpan })),
|
|
379
|
+
)
|
|
380
|
+
const nextOffsets: number[] = []
|
|
381
|
+
for (const { min, max } of spans) {
|
|
382
|
+
const length = max - min
|
|
383
|
+
const offset = min
|
|
384
|
+
const resultBuffer = await this.featureCache.get(
|
|
385
|
+
`${length}_${offset}`,
|
|
386
|
+
{ length, offset },
|
|
387
|
+
opts?.signal,
|
|
202
388
|
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
389
|
+
for (const element of currentOffsets) {
|
|
390
|
+
if (min <= element && element <= max) {
|
|
391
|
+
const data = resultBuffer.subarray(element - offset)
|
|
392
|
+
const dv = new DataView(data.buffer, data.byteOffset, data.length)
|
|
393
|
+
const isLeaf = dv.getUint8(0)
|
|
394
|
+
const count = dv.getUint16(2, true)
|
|
395
|
+
let nodeOffset = 4
|
|
396
|
+
if (isLeaf === 1) {
|
|
397
|
+
for (let i = 0; i < count; i++) {
|
|
398
|
+
const startChrom = dv.getUint32(nodeOffset, true)
|
|
399
|
+
const startBase = dv.getUint32(nodeOffset + 4, true)
|
|
400
|
+
const endChrom = dv.getUint32(nodeOffset + 8, true)
|
|
401
|
+
const endBase = dv.getUint32(nodeOffset + 12, true)
|
|
402
|
+
const blockOffset = Number(
|
|
403
|
+
dv.getBigUint64(nodeOffset + 16, true),
|
|
404
|
+
)
|
|
405
|
+
const blockSize = Number(dv.getBigUint64(nodeOffset + 24, true))
|
|
406
|
+
nodeOffset += 32
|
|
407
|
+
if (
|
|
408
|
+
blockIntersectsQuery(startChrom, startBase, endChrom, endBase)
|
|
409
|
+
) {
|
|
410
|
+
blocks.push({ offset: blockOffset, length: blockSize })
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
} else if (isLeaf === 0) {
|
|
414
|
+
for (let i = 0; i < count; i++) {
|
|
415
|
+
const startChrom = dv.getUint32(nodeOffset, true)
|
|
416
|
+
const startBase = dv.getUint32(nodeOffset + 4, true)
|
|
417
|
+
const endChrom = dv.getUint32(nodeOffset + 8, true)
|
|
418
|
+
const endBase = dv.getUint32(nodeOffset + 12, true)
|
|
419
|
+
const childOffset = Number(
|
|
420
|
+
dv.getBigUint64(nodeOffset + 16, true),
|
|
421
|
+
)
|
|
422
|
+
nodeOffset += 24
|
|
423
|
+
if (
|
|
424
|
+
blockIntersectsQuery(startChrom, startBase, endChrom, endBase)
|
|
425
|
+
) {
|
|
426
|
+
nextOffsets.push(childOffset)
|
|
427
|
+
}
|
|
229
428
|
}
|
|
230
429
|
}
|
|
231
430
|
}
|
|
232
|
-
} catch (e) {
|
|
233
|
-
observer.error(e)
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const traverseRTree = (offsets: number[], level: number) => {
|
|
237
|
-
try {
|
|
238
|
-
outstanding += offsets.length
|
|
239
|
-
|
|
240
|
-
// Upper bound on size, based on a completely full leaf node.
|
|
241
|
-
const maxRTreeBlockSpan = 4 + rTreeBlockSize * 32
|
|
242
|
-
let spans = new Range([
|
|
243
|
-
{
|
|
244
|
-
min: offsets[0]!,
|
|
245
|
-
max: offsets[0]! + maxRTreeBlockSpan,
|
|
246
|
-
},
|
|
247
|
-
])
|
|
248
|
-
for (let i = 1; i < offsets.length; i += 1) {
|
|
249
|
-
const blockSpan = new Range([
|
|
250
|
-
{
|
|
251
|
-
min: offsets[i]!,
|
|
252
|
-
max: offsets[i]! + maxRTreeBlockSpan,
|
|
253
|
-
},
|
|
254
|
-
])
|
|
255
|
-
spans = spans.union(blockSpan)
|
|
256
|
-
}
|
|
257
|
-
spans.getRanges().forEach(range => {
|
|
258
|
-
fetchAndProcessRTreeBlocks(offsets, range, level).catch(
|
|
259
|
-
(e: unknown) => {
|
|
260
|
-
observer.error(e)
|
|
261
|
-
},
|
|
262
|
-
)
|
|
263
|
-
})
|
|
264
|
-
} catch (e) {
|
|
265
|
-
observer.error(e)
|
|
266
431
|
}
|
|
267
432
|
}
|
|
268
|
-
|
|
269
|
-
traverseRTree([this.rTreeOffset + 48], 1)
|
|
270
|
-
return
|
|
271
|
-
} catch (e) {
|
|
272
|
-
observer.error(e)
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private parseSummaryBlock(
|
|
277
|
-
b: Uint8Array,
|
|
278
|
-
startOffset: number,
|
|
279
|
-
request?: CoordRequest,
|
|
280
|
-
) {
|
|
281
|
-
const features: Feature[] = []
|
|
282
|
-
let offset = startOffset
|
|
283
|
-
|
|
284
|
-
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
285
|
-
while (offset < b.byteLength) {
|
|
286
|
-
const chromId = dataView.getUint32(offset, true)
|
|
287
|
-
offset += 4
|
|
288
|
-
const start = dataView.getUint32(offset, true)
|
|
289
|
-
offset += 4
|
|
290
|
-
const end = dataView.getUint32(offset, true)
|
|
291
|
-
offset += 4
|
|
292
|
-
const validCnt = dataView.getUint32(offset, true)
|
|
293
|
-
offset += 4
|
|
294
|
-
const minScore = dataView.getFloat32(offset, true)
|
|
295
|
-
offset += 4
|
|
296
|
-
const maxScore = dataView.getFloat32(offset, true)
|
|
297
|
-
offset += 4
|
|
298
|
-
const sumData = dataView.getFloat32(offset, true)
|
|
299
|
-
offset += 8
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
!request ||
|
|
303
|
-
(chromId === request.chrId &&
|
|
304
|
-
coordFilter(start, end, request.start, request.end))
|
|
305
|
-
) {
|
|
306
|
-
features.push({
|
|
307
|
-
start,
|
|
308
|
-
end,
|
|
309
|
-
maxScore,
|
|
310
|
-
minScore,
|
|
311
|
-
summary: true,
|
|
312
|
-
score: sumData / (validCnt || 1),
|
|
313
|
-
})
|
|
314
|
-
}
|
|
433
|
+
currentOffsets = nextOffsets
|
|
315
434
|
}
|
|
316
435
|
|
|
317
|
-
return
|
|
436
|
+
return { blocks, chrId }
|
|
318
437
|
}
|
|
319
438
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
) {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
while (currOffset < data.byteLength) {
|
|
330
|
-
const c2 = currOffset
|
|
331
|
-
const chromId = dataView.getUint32(currOffset, true)
|
|
332
|
-
currOffset += 4
|
|
333
|
-
const start = dataView.getInt32(currOffset, true)
|
|
334
|
-
currOffset += 4
|
|
335
|
-
const end = dataView.getInt32(currOffset, true)
|
|
336
|
-
currOffset += 4
|
|
337
|
-
let i = currOffset
|
|
338
|
-
for (; i < data.length; i++) {
|
|
339
|
-
if (data[i] === 0) {
|
|
340
|
-
break
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
const b = data.subarray(currOffset, i)
|
|
344
|
-
const rest = decoder.decode(b)
|
|
345
|
-
currOffset = i + 1
|
|
346
|
-
if (
|
|
347
|
-
!request ||
|
|
348
|
-
(chromId === request.chrId &&
|
|
349
|
-
coordFilter(start, end, request.start, request.end))
|
|
350
|
-
) {
|
|
351
|
-
items.push({
|
|
352
|
-
start,
|
|
353
|
-
end,
|
|
354
|
-
rest,
|
|
355
|
-
uniqueId: `bb-${offset + c2}`,
|
|
356
|
-
})
|
|
357
|
-
}
|
|
439
|
+
public async readWigData(
|
|
440
|
+
chrName: string,
|
|
441
|
+
start: number,
|
|
442
|
+
end: number,
|
|
443
|
+
opts?: Options,
|
|
444
|
+
): Promise<Feature[]> {
|
|
445
|
+
const collected = await this._collectBlocks(chrName, start, end, opts)
|
|
446
|
+
if (!collected) {
|
|
447
|
+
return []
|
|
358
448
|
}
|
|
359
|
-
|
|
360
|
-
return
|
|
449
|
+
const { blocks, chrId } = collected
|
|
450
|
+
return this.readFeatures(blocks, {
|
|
451
|
+
...opts,
|
|
452
|
+
request: { chrId, start, end },
|
|
453
|
+
})
|
|
361
454
|
}
|
|
362
455
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
const itemSpan = dataView.getUint32(offset, true)
|
|
378
|
-
offset += 4
|
|
379
|
-
const blockType = dataView.getUint8(offset)
|
|
380
|
-
offset += 2
|
|
381
|
-
const itemCount = dataView.getUint16(offset, true)
|
|
382
|
-
offset += 2
|
|
383
|
-
const items = []
|
|
384
|
-
switch (blockType) {
|
|
385
|
-
case 1: {
|
|
386
|
-
for (let i = 0; i < itemCount; i++) {
|
|
387
|
-
const start = dataView.getInt32(offset, true)
|
|
388
|
-
offset += 4
|
|
389
|
-
const end = dataView.getInt32(offset, true)
|
|
390
|
-
offset += 4
|
|
391
|
-
const score = dataView.getFloat32(offset, true)
|
|
392
|
-
offset += 4
|
|
393
|
-
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
394
|
-
items.push({
|
|
395
|
-
start,
|
|
396
|
-
end,
|
|
397
|
-
score,
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
break
|
|
402
|
-
}
|
|
403
|
-
case 2: {
|
|
404
|
-
for (let i = 0; i < itemCount; i++) {
|
|
405
|
-
const start = dataView.getInt32(offset, true)
|
|
406
|
-
offset += 4
|
|
407
|
-
const score = dataView.getFloat32(offset, true)
|
|
408
|
-
offset += 4
|
|
409
|
-
const end = start + itemSpan
|
|
410
|
-
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
411
|
-
items.push({
|
|
412
|
-
score,
|
|
413
|
-
start,
|
|
414
|
-
end,
|
|
415
|
-
})
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
break
|
|
419
|
-
}
|
|
420
|
-
case 3: {
|
|
421
|
-
for (let i = 0; i < itemCount; i++) {
|
|
422
|
-
const score = dataView.getFloat32(offset, true)
|
|
423
|
-
offset += 4
|
|
424
|
-
const start = blockStart + i * itemStep
|
|
425
|
-
const end = start + itemSpan
|
|
426
|
-
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
427
|
-
items.push({
|
|
428
|
-
score,
|
|
429
|
-
start,
|
|
430
|
-
end,
|
|
431
|
-
})
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
break
|
|
435
|
-
}
|
|
456
|
+
public async readWigDataAsArrays(
|
|
457
|
+
chrName: string,
|
|
458
|
+
start: number,
|
|
459
|
+
end: number,
|
|
460
|
+
opts?: Options,
|
|
461
|
+
): Promise<BigWigFeatureArrays | SummaryFeatureArrays> {
|
|
462
|
+
const collected = await this._collectBlocks(chrName, start, end, opts)
|
|
463
|
+
const blocks = collected?.blocks ?? []
|
|
464
|
+
const request = collected
|
|
465
|
+
? { chrId: collected.chrId, start, end }
|
|
466
|
+
: undefined
|
|
467
|
+
const optsWithReq = { ...opts, request }
|
|
468
|
+
if (this.blockType === 'summary') {
|
|
469
|
+
return this._readSummaryFeaturesAsArrays(blocks, optsWithReq)
|
|
436
470
|
}
|
|
437
|
-
|
|
438
|
-
return items
|
|
471
|
+
return this._readBigWigFeaturesAsArrays(blocks, optsWithReq)
|
|
439
472
|
}
|
|
440
473
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
starts[i] = dataView.getInt32(offset, true)
|
|
467
|
-
ends[i] = dataView.getInt32(offset + 4, true)
|
|
468
|
-
scores[i] = dataView.getFloat32(offset + 8, true)
|
|
469
|
-
offset += 12
|
|
470
|
-
}
|
|
471
|
-
return { starts, ends, scores }
|
|
472
|
-
}
|
|
473
|
-
case 2: {
|
|
474
|
-
let offset = 24
|
|
475
|
-
for (let i = 0; i < itemCount; i++) {
|
|
476
|
-
const start = dataView.getInt32(offset, true)
|
|
477
|
-
starts[i] = start
|
|
478
|
-
ends[i] = start + itemSpan
|
|
479
|
-
scores[i] = dataView.getFloat32(offset + 4, true)
|
|
480
|
-
offset += 8
|
|
481
|
-
}
|
|
482
|
-
return { starts, ends, scores }
|
|
474
|
+
public async readFeatures(
|
|
475
|
+
blocks: { offset: number; length: number }[],
|
|
476
|
+
opts: Options = {},
|
|
477
|
+
): Promise<Feature[]> {
|
|
478
|
+
const { blockType, uncompressBufSize } = this
|
|
479
|
+
const { signal, request } = opts
|
|
480
|
+
const blockGroupsToFetch = groupBlocks(blocks)
|
|
481
|
+
const allFeatures: Feature[] = []
|
|
482
|
+
for (const blockGroup of blockGroupsToFetch) {
|
|
483
|
+
const data = await this.bbi.read(blockGroup.length, blockGroup.offset, {
|
|
484
|
+
signal,
|
|
485
|
+
})
|
|
486
|
+
const groupOffset = blockGroup.offset
|
|
487
|
+
const subBlocks = blockGroup.blocks
|
|
488
|
+
|
|
489
|
+
let decompressedData: Uint8Array
|
|
490
|
+
let decompressedOffsets: number[]
|
|
491
|
+
|
|
492
|
+
if (uncompressBufSize > 0) {
|
|
493
|
+
const localBlocks: { offset: number; length: number }[] = []
|
|
494
|
+
for (const block of subBlocks) {
|
|
495
|
+
localBlocks.push({
|
|
496
|
+
offset: block.offset - groupOffset,
|
|
497
|
+
length: block.length,
|
|
498
|
+
})
|
|
483
499
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
return { starts, ends, scores }
|
|
500
|
+
const result = await unzipBatch(data, localBlocks, uncompressBufSize)
|
|
501
|
+
decompressedData = result.data
|
|
502
|
+
decompressedOffsets = result.offsets
|
|
503
|
+
} else {
|
|
504
|
+
decompressedData = data
|
|
505
|
+
decompressedOffsets = []
|
|
506
|
+
for (const block of subBlocks) {
|
|
507
|
+
decompressedOffsets.push(block.offset - groupOffset)
|
|
494
508
|
}
|
|
509
|
+
decompressedOffsets.push(data.length)
|
|
495
510
|
}
|
|
496
|
-
return { starts, ends, scores }
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const reqStart = req.start
|
|
500
|
-
const reqEnd = req.end
|
|
501
|
-
let idx = 0
|
|
502
511
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
ends[idx] = end
|
|
527
|
-
scores[idx] = dataView.getFloat32(offset + 4, true)
|
|
528
|
-
idx++
|
|
529
|
-
}
|
|
530
|
-
offset += 8
|
|
512
|
+
for (let i = 0; i < subBlocks.length; i++) {
|
|
513
|
+
const start = decompressedOffsets[i]!
|
|
514
|
+
const end = decompressedOffsets[i + 1]!
|
|
515
|
+
const resultData = decompressedData.subarray(start, end)
|
|
516
|
+
let features: Feature[]
|
|
517
|
+
switch (blockType) {
|
|
518
|
+
case 'summary':
|
|
519
|
+
features = parseSummaryBlock(resultData, 0, request)
|
|
520
|
+
break
|
|
521
|
+
case 'bigwig':
|
|
522
|
+
features = parseBigWigBlock(resultData, 0, request)
|
|
523
|
+
break
|
|
524
|
+
case 'bigbed':
|
|
525
|
+
features = parseBigBedBlock(
|
|
526
|
+
resultData,
|
|
527
|
+
0,
|
|
528
|
+
subBlocks[i]!.offset * (1 << 8),
|
|
529
|
+
request,
|
|
530
|
+
)
|
|
531
|
+
break
|
|
532
|
+
default:
|
|
533
|
+
features = []
|
|
534
|
+
console.warn(`Don't know what to do with ${blockType}`)
|
|
531
535
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
case 3: {
|
|
535
|
-
let offset = 24
|
|
536
|
-
for (let i = 0; i < itemCount; i++) {
|
|
537
|
-
const start = blockStart + i * itemStep
|
|
538
|
-
const end = start + itemSpan
|
|
539
|
-
if (start < reqEnd && end >= reqStart) {
|
|
540
|
-
starts[idx] = start
|
|
541
|
-
ends[idx] = end
|
|
542
|
-
scores[idx] = dataView.getFloat32(offset, true)
|
|
543
|
-
idx++
|
|
544
|
-
}
|
|
545
|
-
offset += 4
|
|
536
|
+
for (const f of features) {
|
|
537
|
+
allFeatures.push(f)
|
|
546
538
|
}
|
|
547
|
-
break
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
if (idx < itemCount) {
|
|
552
|
-
return {
|
|
553
|
-
starts: starts.subarray(0, idx),
|
|
554
|
-
ends: ends.subarray(0, idx),
|
|
555
|
-
scores: scores.subarray(0, idx),
|
|
556
539
|
}
|
|
557
540
|
}
|
|
558
|
-
return
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
public async readFeatures(
|
|
562
|
-
observer: Observer<Feature[]>,
|
|
563
|
-
blocks: { offset: number; length: number }[],
|
|
564
|
-
opts: Options = {},
|
|
565
|
-
) {
|
|
566
|
-
try {
|
|
567
|
-
const { blockType, uncompressBufSize } = this
|
|
568
|
-
const { signal, request } = opts
|
|
569
|
-
const blockGroupsToFetch = groupBlocks(blocks)
|
|
570
|
-
await Promise.all(
|
|
571
|
-
blockGroupsToFetch.map(async blockGroup => {
|
|
572
|
-
const { length, offset } = blockGroup
|
|
573
|
-
const data = await this.featureCache.get(
|
|
574
|
-
`${length}_${offset}`,
|
|
575
|
-
blockGroup,
|
|
576
|
-
signal,
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
const localBlocks = blockGroup.blocks.map(block => ({
|
|
580
|
-
offset: block.offset - blockGroup.offset,
|
|
581
|
-
length: block.length,
|
|
582
|
-
}))
|
|
583
|
-
|
|
584
|
-
let decompressedData: Uint8Array
|
|
585
|
-
let decompressedOffsets: number[]
|
|
586
|
-
|
|
587
|
-
if (uncompressBufSize > 0) {
|
|
588
|
-
const result = await unzipBatch(
|
|
589
|
-
data,
|
|
590
|
-
localBlocks,
|
|
591
|
-
uncompressBufSize,
|
|
592
|
-
)
|
|
593
|
-
decompressedData = result.data
|
|
594
|
-
decompressedOffsets = result.offsets
|
|
595
|
-
} else {
|
|
596
|
-
decompressedData = data
|
|
597
|
-
decompressedOffsets = localBlocks.map(b => b.offset)
|
|
598
|
-
decompressedOffsets.push(data.length)
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
for (let i = 0; i < blockGroup.blocks.length; i++) {
|
|
602
|
-
const block = blockGroup.blocks[i]!
|
|
603
|
-
const start = decompressedOffsets[i]!
|
|
604
|
-
const end = decompressedOffsets[i + 1]!
|
|
605
|
-
const resultData = decompressedData.subarray(start, end)
|
|
606
|
-
|
|
607
|
-
switch (blockType) {
|
|
608
|
-
case 'summary': {
|
|
609
|
-
observer.next(this.parseSummaryBlock(resultData, 0, request))
|
|
610
|
-
break
|
|
611
|
-
}
|
|
612
|
-
case 'bigwig': {
|
|
613
|
-
observer.next(this.parseBigWigBlock(resultData, 0, request))
|
|
614
|
-
break
|
|
615
|
-
}
|
|
616
|
-
case 'bigbed': {
|
|
617
|
-
observer.next(
|
|
618
|
-
this.parseBigBedBlock(
|
|
619
|
-
resultData,
|
|
620
|
-
0,
|
|
621
|
-
block.offset * (1 << 8),
|
|
622
|
-
request,
|
|
623
|
-
),
|
|
624
|
-
)
|
|
625
|
-
break
|
|
626
|
-
}
|
|
627
|
-
default: {
|
|
628
|
-
console.warn(`Don't know what to do with ${blockType}`)
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}),
|
|
633
|
-
)
|
|
634
|
-
observer.complete()
|
|
635
|
-
} catch (e) {
|
|
636
|
-
observer.error(e)
|
|
637
|
-
}
|
|
541
|
+
return allFeatures
|
|
638
542
|
}
|
|
639
543
|
|
|
640
|
-
|
|
544
|
+
private async _readBigWigFeaturesAsArrays(
|
|
641
545
|
blocks: { offset: number; length: number }[],
|
|
642
546
|
opts: Options = {},
|
|
643
547
|
): Promise<BigWigFeatureArrays> {
|
|
@@ -650,51 +554,45 @@ export class BlockView {
|
|
|
650
554
|
const allScores: Float32Array[] = []
|
|
651
555
|
let totalCount = 0
|
|
652
556
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
557
|
+
for (const blockGroup of blockGroupsToFetch) {
|
|
558
|
+
const { length, offset } = blockGroup
|
|
559
|
+
const data = await this.bbi.read(length, offset, { signal })
|
|
560
|
+
|
|
561
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
562
|
+
offset: block.offset - blockGroup.offset,
|
|
563
|
+
length: block.length,
|
|
564
|
+
}))
|
|
565
|
+
|
|
566
|
+
if (uncompressBufSize > 0) {
|
|
567
|
+
const result = await decompressAndParseBigWigBlocks(
|
|
568
|
+
data,
|
|
569
|
+
localBlocks,
|
|
570
|
+
uncompressBufSize,
|
|
571
|
+
request?.start ?? 0,
|
|
572
|
+
request?.end ?? 0,
|
|
660
573
|
)
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
request?.start ?? 0,
|
|
673
|
-
request?.end ?? 0,
|
|
574
|
+
if (result.starts.length > 0) {
|
|
575
|
+
allStarts.push(result.starts)
|
|
576
|
+
allEnds.push(result.ends)
|
|
577
|
+
allScores.push(result.scores)
|
|
578
|
+
totalCount += result.starts.length
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
for (const block of localBlocks) {
|
|
582
|
+
const blockData = data.subarray(
|
|
583
|
+
block.offset,
|
|
584
|
+
block.offset + block.length,
|
|
674
585
|
)
|
|
586
|
+
const result = parseBigWigBlockAsArrays(blockData, 0, request)
|
|
675
587
|
if (result.starts.length > 0) {
|
|
676
588
|
allStarts.push(result.starts)
|
|
677
589
|
allEnds.push(result.ends)
|
|
678
590
|
allScores.push(result.scores)
|
|
679
591
|
totalCount += result.starts.length
|
|
680
592
|
}
|
|
681
|
-
} else {
|
|
682
|
-
for (const block of localBlocks) {
|
|
683
|
-
const blockData = data.subarray(
|
|
684
|
-
block.offset,
|
|
685
|
-
block.offset + block.length,
|
|
686
|
-
)
|
|
687
|
-
const result = this.parseBigWigBlockAsArrays(blockData, 0, request)
|
|
688
|
-
if (result.starts.length > 0) {
|
|
689
|
-
allStarts.push(result.starts)
|
|
690
|
-
allEnds.push(result.ends)
|
|
691
|
-
allScores.push(result.scores)
|
|
692
|
-
totalCount += result.starts.length
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
593
|
}
|
|
696
|
-
}
|
|
697
|
-
|
|
594
|
+
}
|
|
595
|
+
}
|
|
698
596
|
|
|
699
597
|
if (allStarts.length === 0) {
|
|
700
598
|
return {
|
|
@@ -728,7 +626,7 @@ export class BlockView {
|
|
|
728
626
|
return { starts, ends, scores, isSummary: false as const }
|
|
729
627
|
}
|
|
730
628
|
|
|
731
|
-
|
|
629
|
+
private async _readSummaryFeaturesAsArrays(
|
|
732
630
|
blocks: { offset: number; length: number }[],
|
|
733
631
|
opts: Options = {},
|
|
734
632
|
): Promise<SummaryFeatureArrays> {
|
|
@@ -743,69 +641,63 @@ export class BlockView {
|
|
|
743
641
|
const allMaxScores: Float32Array[] = []
|
|
744
642
|
let totalCount = 0
|
|
745
643
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
644
|
+
for (const blockGroup of blockGroupsToFetch) {
|
|
645
|
+
const { length, offset } = blockGroup
|
|
646
|
+
const data = await this.bbi.read(length, offset, { signal })
|
|
647
|
+
|
|
648
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
649
|
+
offset: block.offset - blockGroup.offset,
|
|
650
|
+
length: block.length,
|
|
651
|
+
}))
|
|
652
|
+
|
|
653
|
+
if (uncompressBufSize > 0) {
|
|
654
|
+
const result = await decompressAndParseSummaryBlocks(
|
|
655
|
+
data,
|
|
656
|
+
localBlocks,
|
|
657
|
+
uncompressBufSize,
|
|
658
|
+
request?.chrId ?? 0,
|
|
659
|
+
request?.start ?? 0,
|
|
660
|
+
request?.end ?? 0,
|
|
753
661
|
)
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
request?.end ?? 0,
|
|
662
|
+
if (result.starts.length > 0) {
|
|
663
|
+
allStarts.push(result.starts)
|
|
664
|
+
allEnds.push(result.ends)
|
|
665
|
+
allScores.push(result.scores)
|
|
666
|
+
allMinScores.push(result.minScores)
|
|
667
|
+
allMaxScores.push(result.maxScores)
|
|
668
|
+
totalCount += result.starts.length
|
|
669
|
+
}
|
|
670
|
+
} else {
|
|
671
|
+
for (const block of localBlocks) {
|
|
672
|
+
const blockData = data.subarray(
|
|
673
|
+
block.offset,
|
|
674
|
+
block.offset + block.length,
|
|
768
675
|
)
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const features = this.parseSummaryBlock(blockData, 0, request)
|
|
784
|
-
if (features.length > 0) {
|
|
785
|
-
const starts = new Int32Array(features.length)
|
|
786
|
-
const ends = new Int32Array(features.length)
|
|
787
|
-
const scores = new Float32Array(features.length)
|
|
788
|
-
const minScores = new Float32Array(features.length)
|
|
789
|
-
const maxScores = new Float32Array(features.length)
|
|
790
|
-
for (let i = 0; i < features.length; i++) {
|
|
791
|
-
const f = features[i]!
|
|
792
|
-
starts[i] = f.start
|
|
793
|
-
ends[i] = f.end
|
|
794
|
-
scores[i] = f.score ?? 0
|
|
795
|
-
minScores[i] = f.minScore ?? 0
|
|
796
|
-
maxScores[i] = f.maxScore ?? 0
|
|
797
|
-
}
|
|
798
|
-
allStarts.push(starts)
|
|
799
|
-
allEnds.push(ends)
|
|
800
|
-
allScores.push(scores)
|
|
801
|
-
allMinScores.push(minScores)
|
|
802
|
-
allMaxScores.push(maxScores)
|
|
803
|
-
totalCount += features.length
|
|
676
|
+
const features = parseSummaryBlock(blockData, 0, request)
|
|
677
|
+
if (features.length > 0) {
|
|
678
|
+
const starts = new Int32Array(features.length)
|
|
679
|
+
const ends = new Int32Array(features.length)
|
|
680
|
+
const scores = new Float32Array(features.length)
|
|
681
|
+
const minScores = new Float32Array(features.length)
|
|
682
|
+
const maxScores = new Float32Array(features.length)
|
|
683
|
+
for (let i = 0; i < features.length; i++) {
|
|
684
|
+
const f = features[i]!
|
|
685
|
+
starts[i] = f.start
|
|
686
|
+
ends[i] = f.end
|
|
687
|
+
scores[i] = f.score ?? 0
|
|
688
|
+
minScores[i] = f.minScore ?? 0
|
|
689
|
+
maxScores[i] = f.maxScore ?? 0
|
|
804
690
|
}
|
|
691
|
+
allStarts.push(starts)
|
|
692
|
+
allEnds.push(ends)
|
|
693
|
+
allScores.push(scores)
|
|
694
|
+
allMinScores.push(minScores)
|
|
695
|
+
allMaxScores.push(maxScores)
|
|
696
|
+
totalCount += features.length
|
|
805
697
|
}
|
|
806
698
|
}
|
|
807
|
-
}
|
|
808
|
-
|
|
699
|
+
}
|
|
700
|
+
}
|
|
809
701
|
|
|
810
702
|
if (allStarts.length === 0) {
|
|
811
703
|
return {
|