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