@gmod/bbi 7.1.0 → 8.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 +59 -0
- package/dist/bbi.d.ts +13 -3
- package/dist/bbi.js +77 -17
- package/dist/bbi.js.map +1 -1
- package/dist/bigbed.d.ts +14 -2
- package/dist/bigbed.js +115 -76
- package/dist/bigbed.js.map +1 -1
- package/dist/bigwig.js +1 -2
- package/dist/bigwig.js.map +1 -1
- package/dist/block-view.d.ts +13 -4
- package/dist/block-view.js +324 -148
- package/dist/block-view.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/parse-bigwig.d.ts +3 -0
- package/dist/parse-bigwig.js +15 -0
- package/dist/parse-bigwig.js.map +1 -0
- package/dist/range.js +11 -24
- package/dist/range.js.map +1 -1
- package/dist/types.d.ts +14 -2
- package/dist/unzip.d.ts +18 -1
- package/dist/unzip.js +33 -4
- package/dist/unzip.js.map +1 -1
- package/dist/util.d.ts +2 -4
- package/dist/util.js +6 -5
- package/dist/util.js.map +1 -1
- package/dist/wasm/inflate-wasm-inlined.d.ts +19 -0
- package/dist/wasm/inflate-wasm-inlined.js +117 -0
- package/dist/wasm/inflate-wasm-inlined.js.map +1 -0
- package/dist/wasm/inflate_wasm.d.ts +1 -0
- package/dist/wasm/inflate_wasm.js +43 -0
- package/dist/wasm/inflate_wasm.js.map +1 -0
- package/dist/wasm/inflate_wasm_bg.d.ts +68 -0
- package/dist/wasm/inflate_wasm_bg.js +307 -0
- package/dist/wasm/inflate_wasm_bg.js.map +1 -0
- package/esm/bbi.d.ts +13 -3
- package/esm/bbi.js +77 -17
- package/esm/bbi.js.map +1 -1
- package/esm/bigbed.d.ts +14 -2
- package/esm/bigbed.js +115 -76
- package/esm/bigbed.js.map +1 -1
- package/esm/bigwig.js +1 -2
- package/esm/bigwig.js.map +1 -1
- package/esm/block-view.d.ts +13 -4
- package/esm/block-view.js +326 -150
- package/esm/block-view.js.map +1 -1
- package/esm/index.d.ts +2 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/parse-bigwig.d.ts +3 -0
- package/esm/parse-bigwig.js +12 -0
- package/esm/parse-bigwig.js.map +1 -0
- package/esm/range.js +11 -24
- package/esm/range.js.map +1 -1
- package/esm/types.d.ts +14 -2
- package/esm/unzip.d.ts +18 -1
- package/esm/unzip.js +30 -3
- package/esm/unzip.js.map +1 -1
- package/esm/util.d.ts +2 -4
- package/esm/util.js +6 -5
- package/esm/util.js.map +1 -1
- package/esm/wasm/inflate-wasm-inlined.d.ts +19 -0
- package/esm/wasm/inflate-wasm-inlined.js +111 -0
- package/esm/wasm/inflate-wasm-inlined.js.map +1 -0
- package/esm/wasm/inflate_wasm.d.ts +1 -0
- package/esm/wasm/inflate_wasm.js +5 -0
- package/esm/wasm/inflate_wasm.js.map +1 -0
- package/esm/wasm/inflate_wasm_bg.d.ts +68 -0
- package/esm/wasm/inflate_wasm_bg.js +296 -0
- package/esm/wasm/inflate_wasm_bg.js.map +1 -0
- package/package.json +15 -10
- package/src/bbi.ts +100 -20
- package/src/bigbed.ts +157 -80
- package/src/bigwig.ts +1 -2
- package/src/block-view.ts +415 -156
- package/src/index.ts +8 -1
- package/src/parse-bigwig.ts +19 -0
- package/src/range.ts +13 -21
- package/src/types.ts +19 -2
- package/src/unzip.ts +88 -3
- package/src/util.ts +9 -10
- package/src/wasm/inflate-wasm-inlined.d.ts +49 -0
- package/src/wasm/inflate-wasm-inlined.js +1 -0
- package/src/wasm/inflate_wasm.d.ts +35 -0
- package/src/wasm/inflate_wasm.js +4 -0
- package/src/wasm/inflate_wasm_bg.js +309 -0
- package/src/wasm/inflate_wasm_bg.wasm +0 -0
- package/src/wasm/inflate_wasm_bg.wasm.d.ts +13 -0
package/src/block-view.ts
CHANGED
|
@@ -2,15 +2,22 @@ import AbortablePromiseCache from '@gmod/abortable-promise-cache'
|
|
|
2
2
|
import QuickLRU from 'quick-lru'
|
|
3
3
|
|
|
4
4
|
import Range from './range.ts'
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
decompressAndParseBigWigBlocks,
|
|
7
|
+
decompressAndParseSummaryBlocks,
|
|
8
|
+
unzipBatch,
|
|
9
|
+
} from './unzip.ts'
|
|
10
|
+
import { groupBlocks } from './util.ts'
|
|
7
11
|
|
|
8
12
|
import type { Feature } from './types.ts'
|
|
13
|
+
import type {
|
|
14
|
+
BigWigFeatureArrays,
|
|
15
|
+
SummaryFeatureArrays,
|
|
16
|
+
} from './unzip.ts'
|
|
9
17
|
import type { GenericFilehandle } from 'generic-filehandle2'
|
|
10
18
|
import type { Observer } from 'rxjs'
|
|
11
19
|
|
|
12
|
-
const decoder =
|
|
13
|
-
typeof TextDecoder !== 'undefined' ? new TextDecoder('utf8') : undefined
|
|
20
|
+
const decoder = new TextDecoder('utf8')
|
|
14
21
|
|
|
15
22
|
interface CoordRequest {
|
|
16
23
|
chrId: number
|
|
@@ -40,7 +47,9 @@ function coordFilter(s1: number, e1: number, s2: number, e2: number): boolean {
|
|
|
40
47
|
*/
|
|
41
48
|
|
|
42
49
|
export class BlockView {
|
|
43
|
-
|
|
50
|
+
// R-tree index header cache - R-trees are spatial data structures used to
|
|
51
|
+
// efficiently query genomic intervals by chromosome and position
|
|
52
|
+
private rTreePromise?: Promise<Uint8Array>
|
|
44
53
|
|
|
45
54
|
private featureCache = new AbortablePromiseCache<ReadData, Uint8Array>({
|
|
46
55
|
cache: new QuickLRU({ maxSize: 1000 }),
|
|
@@ -51,13 +60,16 @@ export class BlockView {
|
|
|
51
60
|
|
|
52
61
|
public constructor(
|
|
53
62
|
private bbi: GenericFilehandle,
|
|
54
|
-
private refsByName:
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
private refsByName: Record<string, number>,
|
|
64
|
+
// Offset to the R-tree index in the file - this is part of the "cirTree"
|
|
65
|
+
// (combined ID R-tree), which combines a B+ tree for chromosome names
|
|
66
|
+
// with an R-tree for efficient spatial queries
|
|
67
|
+
private rTreeOffset: number,
|
|
68
|
+
private uncompressBufSize: number,
|
|
57
69
|
private blockType: string,
|
|
58
70
|
) {
|
|
59
|
-
if (!(
|
|
60
|
-
throw new Error('invalid
|
|
71
|
+
if (!(rTreeOffset >= 0)) {
|
|
72
|
+
throw new Error('invalid rTreeOffset!')
|
|
61
73
|
}
|
|
62
74
|
}
|
|
63
75
|
|
|
@@ -72,101 +84,115 @@ export class BlockView {
|
|
|
72
84
|
const chrId = this.refsByName[chrName]
|
|
73
85
|
if (chrId === undefined) {
|
|
74
86
|
observer.complete()
|
|
87
|
+
return
|
|
75
88
|
}
|
|
76
89
|
const request = { chrId, start, end }
|
|
77
|
-
if (!this.
|
|
78
|
-
this.
|
|
90
|
+
if (!this.rTreePromise) {
|
|
91
|
+
this.rTreePromise = this.bbi.read(48, this.rTreeOffset, opts)
|
|
79
92
|
}
|
|
80
|
-
const buffer = await this.
|
|
81
|
-
const dataView = new DataView(
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
const buffer = await this.rTreePromise
|
|
94
|
+
const dataView = new DataView(
|
|
95
|
+
buffer.buffer,
|
|
96
|
+
buffer.byteOffset,
|
|
97
|
+
buffer.length,
|
|
98
|
+
)
|
|
99
|
+
// Maximum number of children per R-tree node - used to calculate memory bounds
|
|
100
|
+
const rTreeBlockSize = dataView.getUint32(4, true)
|
|
101
|
+
const blocksToFetch: ReadData[] = []
|
|
84
102
|
let outstanding = 0
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
// R-tree leaf nodes contain the actual data blocks to fetch
|
|
105
|
+
const processLeafNode = (
|
|
106
|
+
dataView: DataView,
|
|
107
|
+
startOffset: number,
|
|
108
|
+
count: number,
|
|
109
|
+
) => {
|
|
110
|
+
let offset = startOffset
|
|
111
|
+
for (let i = 0; i < count; i++) {
|
|
112
|
+
const startChrom = dataView.getUint32(offset, true)
|
|
113
|
+
offset += 4
|
|
114
|
+
const startBase = dataView.getUint32(offset, true)
|
|
115
|
+
offset += 4
|
|
116
|
+
const endChrom = dataView.getUint32(offset, true)
|
|
117
|
+
offset += 4
|
|
118
|
+
const endBase = dataView.getUint32(offset, true)
|
|
119
|
+
offset += 4
|
|
120
|
+
const blockOffset = Number(dataView.getBigUint64(offset, true))
|
|
121
|
+
offset += 8
|
|
122
|
+
const blockSize = Number(dataView.getBigUint64(offset, true))
|
|
123
|
+
offset += 8
|
|
124
|
+
if (
|
|
125
|
+
blockIntersectsQuery({ startChrom, startBase, endBase, endChrom })
|
|
126
|
+
) {
|
|
127
|
+
blocksToFetch.push({
|
|
128
|
+
offset: blockOffset,
|
|
129
|
+
length: blockSize,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// R-tree non-leaf nodes contain pointers to child nodes
|
|
136
|
+
const processNonLeafNode = (
|
|
137
|
+
dataView: DataView,
|
|
138
|
+
startOffset: number,
|
|
139
|
+
count: number,
|
|
140
|
+
level: number,
|
|
141
|
+
) => {
|
|
142
|
+
const recurOffsets = []
|
|
143
|
+
let offset = startOffset
|
|
144
|
+
for (let i = 0; i < count; i++) {
|
|
145
|
+
const startChrom = dataView.getUint32(offset, true)
|
|
146
|
+
offset += 4
|
|
147
|
+
const startBase = dataView.getUint32(offset, true)
|
|
148
|
+
offset += 4
|
|
149
|
+
const endChrom = dataView.getUint32(offset, true)
|
|
150
|
+
offset += 4
|
|
151
|
+
const endBase = dataView.getUint32(offset, true)
|
|
152
|
+
offset += 4
|
|
153
|
+
const blockOffset = Number(dataView.getBigUint64(offset, true))
|
|
154
|
+
offset += 8
|
|
155
|
+
if (
|
|
156
|
+
blockIntersectsQuery({ startChrom, startBase, endChrom, endBase })
|
|
157
|
+
) {
|
|
158
|
+
recurOffsets.push(blockOffset)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (recurOffsets.length > 0) {
|
|
162
|
+
traverseRTree(recurOffsets, level + 1)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const processRTreeNode = (
|
|
167
|
+
rTreeBlockData: Uint8Array,
|
|
88
168
|
offset2: number,
|
|
89
169
|
level: number,
|
|
90
170
|
) => {
|
|
91
171
|
try {
|
|
92
|
-
const data =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
172
|
+
const data = rTreeBlockData.subarray(offset2)
|
|
173
|
+
const dataView = new DataView(
|
|
174
|
+
data.buffer,
|
|
175
|
+
data.byteOffset,
|
|
176
|
+
data.length,
|
|
177
|
+
)
|
|
96
178
|
let offset = 0
|
|
97
179
|
|
|
98
180
|
const isLeaf = dataView.getUint8(offset)
|
|
99
|
-
offset += 2 // 1 skip
|
|
100
|
-
const
|
|
181
|
+
offset += 2 // 1 skip for reserved byte
|
|
182
|
+
const count = dataView.getUint16(offset, true)
|
|
101
183
|
offset += 2
|
|
184
|
+
|
|
102
185
|
if (isLeaf === 1) {
|
|
103
|
-
|
|
104
|
-
for (let i = 0; i < cnt; i++) {
|
|
105
|
-
const startChrom = dataView.getUint32(offset, true)
|
|
106
|
-
offset += 4
|
|
107
|
-
const startBase = dataView.getUint32(offset, true)
|
|
108
|
-
offset += 4
|
|
109
|
-
const endChrom = dataView.getUint32(offset, true)
|
|
110
|
-
offset += 4
|
|
111
|
-
const endBase = dataView.getUint32(offset, true)
|
|
112
|
-
offset += 4
|
|
113
|
-
const blockOffset = Number(dataView.getBigUint64(offset, true))
|
|
114
|
-
offset += 8
|
|
115
|
-
const blockSize = Number(dataView.getBigUint64(offset, true))
|
|
116
|
-
offset += 8
|
|
117
|
-
blocksToFetch2.push({
|
|
118
|
-
startChrom,
|
|
119
|
-
startBase,
|
|
120
|
-
endBase,
|
|
121
|
-
endChrom,
|
|
122
|
-
blockOffset,
|
|
123
|
-
blockSize,
|
|
124
|
-
offset,
|
|
125
|
-
})
|
|
126
|
-
}
|
|
127
|
-
blocksToFetch = blocksToFetch.concat(
|
|
128
|
-
blocksToFetch2
|
|
129
|
-
.filter(f => filterFeats(f))
|
|
130
|
-
.map(l => ({
|
|
131
|
-
offset: l.blockOffset,
|
|
132
|
-
length: l.blockSize,
|
|
133
|
-
})),
|
|
134
|
-
)
|
|
186
|
+
processLeafNode(dataView, offset, count)
|
|
135
187
|
} else if (isLeaf === 0) {
|
|
136
|
-
|
|
137
|
-
for (let i = 0; i < cnt; i++) {
|
|
138
|
-
const startChrom = dataView.getUint32(offset, true)
|
|
139
|
-
offset += 4
|
|
140
|
-
const startBase = dataView.getUint32(offset, true)
|
|
141
|
-
offset += 4
|
|
142
|
-
const endChrom = dataView.getUint32(offset, true)
|
|
143
|
-
offset += 4
|
|
144
|
-
const endBase = dataView.getUint32(offset, true)
|
|
145
|
-
offset += 4
|
|
146
|
-
const blockOffset = Number(dataView.getBigUint64(offset, true))
|
|
147
|
-
offset += 8
|
|
148
|
-
recurOffsets.push({
|
|
149
|
-
startChrom,
|
|
150
|
-
startBase,
|
|
151
|
-
endChrom,
|
|
152
|
-
endBase,
|
|
153
|
-
blockOffset,
|
|
154
|
-
offset,
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
const recurOffsets2 = recurOffsets
|
|
158
|
-
.filter(f => filterFeats(f))
|
|
159
|
-
.map(l => l.blockOffset)
|
|
160
|
-
if (recurOffsets2.length > 0) {
|
|
161
|
-
cirFobRecur(recurOffsets2, level + 1)
|
|
162
|
-
}
|
|
188
|
+
processNonLeafNode(dataView, offset, count, level)
|
|
163
189
|
}
|
|
164
190
|
} catch (e) {
|
|
165
191
|
observer.error(e)
|
|
166
192
|
}
|
|
167
193
|
}
|
|
168
194
|
|
|
169
|
-
const
|
|
195
|
+
const blockIntersectsQuery = (b: {
|
|
170
196
|
startChrom: number
|
|
171
197
|
startBase: number
|
|
172
198
|
endChrom: number
|
|
@@ -179,22 +205,22 @@ export class BlockView {
|
|
|
179
205
|
)
|
|
180
206
|
}
|
|
181
207
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
const fetchAndProcessRTreeBlocks = async (
|
|
209
|
+
offsets: number[],
|
|
210
|
+
range: Range,
|
|
185
211
|
level: number,
|
|
186
212
|
) => {
|
|
187
213
|
try {
|
|
188
|
-
const length =
|
|
189
|
-
const offset =
|
|
214
|
+
const length = range.max - range.min
|
|
215
|
+
const offset = range.min
|
|
190
216
|
const resultBuffer = await this.featureCache.get(
|
|
191
217
|
`${length}_${offset}`,
|
|
192
218
|
{ length, offset },
|
|
193
219
|
opts?.signal,
|
|
194
220
|
)
|
|
195
|
-
for (const element of
|
|
196
|
-
if (
|
|
197
|
-
|
|
221
|
+
for (const element of offsets) {
|
|
222
|
+
if (range.contains(element)) {
|
|
223
|
+
processRTreeNode(resultBuffer, element - offset, level)
|
|
198
224
|
outstanding -= 1
|
|
199
225
|
if (outstanding === 0) {
|
|
200
226
|
this.readFeatures(observer, blocksToFetch, {
|
|
@@ -210,35 +236,40 @@ export class BlockView {
|
|
|
210
236
|
observer.error(e)
|
|
211
237
|
}
|
|
212
238
|
}
|
|
213
|
-
const
|
|
239
|
+
const traverseRTree = (offsets: number[], level: number) => {
|
|
214
240
|
try {
|
|
215
|
-
outstanding +=
|
|
241
|
+
outstanding += offsets.length
|
|
216
242
|
|
|
217
243
|
// Upper bound on size, based on a completely full leaf node.
|
|
218
|
-
const
|
|
244
|
+
const maxRTreeBlockSpan = 4 + rTreeBlockSize * 32
|
|
219
245
|
let spans = new Range([
|
|
220
246
|
{
|
|
221
|
-
min:
|
|
222
|
-
max:
|
|
247
|
+
min: offsets[0]!,
|
|
248
|
+
max: offsets[0]! + maxRTreeBlockSpan,
|
|
223
249
|
},
|
|
224
250
|
])
|
|
225
|
-
for (let i = 1; i <
|
|
251
|
+
for (let i = 1; i < offsets.length; i += 1) {
|
|
226
252
|
const blockSpan = new Range([
|
|
227
253
|
{
|
|
228
|
-
min:
|
|
229
|
-
max:
|
|
254
|
+
min: offsets[i]!,
|
|
255
|
+
max: offsets[i]! + maxRTreeBlockSpan,
|
|
230
256
|
},
|
|
231
257
|
])
|
|
232
258
|
spans = spans.union(blockSpan)
|
|
233
259
|
}
|
|
234
|
-
|
|
235
|
-
|
|
260
|
+
spans.getRanges().forEach(range => {
|
|
261
|
+
fetchAndProcessRTreeBlocks(offsets, range, level).catch(
|
|
262
|
+
(e: unknown) => {
|
|
263
|
+
observer.error(e)
|
|
264
|
+
},
|
|
265
|
+
)
|
|
266
|
+
})
|
|
236
267
|
} catch (e) {
|
|
237
268
|
observer.error(e)
|
|
238
269
|
}
|
|
239
270
|
}
|
|
240
271
|
|
|
241
|
-
|
|
272
|
+
traverseRTree([this.rTreeOffset + 48], 1)
|
|
242
273
|
return
|
|
243
274
|
} catch (e) {
|
|
244
275
|
observer.error(e)
|
|
@@ -250,13 +281,11 @@ export class BlockView {
|
|
|
250
281
|
startOffset: number,
|
|
251
282
|
request?: CoordRequest,
|
|
252
283
|
) {
|
|
253
|
-
const features
|
|
284
|
+
const features: Feature[] = []
|
|
254
285
|
let offset = startOffset
|
|
255
286
|
|
|
256
287
|
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
257
288
|
while (offset < b.byteLength) {
|
|
258
|
-
// this was extracted from looking at the runtime code generated by
|
|
259
|
-
// binary-parser
|
|
260
289
|
const chromId = dataView.getUint32(offset, true)
|
|
261
290
|
offset += 4
|
|
262
291
|
const start = dataView.getUint32(offset, true)
|
|
@@ -270,16 +299,12 @@ export class BlockView {
|
|
|
270
299
|
const maxScore = dataView.getFloat32(offset, true)
|
|
271
300
|
offset += 4
|
|
272
301
|
const sumData = dataView.getFloat32(offset, true)
|
|
273
|
-
offset +=
|
|
274
|
-
// unused
|
|
275
|
-
// const sumSqData = dataView.getFloat32(offset, true)
|
|
276
|
-
offset += 4
|
|
302
|
+
offset += 8
|
|
277
303
|
|
|
278
304
|
if (
|
|
279
|
-
request
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
: true
|
|
305
|
+
!request ||
|
|
306
|
+
(chromId === request.chrId &&
|
|
307
|
+
coordFilter(start, end, request.start, request.end))
|
|
283
308
|
) {
|
|
284
309
|
features.push({
|
|
285
310
|
start,
|
|
@@ -301,10 +326,9 @@ export class BlockView {
|
|
|
301
326
|
offset: number,
|
|
302
327
|
request?: CoordRequest,
|
|
303
328
|
) {
|
|
304
|
-
const items
|
|
329
|
+
const items: Feature[] = []
|
|
305
330
|
let currOffset = startOffset
|
|
306
|
-
const
|
|
307
|
-
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
331
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.length)
|
|
308
332
|
while (currOffset < data.byteLength) {
|
|
309
333
|
const c2 = currOffset
|
|
310
334
|
const chromId = dataView.getUint32(currOffset, true)
|
|
@@ -320,22 +344,23 @@ export class BlockView {
|
|
|
320
344
|
}
|
|
321
345
|
}
|
|
322
346
|
const b = data.subarray(currOffset, i)
|
|
323
|
-
const rest = decoder
|
|
347
|
+
const rest = decoder.decode(b)
|
|
324
348
|
currOffset = i + 1
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
349
|
+
if (
|
|
350
|
+
!request ||
|
|
351
|
+
(chromId === request.chrId &&
|
|
352
|
+
coordFilter(start, end, request.start, request.end))
|
|
353
|
+
) {
|
|
354
|
+
items.push({
|
|
355
|
+
start,
|
|
356
|
+
end,
|
|
357
|
+
rest,
|
|
358
|
+
uniqueId: `bb-${offset + c2}`,
|
|
359
|
+
})
|
|
360
|
+
}
|
|
332
361
|
}
|
|
333
362
|
|
|
334
|
-
return
|
|
335
|
-
? items.filter((f: any) =>
|
|
336
|
-
coordFilter(f.start, f.end, request.start, request.end),
|
|
337
|
-
)
|
|
338
|
-
: items
|
|
363
|
+
return items
|
|
339
364
|
}
|
|
340
365
|
|
|
341
366
|
private parseBigWigBlock(
|
|
@@ -358,7 +383,7 @@ export class BlockView {
|
|
|
358
383
|
offset += 2
|
|
359
384
|
const itemCount = dataView.getUint16(offset, true)
|
|
360
385
|
offset += 2
|
|
361
|
-
const items =
|
|
386
|
+
const items = []
|
|
362
387
|
switch (blockType) {
|
|
363
388
|
case 1: {
|
|
364
389
|
for (let i = 0; i < itemCount; i++) {
|
|
@@ -368,10 +393,12 @@ export class BlockView {
|
|
|
368
393
|
offset += 4
|
|
369
394
|
const score = dataView.getFloat32(offset, true)
|
|
370
395
|
offset += 4
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
396
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
397
|
+
items.push({
|
|
398
|
+
start,
|
|
399
|
+
end,
|
|
400
|
+
score,
|
|
401
|
+
})
|
|
375
402
|
}
|
|
376
403
|
}
|
|
377
404
|
break
|
|
@@ -382,10 +409,13 @@ export class BlockView {
|
|
|
382
409
|
offset += 4
|
|
383
410
|
const score = dataView.getFloat32(offset, true)
|
|
384
411
|
offset += 4
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
412
|
+
const end = start + itemSpan
|
|
413
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
414
|
+
items.push({
|
|
415
|
+
score,
|
|
416
|
+
start,
|
|
417
|
+
end,
|
|
418
|
+
})
|
|
389
419
|
}
|
|
390
420
|
}
|
|
391
421
|
break
|
|
@@ -395,19 +425,20 @@ export class BlockView {
|
|
|
395
425
|
const score = dataView.getFloat32(offset, true)
|
|
396
426
|
offset += 4
|
|
397
427
|
const start = blockStart + i * itemStep
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
428
|
+
const end = start + itemSpan
|
|
429
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
430
|
+
items.push({
|
|
431
|
+
score,
|
|
432
|
+
start,
|
|
433
|
+
end,
|
|
434
|
+
})
|
|
402
435
|
}
|
|
403
436
|
}
|
|
404
437
|
break
|
|
405
438
|
}
|
|
406
439
|
}
|
|
407
440
|
|
|
408
|
-
return
|
|
409
|
-
? items.filter(f => coordFilter(f.start, f.end, req.start, req.end))
|
|
410
|
-
: items
|
|
441
|
+
return items
|
|
411
442
|
}
|
|
412
443
|
|
|
413
444
|
public async readFeatures(
|
|
@@ -416,26 +447,45 @@ export class BlockView {
|
|
|
416
447
|
opts: Options = {},
|
|
417
448
|
) {
|
|
418
449
|
try {
|
|
419
|
-
const { blockType,
|
|
450
|
+
const { blockType, uncompressBufSize } = this
|
|
420
451
|
const { signal, request } = opts
|
|
421
452
|
const blockGroupsToFetch = groupBlocks(blocks)
|
|
422
|
-
checkAbortSignal(signal)
|
|
423
453
|
await Promise.all(
|
|
424
454
|
blockGroupsToFetch.map(async blockGroup => {
|
|
425
|
-
checkAbortSignal(signal)
|
|
426
455
|
const { length, offset } = blockGroup
|
|
427
456
|
const data = await this.featureCache.get(
|
|
428
457
|
`${length}_${offset}`,
|
|
429
458
|
blockGroup,
|
|
430
459
|
signal,
|
|
431
460
|
)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
461
|
+
|
|
462
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
463
|
+
offset: block.offset - blockGroup.offset,
|
|
464
|
+
length: block.length,
|
|
465
|
+
}))
|
|
466
|
+
|
|
467
|
+
let decompressedData: Uint8Array
|
|
468
|
+
let decompressedOffsets: number[]
|
|
469
|
+
|
|
470
|
+
if (uncompressBufSize > 0) {
|
|
471
|
+
const result = await unzipBatch(
|
|
472
|
+
data,
|
|
473
|
+
localBlocks,
|
|
474
|
+
uncompressBufSize,
|
|
475
|
+
)
|
|
476
|
+
decompressedData = result.data
|
|
477
|
+
decompressedOffsets = result.offsets
|
|
478
|
+
} else {
|
|
479
|
+
decompressedData = data
|
|
480
|
+
decompressedOffsets = localBlocks.map(b => b.offset)
|
|
481
|
+
decompressedOffsets.push(data.length)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
for (let i = 0; i < blockGroup.blocks.length; i++) {
|
|
485
|
+
const block = blockGroup.blocks[i]!
|
|
486
|
+
const start = decompressedOffsets[i]!
|
|
487
|
+
const end = decompressedOffsets[i + 1]!
|
|
488
|
+
const resultData = decompressedData.subarray(start, end)
|
|
439
489
|
|
|
440
490
|
switch (blockType) {
|
|
441
491
|
case 'summary': {
|
|
@@ -469,4 +519,213 @@ export class BlockView {
|
|
|
469
519
|
observer.error(e)
|
|
470
520
|
}
|
|
471
521
|
}
|
|
522
|
+
|
|
523
|
+
public async readBigWigFeaturesAsArrays(
|
|
524
|
+
blocks: { offset: number; length: number }[],
|
|
525
|
+
opts: Options = {},
|
|
526
|
+
): Promise<BigWigFeatureArrays> {
|
|
527
|
+
const { uncompressBufSize } = this
|
|
528
|
+
const { signal, request } = opts
|
|
529
|
+
const blockGroupsToFetch = groupBlocks(blocks)
|
|
530
|
+
|
|
531
|
+
const allStarts: Int32Array[] = []
|
|
532
|
+
const allEnds: Int32Array[] = []
|
|
533
|
+
const allScores: Float32Array[] = []
|
|
534
|
+
let totalCount = 0
|
|
535
|
+
|
|
536
|
+
await Promise.all(
|
|
537
|
+
blockGroupsToFetch.map(async blockGroup => {
|
|
538
|
+
const { length, offset } = blockGroup
|
|
539
|
+
const data = await this.featureCache.get(
|
|
540
|
+
`${length}_${offset}`,
|
|
541
|
+
blockGroup,
|
|
542
|
+
signal,
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
546
|
+
offset: block.offset - blockGroup.offset,
|
|
547
|
+
length: block.length,
|
|
548
|
+
}))
|
|
549
|
+
|
|
550
|
+
if (uncompressBufSize > 0) {
|
|
551
|
+
const result = await decompressAndParseBigWigBlocks(
|
|
552
|
+
data,
|
|
553
|
+
localBlocks,
|
|
554
|
+
uncompressBufSize,
|
|
555
|
+
request?.start ?? 0,
|
|
556
|
+
request?.end ?? 0,
|
|
557
|
+
)
|
|
558
|
+
if (result.starts.length > 0) {
|
|
559
|
+
allStarts.push(result.starts)
|
|
560
|
+
allEnds.push(result.ends)
|
|
561
|
+
allScores.push(result.scores)
|
|
562
|
+
totalCount += result.starts.length
|
|
563
|
+
}
|
|
564
|
+
} else {
|
|
565
|
+
for (const block of localBlocks) {
|
|
566
|
+
const blockData = data.subarray(block.offset, block.offset + block.length)
|
|
567
|
+
const features = this.parseBigWigBlock(blockData, 0, request)
|
|
568
|
+
if (features.length > 0) {
|
|
569
|
+
const starts = new Int32Array(features.length)
|
|
570
|
+
const ends = new Int32Array(features.length)
|
|
571
|
+
const scores = new Float32Array(features.length)
|
|
572
|
+
for (let i = 0; i < features.length; i++) {
|
|
573
|
+
const f = features[i]!
|
|
574
|
+
starts[i] = f.start
|
|
575
|
+
ends[i] = f.end
|
|
576
|
+
scores[i] = f.score
|
|
577
|
+
}
|
|
578
|
+
allStarts.push(starts)
|
|
579
|
+
allEnds.push(ends)
|
|
580
|
+
allScores.push(scores)
|
|
581
|
+
totalCount += features.length
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}),
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
if (allStarts.length === 0) {
|
|
589
|
+
return {
|
|
590
|
+
starts: new Int32Array(0),
|
|
591
|
+
ends: new Int32Array(0),
|
|
592
|
+
scores: new Float32Array(0),
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (allStarts.length === 1) {
|
|
597
|
+
return {
|
|
598
|
+
starts: allStarts[0]!,
|
|
599
|
+
ends: allEnds[0]!,
|
|
600
|
+
scores: allScores[0]!,
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const starts = new Int32Array(totalCount)
|
|
605
|
+
const ends = new Int32Array(totalCount)
|
|
606
|
+
const scores = new Float32Array(totalCount)
|
|
607
|
+
let offset = 0
|
|
608
|
+
for (let i = 0; i < allStarts.length; i++) {
|
|
609
|
+
starts.set(allStarts[i]!, offset)
|
|
610
|
+
ends.set(allEnds[i]!, offset)
|
|
611
|
+
scores.set(allScores[i]!, offset)
|
|
612
|
+
offset += allStarts[i]!.length
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return { starts, ends, scores }
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
public async readSummaryFeaturesAsArrays(
|
|
619
|
+
blocks: { offset: number; length: number }[],
|
|
620
|
+
opts: Options = {},
|
|
621
|
+
): Promise<SummaryFeatureArrays> {
|
|
622
|
+
const { uncompressBufSize } = this
|
|
623
|
+
const { signal, request } = opts
|
|
624
|
+
const blockGroupsToFetch = groupBlocks(blocks)
|
|
625
|
+
|
|
626
|
+
const allStarts: Int32Array[] = []
|
|
627
|
+
const allEnds: Int32Array[] = []
|
|
628
|
+
const allScores: Float32Array[] = []
|
|
629
|
+
const allMinScores: Float32Array[] = []
|
|
630
|
+
const allMaxScores: Float32Array[] = []
|
|
631
|
+
let totalCount = 0
|
|
632
|
+
|
|
633
|
+
await Promise.all(
|
|
634
|
+
blockGroupsToFetch.map(async blockGroup => {
|
|
635
|
+
const { length, offset } = blockGroup
|
|
636
|
+
const data = await this.featureCache.get(
|
|
637
|
+
`${length}_${offset}`,
|
|
638
|
+
blockGroup,
|
|
639
|
+
signal,
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
643
|
+
offset: block.offset - blockGroup.offset,
|
|
644
|
+
length: block.length,
|
|
645
|
+
}))
|
|
646
|
+
|
|
647
|
+
if (uncompressBufSize > 0) {
|
|
648
|
+
const result = await decompressAndParseSummaryBlocks(
|
|
649
|
+
data,
|
|
650
|
+
localBlocks,
|
|
651
|
+
uncompressBufSize,
|
|
652
|
+
request?.chrId ?? 0,
|
|
653
|
+
request?.start ?? 0,
|
|
654
|
+
request?.end ?? 0,
|
|
655
|
+
)
|
|
656
|
+
if (result.starts.length > 0) {
|
|
657
|
+
allStarts.push(result.starts)
|
|
658
|
+
allEnds.push(result.ends)
|
|
659
|
+
allScores.push(result.scores)
|
|
660
|
+
allMinScores.push(result.minScores)
|
|
661
|
+
allMaxScores.push(result.maxScores)
|
|
662
|
+
totalCount += result.starts.length
|
|
663
|
+
}
|
|
664
|
+
} else {
|
|
665
|
+
for (const block of localBlocks) {
|
|
666
|
+
const blockData = data.subarray(block.offset, block.offset + block.length)
|
|
667
|
+
const features = this.parseSummaryBlock(blockData, 0, request)
|
|
668
|
+
if (features.length > 0) {
|
|
669
|
+
const starts = new Int32Array(features.length)
|
|
670
|
+
const ends = new Int32Array(features.length)
|
|
671
|
+
const scores = new Float32Array(features.length)
|
|
672
|
+
const minScores = new Float32Array(features.length)
|
|
673
|
+
const maxScores = new Float32Array(features.length)
|
|
674
|
+
for (let i = 0; i < features.length; i++) {
|
|
675
|
+
const f = features[i]!
|
|
676
|
+
starts[i] = f.start
|
|
677
|
+
ends[i] = f.end
|
|
678
|
+
scores[i] = f.score ?? 0
|
|
679
|
+
minScores[i] = f.minScore ?? 0
|
|
680
|
+
maxScores[i] = f.maxScore ?? 0
|
|
681
|
+
}
|
|
682
|
+
allStarts.push(starts)
|
|
683
|
+
allEnds.push(ends)
|
|
684
|
+
allScores.push(scores)
|
|
685
|
+
allMinScores.push(minScores)
|
|
686
|
+
allMaxScores.push(maxScores)
|
|
687
|
+
totalCount += features.length
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}),
|
|
692
|
+
)
|
|
693
|
+
|
|
694
|
+
if (allStarts.length === 0) {
|
|
695
|
+
return {
|
|
696
|
+
starts: new Int32Array(0),
|
|
697
|
+
ends: new Int32Array(0),
|
|
698
|
+
scores: new Float32Array(0),
|
|
699
|
+
minScores: new Float32Array(0),
|
|
700
|
+
maxScores: new Float32Array(0),
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (allStarts.length === 1) {
|
|
705
|
+
return {
|
|
706
|
+
starts: allStarts[0]!,
|
|
707
|
+
ends: allEnds[0]!,
|
|
708
|
+
scores: allScores[0]!,
|
|
709
|
+
minScores: allMinScores[0]!,
|
|
710
|
+
maxScores: allMaxScores[0]!,
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const starts = new Int32Array(totalCount)
|
|
715
|
+
const ends = new Int32Array(totalCount)
|
|
716
|
+
const scores = new Float32Array(totalCount)
|
|
717
|
+
const minScores = new Float32Array(totalCount)
|
|
718
|
+
const maxScores = new Float32Array(totalCount)
|
|
719
|
+
let offset = 0
|
|
720
|
+
for (let i = 0; i < allStarts.length; i++) {
|
|
721
|
+
starts.set(allStarts[i]!, offset)
|
|
722
|
+
ends.set(allEnds[i]!, offset)
|
|
723
|
+
scores.set(allScores[i]!, offset)
|
|
724
|
+
minScores.set(allMinScores[i]!, offset)
|
|
725
|
+
maxScores.set(allMaxScores[i]!, offset)
|
|
726
|
+
offset += allStarts[i]!.length
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return { starts, ends, scores, minScores, maxScores }
|
|
730
|
+
}
|
|
472
731
|
}
|