@gmod/bbi 8.1.1 → 9.0.0

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