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