@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/esm/block-view.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import AbortablePromiseCache from '@gmod/abortable-promise-cache';
2
2
  import QuickLRU from '@jbrowse/quick-lru';
3
- import Range from "./range.js";
3
+ import { mergeRanges } from "./range.js";
4
4
  import { decompressAndParseBigWigBlocks, decompressAndParseSummaryBlocks, unzipBatch, } from "./unzip.js";
5
5
  import { groupBlocks } from "./util.js";
6
6
  const decoder = new TextDecoder('utf8');
@@ -8,6 +8,233 @@ const CIR_TREE_MAGIC = 0x2468ace0;
8
8
  function coordFilter(s1, e1, s2, e2) {
9
9
  return s1 < e2 && e1 >= s2;
10
10
  }
11
+ function parseSummaryBlock(b, startOffset, request) {
12
+ const features = [];
13
+ let offset = startOffset;
14
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length);
15
+ while (offset < b.byteLength) {
16
+ const chromId = dataView.getUint32(offset, true);
17
+ offset += 4;
18
+ const start = dataView.getUint32(offset, true);
19
+ offset += 4;
20
+ const end = dataView.getUint32(offset, true);
21
+ offset += 4;
22
+ const validCnt = dataView.getUint32(offset, true);
23
+ offset += 4;
24
+ const minScore = dataView.getFloat32(offset, true);
25
+ offset += 4;
26
+ const maxScore = dataView.getFloat32(offset, true);
27
+ offset += 4;
28
+ const sumData = dataView.getFloat32(offset, true);
29
+ offset += 8;
30
+ if (!request ||
31
+ (chromId === request.chrId &&
32
+ coordFilter(start, end, request.start, request.end))) {
33
+ features.push({
34
+ start,
35
+ end,
36
+ maxScore,
37
+ minScore,
38
+ summary: true,
39
+ score: sumData / (validCnt || 1),
40
+ });
41
+ }
42
+ }
43
+ return features;
44
+ }
45
+ function parseBigBedBlock(data, startOffset, offset, request) {
46
+ const items = [];
47
+ let currOffset = startOffset;
48
+ const dataView = new DataView(data.buffer, data.byteOffset, data.length);
49
+ while (currOffset < data.byteLength) {
50
+ const c2 = currOffset;
51
+ const chromId = dataView.getUint32(currOffset, true);
52
+ currOffset += 4;
53
+ const start = dataView.getInt32(currOffset, true);
54
+ currOffset += 4;
55
+ const end = dataView.getInt32(currOffset, true);
56
+ currOffset += 4;
57
+ let i = currOffset;
58
+ for (; i < data.length; i++) {
59
+ if (data[i] === 0) {
60
+ break;
61
+ }
62
+ }
63
+ const b = data.subarray(currOffset, i);
64
+ const rest = decoder.decode(b);
65
+ currOffset = i + 1;
66
+ if (!request ||
67
+ (chromId === request.chrId &&
68
+ coordFilter(start, end, request.start, request.end))) {
69
+ items.push({
70
+ start,
71
+ end,
72
+ rest,
73
+ uniqueId: `bb-${offset + c2}`,
74
+ });
75
+ }
76
+ }
77
+ return items;
78
+ }
79
+ function parseBigWigBlock(buffer, startOffset, req) {
80
+ const b = buffer.subarray(startOffset);
81
+ const dataView = new DataView(b.buffer, b.byteOffset, b.length);
82
+ const blockStart = dataView.getInt32(4, true);
83
+ const itemStep = dataView.getUint32(12, true);
84
+ const itemSpan = dataView.getUint32(16, true);
85
+ const blockType = dataView.getUint8(20);
86
+ const itemCount = dataView.getUint16(22, true);
87
+ let offset = 24;
88
+ const items = [];
89
+ switch (blockType) {
90
+ case 1: {
91
+ for (let i = 0; i < itemCount; i++) {
92
+ const start = dataView.getInt32(offset, true);
93
+ offset += 4;
94
+ const end = dataView.getInt32(offset, true);
95
+ offset += 4;
96
+ const score = dataView.getFloat32(offset, true);
97
+ offset += 4;
98
+ if (!req || coordFilter(start, end, req.start, req.end)) {
99
+ items.push({ start, end, score });
100
+ }
101
+ }
102
+ break;
103
+ }
104
+ case 2: {
105
+ for (let i = 0; i < itemCount; i++) {
106
+ const start = dataView.getInt32(offset, true);
107
+ offset += 4;
108
+ const score = dataView.getFloat32(offset, true);
109
+ offset += 4;
110
+ const end = start + itemSpan;
111
+ if (!req || coordFilter(start, end, req.start, req.end)) {
112
+ items.push({ score, start, end });
113
+ }
114
+ }
115
+ break;
116
+ }
117
+ case 3: {
118
+ for (let i = 0; i < itemCount; i++) {
119
+ const score = dataView.getFloat32(offset, true);
120
+ offset += 4;
121
+ const start = blockStart + i * itemStep;
122
+ const end = start + itemSpan;
123
+ if (!req || coordFilter(start, end, req.start, req.end)) {
124
+ items.push({ score, start, end });
125
+ }
126
+ }
127
+ break;
128
+ }
129
+ }
130
+ return items;
131
+ }
132
+ function parseBigWigBlockAsArrays(buffer, startOffset, req) {
133
+ const dataView = new DataView(buffer.buffer, buffer.byteOffset + startOffset, buffer.length - startOffset);
134
+ const blockStart = dataView.getInt32(4, true);
135
+ const itemStep = dataView.getUint32(12, true);
136
+ const itemSpan = dataView.getUint32(16, true);
137
+ const blockType = dataView.getUint8(20);
138
+ const itemCount = dataView.getUint16(22, true);
139
+ const starts = new Int32Array(itemCount);
140
+ const ends = new Int32Array(itemCount);
141
+ const scores = new Float32Array(itemCount);
142
+ if (!req) {
143
+ switch (blockType) {
144
+ case 1: {
145
+ let offset = 24;
146
+ for (let i = 0; i < itemCount; i++) {
147
+ starts[i] = dataView.getInt32(offset, true);
148
+ ends[i] = dataView.getInt32(offset + 4, true);
149
+ scores[i] = dataView.getFloat32(offset + 8, true);
150
+ offset += 12;
151
+ }
152
+ return { starts, ends, scores };
153
+ }
154
+ case 2: {
155
+ let offset = 24;
156
+ for (let i = 0; i < itemCount; i++) {
157
+ const start = dataView.getInt32(offset, true);
158
+ starts[i] = start;
159
+ ends[i] = start + itemSpan;
160
+ scores[i] = dataView.getFloat32(offset + 4, true);
161
+ offset += 8;
162
+ }
163
+ return { starts, ends, scores };
164
+ }
165
+ case 3: {
166
+ let offset = 24;
167
+ for (let i = 0; i < itemCount; i++) {
168
+ const start = blockStart + i * itemStep;
169
+ starts[i] = start;
170
+ ends[i] = start + itemSpan;
171
+ scores[i] = dataView.getFloat32(offset, true);
172
+ offset += 4;
173
+ }
174
+ return { starts, ends, scores };
175
+ }
176
+ }
177
+ return { starts, ends, scores };
178
+ }
179
+ const reqStart = req.start;
180
+ const reqEnd = req.end;
181
+ let idx = 0;
182
+ switch (blockType) {
183
+ case 1: {
184
+ let offset = 24;
185
+ for (let i = 0; i < itemCount; i++) {
186
+ const start = dataView.getInt32(offset, true);
187
+ const end = dataView.getInt32(offset + 4, true);
188
+ if (start < reqEnd && end >= reqStart) {
189
+ starts[idx] = start;
190
+ ends[idx] = end;
191
+ scores[idx] = dataView.getFloat32(offset + 8, true);
192
+ idx++;
193
+ }
194
+ offset += 12;
195
+ }
196
+ break;
197
+ }
198
+ case 2: {
199
+ let offset = 24;
200
+ for (let i = 0; i < itemCount; i++) {
201
+ const start = dataView.getInt32(offset, true);
202
+ const end = start + itemSpan;
203
+ if (start < reqEnd && end >= reqStart) {
204
+ starts[idx] = start;
205
+ ends[idx] = end;
206
+ scores[idx] = dataView.getFloat32(offset + 4, true);
207
+ idx++;
208
+ }
209
+ offset += 8;
210
+ }
211
+ break;
212
+ }
213
+ case 3: {
214
+ let offset = 24;
215
+ for (let i = 0; i < itemCount; i++) {
216
+ const start = blockStart + i * itemStep;
217
+ const end = start + itemSpan;
218
+ if (start < reqEnd && end >= reqStart) {
219
+ starts[idx] = start;
220
+ ends[idx] = end;
221
+ scores[idx] = dataView.getFloat32(offset, true);
222
+ idx++;
223
+ }
224
+ offset += 4;
225
+ }
226
+ break;
227
+ }
228
+ }
229
+ if (idx < itemCount) {
230
+ return {
231
+ starts: starts.subarray(0, idx),
232
+ ends: ends.subarray(0, idx),
233
+ scores: scores.subarray(0, idx),
234
+ };
235
+ }
236
+ return { starts, ends, scores };
237
+ }
11
238
  /**
12
239
  * View into a subset of the data in a BigWig file.
13
240
  *
@@ -41,459 +268,158 @@ export class BlockView {
41
268
  throw new Error('invalid rTreeOffset!');
42
269
  }
43
270
  }
44
- async readWigData(chrName, start, end, observer, opts) {
45
- try {
46
- const chrId = this.refsByName[chrName];
47
- if (chrId === undefined) {
48
- observer.complete();
49
- return;
50
- }
51
- const request = { chrId, start, end };
52
- if (!this.rTreePromise) {
53
- this.rTreePromise = this.bbi.read(48, this.rTreeOffset, opts);
54
- }
55
- const buffer = await this.rTreePromise;
56
- const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
57
- const magic = dataView.getUint32(0, true);
58
- if (magic !== CIR_TREE_MAGIC) {
59
- throw new Error(`invalid cirTree magic: 0x${magic.toString(16)} (expected 0x${CIR_TREE_MAGIC.toString(16)}) at offset ${this.rTreeOffset}, file may be corrupt or unsupported`);
60
- }
61
- const rTreeBlockSize = dataView.getUint32(4, true);
62
- const blocksToFetch = [];
63
- let outstanding = 0;
64
- // R-tree leaf nodes contain the actual data blocks to fetch
65
- const processLeafNode = (dataView, startOffset, count) => {
66
- let offset = startOffset;
67
- for (let i = 0; i < count; i++) {
68
- const startChrom = dataView.getUint32(offset, true);
69
- offset += 4;
70
- const startBase = dataView.getUint32(offset, true);
71
- offset += 4;
72
- const endChrom = dataView.getUint32(offset, true);
73
- offset += 4;
74
- const endBase = dataView.getUint32(offset, true);
75
- offset += 4;
76
- const blockOffset = Number(dataView.getBigUint64(offset, true));
77
- offset += 8;
78
- const blockSize = Number(dataView.getBigUint64(offset, true));
79
- offset += 8;
80
- if (blockIntersectsQuery({ startChrom, startBase, endBase, endChrom })) {
81
- blocksToFetch.push({
82
- offset: blockOffset,
83
- length: blockSize,
84
- });
85
- }
86
- }
87
- };
88
- // R-tree non-leaf nodes contain pointers to child nodes
89
- const processNonLeafNode = (dataView, startOffset, count, level) => {
90
- const recurOffsets = [];
91
- let offset = startOffset;
92
- for (let i = 0; i < count; i++) {
93
- const startChrom = dataView.getUint32(offset, true);
94
- offset += 4;
95
- const startBase = dataView.getUint32(offset, true);
96
- offset += 4;
97
- const endChrom = dataView.getUint32(offset, true);
98
- offset += 4;
99
- const endBase = dataView.getUint32(offset, true);
100
- offset += 4;
101
- const blockOffset = Number(dataView.getBigUint64(offset, true));
102
- offset += 8;
103
- if (blockIntersectsQuery({ startChrom, startBase, endChrom, endBase })) {
104
- recurOffsets.push(blockOffset);
105
- }
106
- }
107
- if (recurOffsets.length > 0) {
108
- traverseRTree(recurOffsets, level + 1);
109
- }
110
- };
111
- const processRTreeNode = (rTreeBlockData, offset2, level) => {
112
- try {
113
- const data = rTreeBlockData.subarray(offset2);
114
- const dataView = new DataView(data.buffer, data.byteOffset, data.length);
115
- let offset = 0;
116
- const isLeaf = dataView.getUint8(offset);
117
- offset += 2; // 1 skip for reserved byte
118
- const count = dataView.getUint16(offset, true);
119
- offset += 2;
120
- if (isLeaf === 1) {
121
- processLeafNode(dataView, offset, count);
122
- }
123
- else if (isLeaf === 0) {
124
- processNonLeafNode(dataView, offset, count, level);
125
- }
126
- }
127
- catch (e) {
128
- observer.error(e);
129
- }
130
- };
131
- const blockIntersectsQuery = (b) => {
132
- const { startChrom, startBase, endChrom, endBase } = b;
133
- return ((startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
134
- (endChrom > chrId || (endChrom === chrId && endBase >= start)));
135
- };
136
- const fetchAndProcessRTreeBlocks = async (offsets, range, level) => {
137
- try {
138
- const length = range.max - range.min;
139
- const offset = range.min;
140
- const resultBuffer = await this.featureCache.get(`${length}_${offset}`, { length, offset }, opts?.signal);
141
- for (const element of offsets) {
142
- if (range.contains(element)) {
143
- processRTreeNode(resultBuffer, element - offset, level);
144
- outstanding -= 1;
145
- if (outstanding === 0) {
146
- this.readFeatures(observer, blocksToFetch, {
147
- ...opts,
148
- request,
149
- }).catch((e) => {
150
- observer.error(e);
151
- });
271
+ async _collectBlocks(chrName, start, end, opts) {
272
+ const chrId = this.refsByName[chrName];
273
+ if (chrId === undefined) {
274
+ return undefined;
275
+ }
276
+ if (!this.rTreePromise) {
277
+ this.rTreePromise = this.bbi.read(48, this.rTreeOffset, opts);
278
+ }
279
+ const buffer = await this.rTreePromise;
280
+ const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
281
+ const magic = dataView.getUint32(0, true);
282
+ if (magic !== CIR_TREE_MAGIC) {
283
+ throw new Error(`invalid cirTree magic: 0x${magic.toString(16)} (expected 0x${CIR_TREE_MAGIC.toString(16)}) at offset ${this.rTreeOffset}, file may be corrupt or unsupported`);
284
+ }
285
+ const rTreeBlockSize = dataView.getUint32(4, true);
286
+ // Upper bound on size, based on a completely full leaf node.
287
+ const maxRTreeBlockSpan = 4 + rTreeBlockSize * 32;
288
+ const blockIntersectsQuery = (startChrom, startBase, endChrom, endBase) => (startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
289
+ (endChrom > chrId || (endChrom === chrId && endBase >= start));
290
+ const blocks = [];
291
+ let currentOffsets = [this.rTreeOffset + 48];
292
+ while (currentOffsets.length > 0) {
293
+ const spans = mergeRanges(currentOffsets.map(o => ({ min: o, max: o + maxRTreeBlockSpan })));
294
+ const nextOffsets = [];
295
+ for (const { min, max } of spans) {
296
+ const length = max - min;
297
+ const offset = min;
298
+ const resultBuffer = await this.featureCache.get(`${length}_${offset}`, { length, offset }, opts?.signal);
299
+ for (const element of currentOffsets) {
300
+ if (min <= element && element <= max) {
301
+ const data = resultBuffer.subarray(element - offset);
302
+ const dv = new DataView(data.buffer, data.byteOffset, data.length);
303
+ const isLeaf = dv.getUint8(0);
304
+ const count = dv.getUint16(2, true);
305
+ let nodeOffset = 4;
306
+ if (isLeaf === 1) {
307
+ for (let i = 0; i < count; i++) {
308
+ const startChrom = dv.getUint32(nodeOffset, true);
309
+ const startBase = dv.getUint32(nodeOffset + 4, true);
310
+ const endChrom = dv.getUint32(nodeOffset + 8, true);
311
+ const endBase = dv.getUint32(nodeOffset + 12, true);
312
+ const blockOffset = Number(dv.getBigUint64(nodeOffset + 16, true));
313
+ const blockSize = Number(dv.getBigUint64(nodeOffset + 24, true));
314
+ nodeOffset += 32;
315
+ if (blockIntersectsQuery(startChrom, startBase, endChrom, endBase)) {
316
+ blocks.push({ offset: blockOffset, length: blockSize });
317
+ }
318
+ }
319
+ }
320
+ else if (isLeaf === 0) {
321
+ for (let i = 0; i < count; i++) {
322
+ const startChrom = dv.getUint32(nodeOffset, true);
323
+ const startBase = dv.getUint32(nodeOffset + 4, true);
324
+ const endChrom = dv.getUint32(nodeOffset + 8, true);
325
+ const endBase = dv.getUint32(nodeOffset + 12, true);
326
+ const childOffset = Number(dv.getBigUint64(nodeOffset + 16, true));
327
+ nodeOffset += 24;
328
+ if (blockIntersectsQuery(startChrom, startBase, endChrom, endBase)) {
329
+ nextOffsets.push(childOffset);
330
+ }
152
331
  }
153
332
  }
154
333
  }
155
334
  }
156
- catch (e) {
157
- observer.error(e);
158
- }
159
- };
160
- const traverseRTree = (offsets, level) => {
161
- try {
162
- outstanding += offsets.length;
163
- // Upper bound on size, based on a completely full leaf node.
164
- const maxRTreeBlockSpan = 4 + rTreeBlockSize * 32;
165
- let spans = new Range([
166
- {
167
- min: offsets[0],
168
- max: offsets[0] + maxRTreeBlockSpan,
169
- },
170
- ]);
171
- for (let i = 1; i < offsets.length; i += 1) {
172
- const blockSpan = new Range([
173
- {
174
- min: offsets[i],
175
- max: offsets[i] + maxRTreeBlockSpan,
176
- },
177
- ]);
178
- spans = spans.union(blockSpan);
179
- }
180
- spans.getRanges().forEach(range => {
181
- fetchAndProcessRTreeBlocks(offsets, range, level).catch((e) => {
182
- observer.error(e);
183
- });
184
- });
185
- }
186
- catch (e) {
187
- observer.error(e);
188
- }
189
- };
190
- traverseRTree([this.rTreeOffset + 48], 1);
191
- return;
192
- }
193
- catch (e) {
194
- observer.error(e);
195
- }
196
- }
197
- parseSummaryBlock(b, startOffset, request) {
198
- const features = [];
199
- let offset = startOffset;
200
- const dataView = new DataView(b.buffer, b.byteOffset, b.length);
201
- while (offset < b.byteLength) {
202
- const chromId = dataView.getUint32(offset, true);
203
- offset += 4;
204
- const start = dataView.getUint32(offset, true);
205
- offset += 4;
206
- const end = dataView.getUint32(offset, true);
207
- offset += 4;
208
- const validCnt = dataView.getUint32(offset, true);
209
- offset += 4;
210
- const minScore = dataView.getFloat32(offset, true);
211
- offset += 4;
212
- const maxScore = dataView.getFloat32(offset, true);
213
- offset += 4;
214
- const sumData = dataView.getFloat32(offset, true);
215
- offset += 8;
216
- if (!request ||
217
- (chromId === request.chrId &&
218
- coordFilter(start, end, request.start, request.end))) {
219
- features.push({
220
- start,
221
- end,
222
- maxScore,
223
- minScore,
224
- summary: true,
225
- score: sumData / (validCnt || 1),
226
- });
227
335
  }
336
+ currentOffsets = nextOffsets;
228
337
  }
229
- return features;
338
+ return { blocks, chrId };
230
339
  }
231
- parseBigBedBlock(data, startOffset, offset, request) {
232
- const items = [];
233
- let currOffset = startOffset;
234
- const dataView = new DataView(data.buffer, data.byteOffset, data.length);
235
- while (currOffset < data.byteLength) {
236
- const c2 = currOffset;
237
- const chromId = dataView.getUint32(currOffset, true);
238
- currOffset += 4;
239
- const start = dataView.getInt32(currOffset, true);
240
- currOffset += 4;
241
- const end = dataView.getInt32(currOffset, true);
242
- currOffset += 4;
243
- let i = currOffset;
244
- for (; i < data.length; i++) {
245
- if (data[i] === 0) {
246
- break;
247
- }
248
- }
249
- const b = data.subarray(currOffset, i);
250
- const rest = decoder.decode(b);
251
- currOffset = i + 1;
252
- if (!request ||
253
- (chromId === request.chrId &&
254
- coordFilter(start, end, request.start, request.end))) {
255
- items.push({
256
- start,
257
- end,
258
- rest,
259
- uniqueId: `bb-${offset + c2}`,
260
- });
261
- }
340
+ async readWigData(chrName, start, end, opts) {
341
+ const collected = await this._collectBlocks(chrName, start, end, opts);
342
+ if (!collected) {
343
+ return [];
262
344
  }
263
- return items;
345
+ const { blocks, chrId } = collected;
346
+ return this.readFeatures(blocks, {
347
+ ...opts,
348
+ request: { chrId, start, end },
349
+ });
264
350
  }
265
- parseBigWigBlock(buffer, startOffset, req) {
266
- const b = buffer.subarray(startOffset);
267
- const dataView = new DataView(b.buffer, b.byteOffset, b.length);
268
- let offset = 0;
269
- offset += 4;
270
- const blockStart = dataView.getInt32(offset, true);
271
- offset += 8;
272
- const itemStep = dataView.getUint32(offset, true);
273
- offset += 4;
274
- const itemSpan = dataView.getUint32(offset, true);
275
- offset += 4;
276
- const blockType = dataView.getUint8(offset);
277
- offset += 2;
278
- const itemCount = dataView.getUint16(offset, true);
279
- offset += 2;
280
- const items = [];
281
- switch (blockType) {
282
- case 1: {
283
- for (let i = 0; i < itemCount; i++) {
284
- const start = dataView.getInt32(offset, true);
285
- offset += 4;
286
- const end = dataView.getInt32(offset, true);
287
- offset += 4;
288
- const score = dataView.getFloat32(offset, true);
289
- offset += 4;
290
- if (!req || coordFilter(start, end, req.start, req.end)) {
291
- items.push({
292
- start,
293
- end,
294
- score,
295
- });
296
- }
297
- }
298
- break;
299
- }
300
- case 2: {
301
- for (let i = 0; i < itemCount; i++) {
302
- const start = dataView.getInt32(offset, true);
303
- offset += 4;
304
- const score = dataView.getFloat32(offset, true);
305
- offset += 4;
306
- const end = start + itemSpan;
307
- if (!req || coordFilter(start, end, req.start, req.end)) {
308
- items.push({
309
- score,
310
- start,
311
- end,
312
- });
313
- }
314
- }
315
- break;
316
- }
317
- case 3: {
318
- for (let i = 0; i < itemCount; i++) {
319
- const score = dataView.getFloat32(offset, true);
320
- offset += 4;
321
- const start = blockStart + i * itemStep;
322
- const end = start + itemSpan;
323
- if (!req || coordFilter(start, end, req.start, req.end)) {
324
- items.push({
325
- score,
326
- start,
327
- end,
328
- });
329
- }
330
- }
331
- break;
332
- }
351
+ async readWigDataAsArrays(chrName, start, end, opts) {
352
+ const collected = await this._collectBlocks(chrName, start, end, opts);
353
+ const blocks = collected?.blocks ?? [];
354
+ const request = collected
355
+ ? { chrId: collected.chrId, start, end }
356
+ : undefined;
357
+ const optsWithReq = { ...opts, request };
358
+ if (this.blockType === 'summary') {
359
+ return this._readSummaryFeaturesAsArrays(blocks, optsWithReq);
333
360
  }
334
- return items;
361
+ return this._readBigWigFeaturesAsArrays(blocks, optsWithReq);
335
362
  }
336
- parseBigWigBlockAsArrays(buffer, startOffset, req) {
337
- const dataView = new DataView(buffer.buffer, buffer.byteOffset + startOffset, buffer.length - startOffset);
338
- const blockStart = dataView.getInt32(4, true);
339
- const itemStep = dataView.getUint32(12, true);
340
- const itemSpan = dataView.getUint32(16, true);
341
- const blockType = dataView.getUint8(20);
342
- const itemCount = dataView.getUint16(22, true);
343
- const starts = new Int32Array(itemCount);
344
- const ends = new Int32Array(itemCount);
345
- const scores = new Float32Array(itemCount);
346
- if (!req) {
347
- switch (blockType) {
348
- case 1: {
349
- let offset = 24;
350
- for (let i = 0; i < itemCount; i++) {
351
- starts[i] = dataView.getInt32(offset, true);
352
- ends[i] = dataView.getInt32(offset + 4, true);
353
- scores[i] = dataView.getFloat32(offset + 8, true);
354
- offset += 12;
355
- }
356
- return { starts, ends, scores };
357
- }
358
- case 2: {
359
- let offset = 24;
360
- for (let i = 0; i < itemCount; i++) {
361
- const start = dataView.getInt32(offset, true);
362
- starts[i] = start;
363
- ends[i] = start + itemSpan;
364
- scores[i] = dataView.getFloat32(offset + 4, true);
365
- offset += 8;
366
- }
367
- return { starts, ends, scores };
368
- }
369
- case 3: {
370
- let offset = 24;
371
- for (let i = 0; i < itemCount; i++) {
372
- const start = blockStart + i * itemStep;
373
- starts[i] = start;
374
- ends[i] = start + itemSpan;
375
- scores[i] = dataView.getFloat32(offset, true);
376
- offset += 4;
377
- }
378
- return { starts, ends, scores };
363
+ async readFeatures(blocks, opts = {}) {
364
+ const { blockType, uncompressBufSize } = this;
365
+ const { signal, request } = opts;
366
+ const blockGroupsToFetch = groupBlocks(blocks);
367
+ const allFeatures = [];
368
+ for (const blockGroup of blockGroupsToFetch) {
369
+ const data = await this.bbi.read(blockGroup.length, blockGroup.offset, {
370
+ signal,
371
+ });
372
+ const groupOffset = blockGroup.offset;
373
+ const subBlocks = blockGroup.blocks;
374
+ let decompressedData;
375
+ let decompressedOffsets;
376
+ if (uncompressBufSize > 0) {
377
+ const localBlocks = [];
378
+ for (const block of subBlocks) {
379
+ localBlocks.push({
380
+ offset: block.offset - groupOffset,
381
+ length: block.length,
382
+ });
379
383
  }
384
+ const result = await unzipBatch(data, localBlocks, uncompressBufSize);
385
+ decompressedData = result.data;
386
+ decompressedOffsets = result.offsets;
380
387
  }
381
- return { starts, ends, scores };
382
- }
383
- const reqStart = req.start;
384
- const reqEnd = req.end;
385
- let idx = 0;
386
- switch (blockType) {
387
- case 1: {
388
- let offset = 24;
389
- for (let i = 0; i < itemCount; i++) {
390
- const start = dataView.getInt32(offset, true);
391
- const end = dataView.getInt32(offset + 4, true);
392
- if (start < reqEnd && end >= reqStart) {
393
- starts[idx] = start;
394
- ends[idx] = end;
395
- scores[idx] = dataView.getFloat32(offset + 8, true);
396
- idx++;
397
- }
398
- offset += 12;
388
+ else {
389
+ decompressedData = data;
390
+ decompressedOffsets = [];
391
+ for (const block of subBlocks) {
392
+ decompressedOffsets.push(block.offset - groupOffset);
399
393
  }
400
- break;
394
+ decompressedOffsets.push(data.length);
401
395
  }
402
- case 2: {
403
- let offset = 24;
404
- for (let i = 0; i < itemCount; i++) {
405
- const start = dataView.getInt32(offset, true);
406
- const end = start + itemSpan;
407
- if (start < reqEnd && end >= reqStart) {
408
- starts[idx] = start;
409
- ends[idx] = end;
410
- scores[idx] = dataView.getFloat32(offset + 4, true);
411
- idx++;
412
- }
413
- offset += 8;
396
+ for (let i = 0; i < subBlocks.length; i++) {
397
+ const start = decompressedOffsets[i];
398
+ const end = decompressedOffsets[i + 1];
399
+ const resultData = decompressedData.subarray(start, end);
400
+ let features;
401
+ switch (blockType) {
402
+ case 'summary':
403
+ features = parseSummaryBlock(resultData, 0, request);
404
+ break;
405
+ case 'bigwig':
406
+ features = parseBigWigBlock(resultData, 0, request);
407
+ break;
408
+ case 'bigbed':
409
+ features = parseBigBedBlock(resultData, 0, subBlocks[i].offset * (1 << 8), request);
410
+ break;
411
+ default:
412
+ features = [];
413
+ console.warn(`Don't know what to do with ${blockType}`);
414
414
  }
415
- break;
416
- }
417
- case 3: {
418
- let offset = 24;
419
- for (let i = 0; i < itemCount; i++) {
420
- const start = blockStart + i * itemStep;
421
- const end = start + itemSpan;
422
- if (start < reqEnd && end >= reqStart) {
423
- starts[idx] = start;
424
- ends[idx] = end;
425
- scores[idx] = dataView.getFloat32(offset, true);
426
- idx++;
427
- }
428
- offset += 4;
415
+ for (const f of features) {
416
+ allFeatures.push(f);
429
417
  }
430
- break;
431
418
  }
432
419
  }
433
- if (idx < itemCount) {
434
- return {
435
- starts: starts.subarray(0, idx),
436
- ends: ends.subarray(0, idx),
437
- scores: scores.subarray(0, idx),
438
- };
439
- }
440
- return { starts, ends, scores };
441
- }
442
- async readFeatures(observer, blocks, opts = {}) {
443
- try {
444
- const { blockType, uncompressBufSize } = this;
445
- const { signal, request } = opts;
446
- const blockGroupsToFetch = groupBlocks(blocks);
447
- await Promise.all(blockGroupsToFetch.map(async (blockGroup) => {
448
- const { length, offset } = blockGroup;
449
- const data = await this.featureCache.get(`${length}_${offset}`, blockGroup, signal);
450
- const localBlocks = blockGroup.blocks.map(block => ({
451
- offset: block.offset - blockGroup.offset,
452
- length: block.length,
453
- }));
454
- let decompressedData;
455
- let decompressedOffsets;
456
- if (uncompressBufSize > 0) {
457
- const result = await unzipBatch(data, localBlocks, uncompressBufSize);
458
- decompressedData = result.data;
459
- decompressedOffsets = result.offsets;
460
- }
461
- else {
462
- decompressedData = data;
463
- decompressedOffsets = localBlocks.map(b => b.offset);
464
- decompressedOffsets.push(data.length);
465
- }
466
- for (let i = 0; i < blockGroup.blocks.length; i++) {
467
- const block = blockGroup.blocks[i];
468
- const start = decompressedOffsets[i];
469
- const end = decompressedOffsets[i + 1];
470
- const resultData = decompressedData.subarray(start, end);
471
- switch (blockType) {
472
- case 'summary': {
473
- observer.next(this.parseSummaryBlock(resultData, 0, request));
474
- break;
475
- }
476
- case 'bigwig': {
477
- observer.next(this.parseBigWigBlock(resultData, 0, request));
478
- break;
479
- }
480
- case 'bigbed': {
481
- observer.next(this.parseBigBedBlock(resultData, 0, block.offset * (1 << 8), request));
482
- break;
483
- }
484
- default: {
485
- console.warn(`Don't know what to do with ${blockType}`);
486
- }
487
- }
488
- }
489
- }));
490
- observer.complete();
491
- }
492
- catch (e) {
493
- observer.error(e);
494
- }
420
+ return allFeatures;
495
421
  }
496
- async readBigWigFeaturesAsArrays(blocks, opts = {}) {
422
+ async _readBigWigFeaturesAsArrays(blocks, opts = {}) {
497
423
  const { uncompressBufSize } = this;
498
424
  const { signal, request } = opts;
499
425
  const blockGroupsToFetch = groupBlocks(blocks);
@@ -501,9 +427,9 @@ export class BlockView {
501
427
  const allEnds = [];
502
428
  const allScores = [];
503
429
  let totalCount = 0;
504
- await Promise.all(blockGroupsToFetch.map(async (blockGroup) => {
430
+ for (const blockGroup of blockGroupsToFetch) {
505
431
  const { length, offset } = blockGroup;
506
- const data = await this.featureCache.get(`${length}_${offset}`, blockGroup, signal);
432
+ const data = await this.bbi.read(length, offset, { signal });
507
433
  const localBlocks = blockGroup.blocks.map(block => ({
508
434
  offset: block.offset - blockGroup.offset,
509
435
  length: block.length,
@@ -520,7 +446,7 @@ export class BlockView {
520
446
  else {
521
447
  for (const block of localBlocks) {
522
448
  const blockData = data.subarray(block.offset, block.offset + block.length);
523
- const result = this.parseBigWigBlockAsArrays(blockData, 0, request);
449
+ const result = parseBigWigBlockAsArrays(blockData, 0, request);
524
450
  if (result.starts.length > 0) {
525
451
  allStarts.push(result.starts);
526
452
  allEnds.push(result.ends);
@@ -529,7 +455,7 @@ export class BlockView {
529
455
  }
530
456
  }
531
457
  }
532
- }));
458
+ }
533
459
  if (allStarts.length === 0) {
534
460
  return {
535
461
  starts: new Int32Array(0),
@@ -558,7 +484,7 @@ export class BlockView {
558
484
  }
559
485
  return { starts, ends, scores, isSummary: false };
560
486
  }
561
- async readSummaryFeaturesAsArrays(blocks, opts = {}) {
487
+ async _readSummaryFeaturesAsArrays(blocks, opts = {}) {
562
488
  const { uncompressBufSize } = this;
563
489
  const { signal, request } = opts;
564
490
  const blockGroupsToFetch = groupBlocks(blocks);
@@ -568,9 +494,9 @@ export class BlockView {
568
494
  const allMinScores = [];
569
495
  const allMaxScores = [];
570
496
  let totalCount = 0;
571
- await Promise.all(blockGroupsToFetch.map(async (blockGroup) => {
497
+ for (const blockGroup of blockGroupsToFetch) {
572
498
  const { length, offset } = blockGroup;
573
- const data = await this.featureCache.get(`${length}_${offset}`, blockGroup, signal);
499
+ const data = await this.bbi.read(length, offset, { signal });
574
500
  const localBlocks = blockGroup.blocks.map(block => ({
575
501
  offset: block.offset - blockGroup.offset,
576
502
  length: block.length,
@@ -589,7 +515,7 @@ export class BlockView {
589
515
  else {
590
516
  for (const block of localBlocks) {
591
517
  const blockData = data.subarray(block.offset, block.offset + block.length);
592
- const features = this.parseSummaryBlock(blockData, 0, request);
518
+ const features = parseSummaryBlock(blockData, 0, request);
593
519
  if (features.length > 0) {
594
520
  const starts = new Int32Array(features.length);
595
521
  const ends = new Int32Array(features.length);
@@ -613,7 +539,7 @@ export class BlockView {
613
539
  }
614
540
  }
615
541
  }
616
- }));
542
+ }
617
543
  if (allStarts.length === 0) {
618
544
  return {
619
545
  starts: new Int32Array(0),