@gmod/bbi 7.1.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +59 -0
- package/dist/bbi.d.ts +13 -3
- package/dist/bbi.js +80 -17
- package/dist/bbi.js.map +1 -1
- package/dist/bigbed.d.ts +14 -2
- package/dist/bigbed.js +116 -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 +18 -0
- package/dist/wasm/inflate-wasm-inlined.js +455 -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 +80 -17
- package/esm/bbi.js.map +1 -1
- package/esm/bigbed.d.ts +14 -2
- package/esm/bigbed.js +116 -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 +18 -0
- package/esm/wasm/inflate-wasm-inlined.js +449 -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 +17 -12
- package/src/bbi.ts +102 -20
- package/src/bigbed.ts +157 -80
- package/src/bigwig.ts +1 -2
- package/src/block-view.ts +418 -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 +86 -3
- package/src/util.ts +9 -10
- package/src/wasm/inflate-wasm-inlined.d.ts +49 -0
- package/src/wasm/inflate-wasm-inlined.js +547 -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,19 @@ 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 { BigWigFeatureArrays, SummaryFeatureArrays } from './unzip.ts'
|
|
9
14
|
import type { GenericFilehandle } from 'generic-filehandle2'
|
|
10
15
|
import type { Observer } from 'rxjs'
|
|
11
16
|
|
|
12
|
-
const decoder =
|
|
13
|
-
typeof TextDecoder !== 'undefined' ? new TextDecoder('utf8') : undefined
|
|
17
|
+
const decoder = new TextDecoder('utf8')
|
|
14
18
|
|
|
15
19
|
interface CoordRequest {
|
|
16
20
|
chrId: number
|
|
@@ -40,7 +44,9 @@ function coordFilter(s1: number, e1: number, s2: number, e2: number): boolean {
|
|
|
40
44
|
*/
|
|
41
45
|
|
|
42
46
|
export class BlockView {
|
|
43
|
-
|
|
47
|
+
// R-tree index header cache - R-trees are spatial data structures used to
|
|
48
|
+
// efficiently query genomic intervals by chromosome and position
|
|
49
|
+
private rTreePromise?: Promise<Uint8Array>
|
|
44
50
|
|
|
45
51
|
private featureCache = new AbortablePromiseCache<ReadData, Uint8Array>({
|
|
46
52
|
cache: new QuickLRU({ maxSize: 1000 }),
|
|
@@ -51,13 +57,16 @@ export class BlockView {
|
|
|
51
57
|
|
|
52
58
|
public constructor(
|
|
53
59
|
private bbi: GenericFilehandle,
|
|
54
|
-
private refsByName:
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
private refsByName: Record<string, number>,
|
|
61
|
+
// Offset to the R-tree index in the file - this is part of the "cirTree"
|
|
62
|
+
// (combined ID R-tree), which combines a B+ tree for chromosome names
|
|
63
|
+
// with an R-tree for efficient spatial queries
|
|
64
|
+
private rTreeOffset: number,
|
|
65
|
+
private uncompressBufSize: number,
|
|
57
66
|
private blockType: string,
|
|
58
67
|
) {
|
|
59
|
-
if (!(
|
|
60
|
-
throw new Error('invalid
|
|
68
|
+
if (!(rTreeOffset >= 0)) {
|
|
69
|
+
throw new Error('invalid rTreeOffset!')
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
|
|
@@ -72,101 +81,115 @@ export class BlockView {
|
|
|
72
81
|
const chrId = this.refsByName[chrName]
|
|
73
82
|
if (chrId === undefined) {
|
|
74
83
|
observer.complete()
|
|
84
|
+
return
|
|
75
85
|
}
|
|
76
86
|
const request = { chrId, start, end }
|
|
77
|
-
if (!this.
|
|
78
|
-
this.
|
|
87
|
+
if (!this.rTreePromise) {
|
|
88
|
+
this.rTreePromise = this.bbi.read(48, this.rTreeOffset, opts)
|
|
79
89
|
}
|
|
80
|
-
const buffer = await this.
|
|
81
|
-
const dataView = new DataView(
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
const buffer = await this.rTreePromise
|
|
91
|
+
const dataView = new DataView(
|
|
92
|
+
buffer.buffer,
|
|
93
|
+
buffer.byteOffset,
|
|
94
|
+
buffer.length,
|
|
95
|
+
)
|
|
96
|
+
// Maximum number of children per R-tree node - used to calculate memory bounds
|
|
97
|
+
const rTreeBlockSize = dataView.getUint32(4, true)
|
|
98
|
+
const blocksToFetch: ReadData[] = []
|
|
84
99
|
let outstanding = 0
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
// R-tree leaf nodes contain the actual data blocks to fetch
|
|
102
|
+
const processLeafNode = (
|
|
103
|
+
dataView: DataView,
|
|
104
|
+
startOffset: number,
|
|
105
|
+
count: number,
|
|
106
|
+
) => {
|
|
107
|
+
let offset = startOffset
|
|
108
|
+
for (let i = 0; i < count; i++) {
|
|
109
|
+
const startChrom = dataView.getUint32(offset, true)
|
|
110
|
+
offset += 4
|
|
111
|
+
const startBase = dataView.getUint32(offset, true)
|
|
112
|
+
offset += 4
|
|
113
|
+
const endChrom = dataView.getUint32(offset, true)
|
|
114
|
+
offset += 4
|
|
115
|
+
const endBase = dataView.getUint32(offset, true)
|
|
116
|
+
offset += 4
|
|
117
|
+
const blockOffset = Number(dataView.getBigUint64(offset, true))
|
|
118
|
+
offset += 8
|
|
119
|
+
const blockSize = Number(dataView.getBigUint64(offset, true))
|
|
120
|
+
offset += 8
|
|
121
|
+
if (
|
|
122
|
+
blockIntersectsQuery({ startChrom, startBase, endBase, endChrom })
|
|
123
|
+
) {
|
|
124
|
+
blocksToFetch.push({
|
|
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,
|
|
88
165
|
offset2: number,
|
|
89
166
|
level: number,
|
|
90
167
|
) => {
|
|
91
168
|
try {
|
|
92
|
-
const data =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
169
|
+
const data = rTreeBlockData.subarray(offset2)
|
|
170
|
+
const dataView = new DataView(
|
|
171
|
+
data.buffer,
|
|
172
|
+
data.byteOffset,
|
|
173
|
+
data.length,
|
|
174
|
+
)
|
|
96
175
|
let offset = 0
|
|
97
176
|
|
|
98
177
|
const isLeaf = dataView.getUint8(offset)
|
|
99
|
-
offset += 2 // 1 skip
|
|
100
|
-
const
|
|
178
|
+
offset += 2 // 1 skip for reserved byte
|
|
179
|
+
const count = dataView.getUint16(offset, true)
|
|
101
180
|
offset += 2
|
|
181
|
+
|
|
102
182
|
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
|
-
)
|
|
183
|
+
processLeafNode(dataView, offset, count)
|
|
135
184
|
} 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
|
-
}
|
|
185
|
+
processNonLeafNode(dataView, offset, count, level)
|
|
163
186
|
}
|
|
164
187
|
} catch (e) {
|
|
165
188
|
observer.error(e)
|
|
166
189
|
}
|
|
167
190
|
}
|
|
168
191
|
|
|
169
|
-
const
|
|
192
|
+
const blockIntersectsQuery = (b: {
|
|
170
193
|
startChrom: number
|
|
171
194
|
startBase: number
|
|
172
195
|
endChrom: number
|
|
@@ -179,22 +202,22 @@ export class BlockView {
|
|
|
179
202
|
)
|
|
180
203
|
}
|
|
181
204
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
const fetchAndProcessRTreeBlocks = async (
|
|
206
|
+
offsets: number[],
|
|
207
|
+
range: Range,
|
|
185
208
|
level: number,
|
|
186
209
|
) => {
|
|
187
210
|
try {
|
|
188
|
-
const length =
|
|
189
|
-
const offset =
|
|
211
|
+
const length = range.max - range.min
|
|
212
|
+
const offset = range.min
|
|
190
213
|
const resultBuffer = await this.featureCache.get(
|
|
191
214
|
`${length}_${offset}`,
|
|
192
215
|
{ length, offset },
|
|
193
216
|
opts?.signal,
|
|
194
217
|
)
|
|
195
|
-
for (const element of
|
|
196
|
-
if (
|
|
197
|
-
|
|
218
|
+
for (const element of offsets) {
|
|
219
|
+
if (range.contains(element)) {
|
|
220
|
+
processRTreeNode(resultBuffer, element - offset, level)
|
|
198
221
|
outstanding -= 1
|
|
199
222
|
if (outstanding === 0) {
|
|
200
223
|
this.readFeatures(observer, blocksToFetch, {
|
|
@@ -210,35 +233,40 @@ export class BlockView {
|
|
|
210
233
|
observer.error(e)
|
|
211
234
|
}
|
|
212
235
|
}
|
|
213
|
-
const
|
|
236
|
+
const traverseRTree = (offsets: number[], level: number) => {
|
|
214
237
|
try {
|
|
215
|
-
outstanding +=
|
|
238
|
+
outstanding += offsets.length
|
|
216
239
|
|
|
217
240
|
// Upper bound on size, based on a completely full leaf node.
|
|
218
|
-
const
|
|
241
|
+
const maxRTreeBlockSpan = 4 + rTreeBlockSize * 32
|
|
219
242
|
let spans = new Range([
|
|
220
243
|
{
|
|
221
|
-
min:
|
|
222
|
-
max:
|
|
244
|
+
min: offsets[0]!,
|
|
245
|
+
max: offsets[0]! + maxRTreeBlockSpan,
|
|
223
246
|
},
|
|
224
247
|
])
|
|
225
|
-
for (let i = 1; i <
|
|
248
|
+
for (let i = 1; i < offsets.length; i += 1) {
|
|
226
249
|
const blockSpan = new Range([
|
|
227
250
|
{
|
|
228
|
-
min:
|
|
229
|
-
max:
|
|
251
|
+
min: offsets[i]!,
|
|
252
|
+
max: offsets[i]! + maxRTreeBlockSpan,
|
|
230
253
|
},
|
|
231
254
|
])
|
|
232
255
|
spans = spans.union(blockSpan)
|
|
233
256
|
}
|
|
234
|
-
|
|
235
|
-
|
|
257
|
+
spans.getRanges().forEach(range => {
|
|
258
|
+
fetchAndProcessRTreeBlocks(offsets, range, level).catch(
|
|
259
|
+
(e: unknown) => {
|
|
260
|
+
observer.error(e)
|
|
261
|
+
},
|
|
262
|
+
)
|
|
263
|
+
})
|
|
236
264
|
} catch (e) {
|
|
237
265
|
observer.error(e)
|
|
238
266
|
}
|
|
239
267
|
}
|
|
240
268
|
|
|
241
|
-
|
|
269
|
+
traverseRTree([this.rTreeOffset + 48], 1)
|
|
242
270
|
return
|
|
243
271
|
} catch (e) {
|
|
244
272
|
observer.error(e)
|
|
@@ -250,13 +278,11 @@ export class BlockView {
|
|
|
250
278
|
startOffset: number,
|
|
251
279
|
request?: CoordRequest,
|
|
252
280
|
) {
|
|
253
|
-
const features
|
|
281
|
+
const features: Feature[] = []
|
|
254
282
|
let offset = startOffset
|
|
255
283
|
|
|
256
284
|
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
257
285
|
while (offset < b.byteLength) {
|
|
258
|
-
// this was extracted from looking at the runtime code generated by
|
|
259
|
-
// binary-parser
|
|
260
286
|
const chromId = dataView.getUint32(offset, true)
|
|
261
287
|
offset += 4
|
|
262
288
|
const start = dataView.getUint32(offset, true)
|
|
@@ -270,16 +296,12 @@ export class BlockView {
|
|
|
270
296
|
const maxScore = dataView.getFloat32(offset, true)
|
|
271
297
|
offset += 4
|
|
272
298
|
const sumData = dataView.getFloat32(offset, true)
|
|
273
|
-
offset +=
|
|
274
|
-
// unused
|
|
275
|
-
// const sumSqData = dataView.getFloat32(offset, true)
|
|
276
|
-
offset += 4
|
|
299
|
+
offset += 8
|
|
277
300
|
|
|
278
301
|
if (
|
|
279
|
-
request
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
: true
|
|
302
|
+
!request ||
|
|
303
|
+
(chromId === request.chrId &&
|
|
304
|
+
coordFilter(start, end, request.start, request.end))
|
|
283
305
|
) {
|
|
284
306
|
features.push({
|
|
285
307
|
start,
|
|
@@ -301,10 +323,9 @@ export class BlockView {
|
|
|
301
323
|
offset: number,
|
|
302
324
|
request?: CoordRequest,
|
|
303
325
|
) {
|
|
304
|
-
const items
|
|
326
|
+
const items: Feature[] = []
|
|
305
327
|
let currOffset = startOffset
|
|
306
|
-
const
|
|
307
|
-
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
328
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.length)
|
|
308
329
|
while (currOffset < data.byteLength) {
|
|
309
330
|
const c2 = currOffset
|
|
310
331
|
const chromId = dataView.getUint32(currOffset, true)
|
|
@@ -320,22 +341,23 @@ export class BlockView {
|
|
|
320
341
|
}
|
|
321
342
|
}
|
|
322
343
|
const b = data.subarray(currOffset, i)
|
|
323
|
-
const rest = decoder
|
|
344
|
+
const rest = decoder.decode(b)
|
|
324
345
|
currOffset = i + 1
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
+
}
|
|
332
358
|
}
|
|
333
359
|
|
|
334
|
-
return
|
|
335
|
-
? items.filter((f: any) =>
|
|
336
|
-
coordFilter(f.start, f.end, request.start, request.end),
|
|
337
|
-
)
|
|
338
|
-
: items
|
|
360
|
+
return items
|
|
339
361
|
}
|
|
340
362
|
|
|
341
363
|
private parseBigWigBlock(
|
|
@@ -358,7 +380,7 @@ export class BlockView {
|
|
|
358
380
|
offset += 2
|
|
359
381
|
const itemCount = dataView.getUint16(offset, true)
|
|
360
382
|
offset += 2
|
|
361
|
-
const items =
|
|
383
|
+
const items = []
|
|
362
384
|
switch (blockType) {
|
|
363
385
|
case 1: {
|
|
364
386
|
for (let i = 0; i < itemCount; i++) {
|
|
@@ -368,10 +390,12 @@ export class BlockView {
|
|
|
368
390
|
offset += 4
|
|
369
391
|
const score = dataView.getFloat32(offset, true)
|
|
370
392
|
offset += 4
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
393
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
394
|
+
items.push({
|
|
395
|
+
start,
|
|
396
|
+
end,
|
|
397
|
+
score,
|
|
398
|
+
})
|
|
375
399
|
}
|
|
376
400
|
}
|
|
377
401
|
break
|
|
@@ -382,10 +406,13 @@ export class BlockView {
|
|
|
382
406
|
offset += 4
|
|
383
407
|
const score = dataView.getFloat32(offset, true)
|
|
384
408
|
offset += 4
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
+
})
|
|
389
416
|
}
|
|
390
417
|
}
|
|
391
418
|
break
|
|
@@ -395,19 +422,20 @@ export class BlockView {
|
|
|
395
422
|
const score = dataView.getFloat32(offset, true)
|
|
396
423
|
offset += 4
|
|
397
424
|
const start = blockStart + i * itemStep
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
+
})
|
|
402
432
|
}
|
|
403
433
|
}
|
|
404
434
|
break
|
|
405
435
|
}
|
|
406
436
|
}
|
|
407
437
|
|
|
408
|
-
return
|
|
409
|
-
? items.filter(f => coordFilter(f.start, f.end, req.start, req.end))
|
|
410
|
-
: items
|
|
438
|
+
return items
|
|
411
439
|
}
|
|
412
440
|
|
|
413
441
|
public async readFeatures(
|
|
@@ -416,26 +444,45 @@ export class BlockView {
|
|
|
416
444
|
opts: Options = {},
|
|
417
445
|
) {
|
|
418
446
|
try {
|
|
419
|
-
const { blockType,
|
|
447
|
+
const { blockType, uncompressBufSize } = this
|
|
420
448
|
const { signal, request } = opts
|
|
421
449
|
const blockGroupsToFetch = groupBlocks(blocks)
|
|
422
|
-
checkAbortSignal(signal)
|
|
423
450
|
await Promise.all(
|
|
424
451
|
blockGroupsToFetch.map(async blockGroup => {
|
|
425
|
-
checkAbortSignal(signal)
|
|
426
452
|
const { length, offset } = blockGroup
|
|
427
453
|
const data = await this.featureCache.get(
|
|
428
454
|
`${length}_${offset}`,
|
|
429
455
|
blockGroup,
|
|
430
456
|
signal,
|
|
431
457
|
)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
458
|
+
|
|
459
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
460
|
+
offset: block.offset - blockGroup.offset,
|
|
461
|
+
length: block.length,
|
|
462
|
+
}))
|
|
463
|
+
|
|
464
|
+
let decompressedData: Uint8Array
|
|
465
|
+
let decompressedOffsets: number[]
|
|
466
|
+
|
|
467
|
+
if (uncompressBufSize > 0) {
|
|
468
|
+
const result = await unzipBatch(
|
|
469
|
+
data,
|
|
470
|
+
localBlocks,
|
|
471
|
+
uncompressBufSize,
|
|
472
|
+
)
|
|
473
|
+
decompressedData = result.data
|
|
474
|
+
decompressedOffsets = result.offsets
|
|
475
|
+
} else {
|
|
476
|
+
decompressedData = data
|
|
477
|
+
decompressedOffsets = localBlocks.map(b => b.offset)
|
|
478
|
+
decompressedOffsets.push(data.length)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
for (let i = 0; i < blockGroup.blocks.length; i++) {
|
|
482
|
+
const block = blockGroup.blocks[i]!
|
|
483
|
+
const start = decompressedOffsets[i]!
|
|
484
|
+
const end = decompressedOffsets[i + 1]!
|
|
485
|
+
const resultData = decompressedData.subarray(start, end)
|
|
439
486
|
|
|
440
487
|
switch (blockType) {
|
|
441
488
|
case 'summary': {
|
|
@@ -469,4 +516,219 @@ export class BlockView {
|
|
|
469
516
|
observer.error(e)
|
|
470
517
|
}
|
|
471
518
|
}
|
|
519
|
+
|
|
520
|
+
public async readBigWigFeaturesAsArrays(
|
|
521
|
+
blocks: { offset: number; length: number }[],
|
|
522
|
+
opts: Options = {},
|
|
523
|
+
): Promise<BigWigFeatureArrays> {
|
|
524
|
+
const { uncompressBufSize } = this
|
|
525
|
+
const { signal, request } = opts
|
|
526
|
+
const blockGroupsToFetch = groupBlocks(blocks)
|
|
527
|
+
|
|
528
|
+
const allStarts: Int32Array[] = []
|
|
529
|
+
const allEnds: Int32Array[] = []
|
|
530
|
+
const allScores: Float32Array[] = []
|
|
531
|
+
let totalCount = 0
|
|
532
|
+
|
|
533
|
+
await Promise.all(
|
|
534
|
+
blockGroupsToFetch.map(async blockGroup => {
|
|
535
|
+
const { length, offset } = blockGroup
|
|
536
|
+
const data = await this.featureCache.get(
|
|
537
|
+
`${length}_${offset}`,
|
|
538
|
+
blockGroup,
|
|
539
|
+
signal,
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
543
|
+
offset: block.offset - blockGroup.offset,
|
|
544
|
+
length: block.length,
|
|
545
|
+
}))
|
|
546
|
+
|
|
547
|
+
if (uncompressBufSize > 0) {
|
|
548
|
+
const result = await decompressAndParseBigWigBlocks(
|
|
549
|
+
data,
|
|
550
|
+
localBlocks,
|
|
551
|
+
uncompressBufSize,
|
|
552
|
+
request?.start ?? 0,
|
|
553
|
+
request?.end ?? 0,
|
|
554
|
+
)
|
|
555
|
+
if (result.starts.length > 0) {
|
|
556
|
+
allStarts.push(result.starts)
|
|
557
|
+
allEnds.push(result.ends)
|
|
558
|
+
allScores.push(result.scores)
|
|
559
|
+
totalCount += result.starts.length
|
|
560
|
+
}
|
|
561
|
+
} else {
|
|
562
|
+
for (const block of localBlocks) {
|
|
563
|
+
const blockData = data.subarray(
|
|
564
|
+
block.offset,
|
|
565
|
+
block.offset + block.length,
|
|
566
|
+
)
|
|
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(
|
|
667
|
+
block.offset,
|
|
668
|
+
block.offset + block.length,
|
|
669
|
+
)
|
|
670
|
+
const features = this.parseSummaryBlock(blockData, 0, request)
|
|
671
|
+
if (features.length > 0) {
|
|
672
|
+
const starts = new Int32Array(features.length)
|
|
673
|
+
const ends = new Int32Array(features.length)
|
|
674
|
+
const scores = new Float32Array(features.length)
|
|
675
|
+
const minScores = new Float32Array(features.length)
|
|
676
|
+
const maxScores = new Float32Array(features.length)
|
|
677
|
+
for (let i = 0; i < features.length; i++) {
|
|
678
|
+
const f = features[i]!
|
|
679
|
+
starts[i] = f.start
|
|
680
|
+
ends[i] = f.end
|
|
681
|
+
scores[i] = f.score ?? 0
|
|
682
|
+
minScores[i] = f.minScore ?? 0
|
|
683
|
+
maxScores[i] = f.maxScore ?? 0
|
|
684
|
+
}
|
|
685
|
+
allStarts.push(starts)
|
|
686
|
+
allEnds.push(ends)
|
|
687
|
+
allScores.push(scores)
|
|
688
|
+
allMinScores.push(minScores)
|
|
689
|
+
allMaxScores.push(maxScores)
|
|
690
|
+
totalCount += features.length
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}),
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
if (allStarts.length === 0) {
|
|
698
|
+
return {
|
|
699
|
+
starts: new Int32Array(0),
|
|
700
|
+
ends: new Int32Array(0),
|
|
701
|
+
scores: new Float32Array(0),
|
|
702
|
+
minScores: new Float32Array(0),
|
|
703
|
+
maxScores: new Float32Array(0),
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (allStarts.length === 1) {
|
|
708
|
+
return {
|
|
709
|
+
starts: allStarts[0]!,
|
|
710
|
+
ends: allEnds[0]!,
|
|
711
|
+
scores: allScores[0]!,
|
|
712
|
+
minScores: allMinScores[0]!,
|
|
713
|
+
maxScores: allMaxScores[0]!,
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const starts = new Int32Array(totalCount)
|
|
718
|
+
const ends = new Int32Array(totalCount)
|
|
719
|
+
const scores = new Float32Array(totalCount)
|
|
720
|
+
const minScores = new Float32Array(totalCount)
|
|
721
|
+
const maxScores = new Float32Array(totalCount)
|
|
722
|
+
let offset = 0
|
|
723
|
+
for (let i = 0; i < allStarts.length; i++) {
|
|
724
|
+
starts.set(allStarts[i]!, offset)
|
|
725
|
+
ends.set(allEnds[i]!, offset)
|
|
726
|
+
scores.set(allScores[i]!, offset)
|
|
727
|
+
minScores.set(allMinScores[i]!, offset)
|
|
728
|
+
maxScores.set(allMaxScores[i]!, offset)
|
|
729
|
+
offset += allStarts[i]!.length
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return { starts, ends, scores, minScores, maxScores }
|
|
733
|
+
}
|
|
472
734
|
}
|