@gmod/bbi 7.0.5 → 8.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 +8 -0
- package/README.md +59 -0
- package/dist/bbi.d.ts +13 -3
- package/dist/bbi.js +81 -18
- package/dist/bbi.js.map +1 -1
- package/dist/bigbed.d.ts +14 -2
- package/dist/bigbed.js +127 -86
- package/dist/bigbed.js.map +1 -1
- package/dist/bigwig.js +1 -2
- package/dist/bigwig.js.map +1 -1
- package/dist/block-view.d.ts +13 -4
- package/dist/block-view.js +332 -150
- package/dist/block-view.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/parse-bigwig.d.ts +3 -0
- package/dist/parse-bigwig.js +15 -0
- package/dist/parse-bigwig.js.map +1 -0
- package/dist/range.js +12 -24
- package/dist/range.js.map +1 -1
- package/dist/types.d.ts +14 -2
- package/dist/unzip.d.ts +18 -1
- package/dist/unzip.js +33 -4
- package/dist/unzip.js.map +1 -1
- package/dist/util.d.ts +2 -4
- package/dist/util.js +7 -5
- package/dist/util.js.map +1 -1
- package/dist/wasm/inflate-wasm-inlined.d.ts +19 -0
- package/dist/wasm/inflate-wasm-inlined.js +117 -0
- package/dist/wasm/inflate-wasm-inlined.js.map +1 -0
- package/dist/wasm/inflate_wasm.d.ts +1 -0
- package/dist/wasm/inflate_wasm.js +43 -0
- package/dist/wasm/inflate_wasm.js.map +1 -0
- package/dist/wasm/inflate_wasm_bg.d.ts +68 -0
- package/dist/wasm/inflate_wasm_bg.js +307 -0
- package/dist/wasm/inflate_wasm_bg.js.map +1 -0
- package/esm/bbi.d.ts +13 -3
- package/esm/bbi.js +81 -18
- package/esm/bbi.js.map +1 -1
- package/esm/bigbed.d.ts +14 -2
- package/esm/bigbed.js +127 -86
- package/esm/bigbed.js.map +1 -1
- package/esm/bigwig.js +1 -2
- package/esm/bigwig.js.map +1 -1
- package/esm/block-view.d.ts +13 -4
- package/esm/block-view.js +334 -152
- package/esm/block-view.js.map +1 -1
- package/esm/index.d.ts +2 -1
- package/esm/index.js +1 -0
- package/esm/index.js.map +1 -1
- package/esm/parse-bigwig.d.ts +3 -0
- package/esm/parse-bigwig.js +12 -0
- package/esm/parse-bigwig.js.map +1 -0
- package/esm/range.js +12 -24
- package/esm/range.js.map +1 -1
- package/esm/types.d.ts +14 -2
- package/esm/unzip.d.ts +18 -1
- package/esm/unzip.js +30 -3
- package/esm/unzip.js.map +1 -1
- package/esm/util.d.ts +2 -4
- package/esm/util.js +7 -5
- package/esm/util.js.map +1 -1
- package/esm/wasm/inflate-wasm-inlined.d.ts +19 -0
- package/esm/wasm/inflate-wasm-inlined.js +111 -0
- package/esm/wasm/inflate-wasm-inlined.js.map +1 -0
- package/esm/wasm/inflate_wasm.d.ts +1 -0
- package/esm/wasm/inflate_wasm.js +5 -0
- package/esm/wasm/inflate_wasm.js.map +1 -0
- package/esm/wasm/inflate_wasm_bg.d.ts +68 -0
- package/esm/wasm/inflate_wasm_bg.js +296 -0
- package/esm/wasm/inflate_wasm_bg.js.map +1 -0
- package/package.json +21 -17
- package/src/bbi.ts +101 -21
- package/src/bigbed.ts +165 -83
- package/src/bigwig.ts +1 -2
- package/src/block-view.ts +415 -158
- package/src/index.ts +8 -1
- package/src/parse-bigwig.ts +19 -0
- package/src/range.ts +13 -21
- package/src/types.ts +19 -2
- package/src/unzip.ts +88 -3
- package/src/util.ts +9 -10
- package/src/wasm/inflate-wasm-inlined.d.ts +49 -0
- package/src/wasm/inflate-wasm-inlined.js +1 -0
- package/src/wasm/inflate_wasm.d.ts +35 -0
- package/src/wasm/inflate_wasm.js +4 -0
- package/src/wasm/inflate_wasm_bg.js +309 -0
- package/src/wasm/inflate_wasm_bg.wasm +0 -0
- package/src/wasm/inflate_wasm_bg.wasm.d.ts +13 -0
package/esm/block-view.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import AbortablePromiseCache from '@gmod/abortable-promise-cache';
|
|
2
2
|
import QuickLRU from 'quick-lru';
|
|
3
3
|
import Range from "./range.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
const decoder =
|
|
4
|
+
import { decompressAndParseBigWigBlocks, decompressAndParseSummaryBlocks, unzipBatch, } from "./unzip.js";
|
|
5
|
+
import { groupBlocks } from "./util.js";
|
|
6
|
+
const decoder = new TextDecoder('utf8');
|
|
7
7
|
function coordFilter(s1, e1, s2, e2) {
|
|
8
8
|
return s1 < e2 && e1 >= s2;
|
|
9
9
|
}
|
|
@@ -14,18 +14,30 @@ function coordFilter(s1, e1, s2, e2) {
|
|
|
14
14
|
* Genome Explorer by Thomas Down.
|
|
15
15
|
*/
|
|
16
16
|
export class BlockView {
|
|
17
|
-
|
|
17
|
+
bbi;
|
|
18
|
+
refsByName;
|
|
19
|
+
rTreeOffset;
|
|
20
|
+
uncompressBufSize;
|
|
21
|
+
blockType;
|
|
22
|
+
// R-tree index header cache - R-trees are spatial data structures used to
|
|
23
|
+
// efficiently query genomic intervals by chromosome and position
|
|
24
|
+
rTreePromise;
|
|
25
|
+
featureCache = new AbortablePromiseCache({
|
|
26
|
+
cache: new QuickLRU({ maxSize: 1000 }),
|
|
27
|
+
fill: async ({ length, offset }, signal) => this.bbi.read(length, offset, { signal }),
|
|
28
|
+
});
|
|
29
|
+
constructor(bbi, refsByName,
|
|
30
|
+
// Offset to the R-tree index in the file - this is part of the "cirTree"
|
|
31
|
+
// (combined ID R-tree), which combines a B+ tree for chromosome names
|
|
32
|
+
// with an R-tree for efficient spatial queries
|
|
33
|
+
rTreeOffset, uncompressBufSize, blockType) {
|
|
18
34
|
this.bbi = bbi;
|
|
19
35
|
this.refsByName = refsByName;
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
36
|
+
this.rTreeOffset = rTreeOffset;
|
|
37
|
+
this.uncompressBufSize = uncompressBufSize;
|
|
22
38
|
this.blockType = blockType;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
fill: async ({ length, offset }, signal) => this.bbi.read(length, offset, { signal }),
|
|
26
|
-
});
|
|
27
|
-
if (!(cirTreeOffset >= 0)) {
|
|
28
|
-
throw new Error('invalid cirTreeOffset!');
|
|
39
|
+
if (!(rTreeOffset >= 0)) {
|
|
40
|
+
throw new Error('invalid rTreeOffset!');
|
|
29
41
|
}
|
|
30
42
|
}
|
|
31
43
|
async readWigData(chrName, start, end, observer, opts) {
|
|
@@ -33,105 +45,98 @@ export class BlockView {
|
|
|
33
45
|
const chrId = this.refsByName[chrName];
|
|
34
46
|
if (chrId === undefined) {
|
|
35
47
|
observer.complete();
|
|
48
|
+
return;
|
|
36
49
|
}
|
|
37
50
|
const request = { chrId, start, end };
|
|
38
|
-
if (!this.
|
|
39
|
-
this.
|
|
51
|
+
if (!this.rTreePromise) {
|
|
52
|
+
this.rTreePromise = this.bbi.read(48, this.rTreeOffset, opts);
|
|
40
53
|
}
|
|
41
|
-
const buffer = await this.
|
|
42
|
-
const dataView = new DataView(buffer.buffer);
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
const buffer = await this.rTreePromise;
|
|
55
|
+
const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
|
|
56
|
+
// Maximum number of children per R-tree node - used to calculate memory bounds
|
|
57
|
+
const rTreeBlockSize = dataView.getUint32(4, true);
|
|
58
|
+
const blocksToFetch = [];
|
|
45
59
|
let outstanding = 0;
|
|
46
|
-
|
|
60
|
+
// R-tree leaf nodes contain the actual data blocks to fetch
|
|
61
|
+
const processLeafNode = (dataView, startOffset, count) => {
|
|
62
|
+
let offset = startOffset;
|
|
63
|
+
for (let i = 0; i < count; i++) {
|
|
64
|
+
const startChrom = dataView.getUint32(offset, true);
|
|
65
|
+
offset += 4;
|
|
66
|
+
const startBase = dataView.getUint32(offset, true);
|
|
67
|
+
offset += 4;
|
|
68
|
+
const endChrom = dataView.getUint32(offset, true);
|
|
69
|
+
offset += 4;
|
|
70
|
+
const endBase = dataView.getUint32(offset, true);
|
|
71
|
+
offset += 4;
|
|
72
|
+
const blockOffset = Number(dataView.getBigUint64(offset, true));
|
|
73
|
+
offset += 8;
|
|
74
|
+
const blockSize = Number(dataView.getBigUint64(offset, true));
|
|
75
|
+
offset += 8;
|
|
76
|
+
if (blockIntersectsQuery({ startChrom, startBase, endBase, endChrom })) {
|
|
77
|
+
blocksToFetch.push({
|
|
78
|
+
offset: blockOffset,
|
|
79
|
+
length: blockSize,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
// R-tree non-leaf nodes contain pointers to child nodes
|
|
85
|
+
const processNonLeafNode = (dataView, startOffset, count, level) => {
|
|
86
|
+
const recurOffsets = [];
|
|
87
|
+
let offset = startOffset;
|
|
88
|
+
for (let i = 0; i < count; i++) {
|
|
89
|
+
const startChrom = dataView.getUint32(offset, true);
|
|
90
|
+
offset += 4;
|
|
91
|
+
const startBase = dataView.getUint32(offset, true);
|
|
92
|
+
offset += 4;
|
|
93
|
+
const endChrom = dataView.getUint32(offset, true);
|
|
94
|
+
offset += 4;
|
|
95
|
+
const endBase = dataView.getUint32(offset, true);
|
|
96
|
+
offset += 4;
|
|
97
|
+
const blockOffset = Number(dataView.getBigUint64(offset, true));
|
|
98
|
+
offset += 8;
|
|
99
|
+
if (blockIntersectsQuery({ startChrom, startBase, endChrom, endBase })) {
|
|
100
|
+
recurOffsets.push(blockOffset);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (recurOffsets.length > 0) {
|
|
104
|
+
traverseRTree(recurOffsets, level + 1);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const processRTreeNode = (rTreeBlockData, offset2, level) => {
|
|
47
108
|
try {
|
|
48
|
-
const data =
|
|
49
|
-
const
|
|
50
|
-
const dataView = new DataView(b.buffer, b.byteOffset, b.length);
|
|
109
|
+
const data = rTreeBlockData.subarray(offset2);
|
|
110
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.length);
|
|
51
111
|
let offset = 0;
|
|
52
112
|
const isLeaf = dataView.getUint8(offset);
|
|
53
|
-
offset += 2; // 1 skip
|
|
54
|
-
const
|
|
113
|
+
offset += 2; // 1 skip for reserved byte
|
|
114
|
+
const count = dataView.getUint16(offset, true);
|
|
55
115
|
offset += 2;
|
|
56
116
|
if (isLeaf === 1) {
|
|
57
|
-
|
|
58
|
-
for (let i = 0; i < cnt; i++) {
|
|
59
|
-
const startChrom = dataView.getUint32(offset, true);
|
|
60
|
-
offset += 4;
|
|
61
|
-
const startBase = dataView.getUint32(offset, true);
|
|
62
|
-
offset += 4;
|
|
63
|
-
const endChrom = dataView.getUint32(offset, true);
|
|
64
|
-
offset += 4;
|
|
65
|
-
const endBase = dataView.getUint32(offset, true);
|
|
66
|
-
offset += 4;
|
|
67
|
-
const blockOffset = Number(dataView.getBigUint64(offset, true));
|
|
68
|
-
offset += 8;
|
|
69
|
-
const blockSize = Number(dataView.getBigUint64(offset, true));
|
|
70
|
-
offset += 8;
|
|
71
|
-
blocksToFetch2.push({
|
|
72
|
-
startChrom,
|
|
73
|
-
startBase,
|
|
74
|
-
endBase,
|
|
75
|
-
endChrom,
|
|
76
|
-
blockOffset,
|
|
77
|
-
blockSize,
|
|
78
|
-
offset,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
blocksToFetch = blocksToFetch.concat(blocksToFetch2
|
|
82
|
-
.filter(f => filterFeats(f))
|
|
83
|
-
.map(l => ({
|
|
84
|
-
offset: l.blockOffset,
|
|
85
|
-
length: l.blockSize,
|
|
86
|
-
})));
|
|
117
|
+
processLeafNode(dataView, offset, count);
|
|
87
118
|
}
|
|
88
119
|
else if (isLeaf === 0) {
|
|
89
|
-
|
|
90
|
-
for (let i = 0; i < cnt; i++) {
|
|
91
|
-
const startChrom = dataView.getUint32(offset, true);
|
|
92
|
-
offset += 4;
|
|
93
|
-
const startBase = dataView.getUint32(offset, true);
|
|
94
|
-
offset += 4;
|
|
95
|
-
const endChrom = dataView.getUint32(offset, true);
|
|
96
|
-
offset += 4;
|
|
97
|
-
const endBase = dataView.getUint32(offset, true);
|
|
98
|
-
offset += 4;
|
|
99
|
-
const blockOffset = Number(dataView.getBigUint64(offset, true));
|
|
100
|
-
offset += 8;
|
|
101
|
-
recurOffsets.push({
|
|
102
|
-
startChrom,
|
|
103
|
-
startBase,
|
|
104
|
-
endChrom,
|
|
105
|
-
endBase,
|
|
106
|
-
blockOffset,
|
|
107
|
-
offset,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
const recurOffsets2 = recurOffsets
|
|
111
|
-
.filter(f => filterFeats(f))
|
|
112
|
-
.map(l => l.blockOffset);
|
|
113
|
-
if (recurOffsets2.length > 0) {
|
|
114
|
-
cirFobRecur(recurOffsets2, level + 1);
|
|
115
|
-
}
|
|
120
|
+
processNonLeafNode(dataView, offset, count, level);
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
catch (e) {
|
|
119
124
|
observer.error(e);
|
|
120
125
|
}
|
|
121
126
|
};
|
|
122
|
-
const
|
|
127
|
+
const blockIntersectsQuery = (b) => {
|
|
123
128
|
const { startChrom, startBase, endChrom, endBase } = b;
|
|
124
129
|
return ((startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
|
|
125
130
|
(endChrom > chrId || (endChrom === chrId && endBase >= start)));
|
|
126
131
|
};
|
|
127
|
-
const
|
|
132
|
+
const fetchAndProcessRTreeBlocks = async (offsets, range, level) => {
|
|
128
133
|
try {
|
|
129
|
-
const length =
|
|
130
|
-
const offset =
|
|
134
|
+
const length = range.max - range.min;
|
|
135
|
+
const offset = range.min;
|
|
131
136
|
const resultBuffer = await this.featureCache.get(`${length}_${offset}`, { length, offset }, opts?.signal);
|
|
132
|
-
for (const element of
|
|
133
|
-
if (
|
|
134
|
-
|
|
137
|
+
for (const element of offsets) {
|
|
138
|
+
if (range.contains(element)) {
|
|
139
|
+
processRTreeNode(resultBuffer, element - offset, level);
|
|
135
140
|
outstanding -= 1;
|
|
136
141
|
if (outstanding === 0) {
|
|
137
142
|
this.readFeatures(observer, blocksToFetch, {
|
|
@@ -148,34 +153,37 @@ export class BlockView {
|
|
|
148
153
|
observer.error(e);
|
|
149
154
|
}
|
|
150
155
|
};
|
|
151
|
-
const
|
|
156
|
+
const traverseRTree = (offsets, level) => {
|
|
152
157
|
try {
|
|
153
|
-
outstanding +=
|
|
158
|
+
outstanding += offsets.length;
|
|
154
159
|
// Upper bound on size, based on a completely full leaf node.
|
|
155
|
-
const
|
|
160
|
+
const maxRTreeBlockSpan = 4 + rTreeBlockSize * 32;
|
|
156
161
|
let spans = new Range([
|
|
157
162
|
{
|
|
158
|
-
min:
|
|
159
|
-
max:
|
|
163
|
+
min: offsets[0],
|
|
164
|
+
max: offsets[0] + maxRTreeBlockSpan,
|
|
160
165
|
},
|
|
161
166
|
]);
|
|
162
|
-
for (let i = 1; i <
|
|
167
|
+
for (let i = 1; i < offsets.length; i += 1) {
|
|
163
168
|
const blockSpan = new Range([
|
|
164
169
|
{
|
|
165
|
-
min:
|
|
166
|
-
max:
|
|
170
|
+
min: offsets[i],
|
|
171
|
+
max: offsets[i] + maxRTreeBlockSpan,
|
|
167
172
|
},
|
|
168
173
|
]);
|
|
169
174
|
spans = spans.union(blockSpan);
|
|
170
175
|
}
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
spans.getRanges().forEach(range => {
|
|
177
|
+
fetchAndProcessRTreeBlocks(offsets, range, level).catch((e) => {
|
|
178
|
+
observer.error(e);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
173
181
|
}
|
|
174
182
|
catch (e) {
|
|
175
183
|
observer.error(e);
|
|
176
184
|
}
|
|
177
185
|
};
|
|
178
|
-
|
|
186
|
+
traverseRTree([this.rTreeOffset + 48], 1);
|
|
179
187
|
return;
|
|
180
188
|
}
|
|
181
189
|
catch (e) {
|
|
@@ -187,8 +195,6 @@ export class BlockView {
|
|
|
187
195
|
let offset = startOffset;
|
|
188
196
|
const dataView = new DataView(b.buffer, b.byteOffset, b.length);
|
|
189
197
|
while (offset < b.byteLength) {
|
|
190
|
-
// this was extracted from looking at the runtime code generated by
|
|
191
|
-
// binary-parser
|
|
192
198
|
const chromId = dataView.getUint32(offset, true);
|
|
193
199
|
offset += 4;
|
|
194
200
|
const start = dataView.getUint32(offset, true);
|
|
@@ -202,14 +208,10 @@ export class BlockView {
|
|
|
202
208
|
const maxScore = dataView.getFloat32(offset, true);
|
|
203
209
|
offset += 4;
|
|
204
210
|
const sumData = dataView.getFloat32(offset, true);
|
|
205
|
-
offset +=
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (request
|
|
210
|
-
? chromId === request.chrId &&
|
|
211
|
-
coordFilter(start, end, request.start, request.end)
|
|
212
|
-
: true) {
|
|
211
|
+
offset += 8;
|
|
212
|
+
if (!request ||
|
|
213
|
+
(chromId === request.chrId &&
|
|
214
|
+
coordFilter(start, end, request.start, request.end))) {
|
|
213
215
|
features.push({
|
|
214
216
|
start,
|
|
215
217
|
end,
|
|
@@ -225,8 +227,7 @@ export class BlockView {
|
|
|
225
227
|
parseBigBedBlock(data, startOffset, offset, request) {
|
|
226
228
|
const items = [];
|
|
227
229
|
let currOffset = startOffset;
|
|
228
|
-
const
|
|
229
|
-
const dataView = new DataView(b.buffer, b.byteOffset, b.length);
|
|
230
|
+
const dataView = new DataView(data.buffer, data.byteOffset, data.length);
|
|
230
231
|
while (currOffset < data.byteLength) {
|
|
231
232
|
const c2 = currOffset;
|
|
232
233
|
const chromId = dataView.getUint32(currOffset, true);
|
|
@@ -242,19 +243,20 @@ export class BlockView {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
const b = data.subarray(currOffset, i);
|
|
245
|
-
const rest = decoder
|
|
246
|
+
const rest = decoder.decode(b);
|
|
246
247
|
currOffset = i + 1;
|
|
247
|
-
|
|
248
|
-
chromId
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
}
|
|
254
258
|
}
|
|
255
|
-
return
|
|
256
|
-
? items.filter((f) => coordFilter(f.start, f.end, request.start, request.end))
|
|
257
|
-
: items;
|
|
259
|
+
return items;
|
|
258
260
|
}
|
|
259
261
|
parseBigWigBlock(buffer, startOffset, req) {
|
|
260
262
|
const b = buffer.subarray(startOffset);
|
|
@@ -271,7 +273,7 @@ export class BlockView {
|
|
|
271
273
|
offset += 2;
|
|
272
274
|
const itemCount = dataView.getUint16(offset, true);
|
|
273
275
|
offset += 2;
|
|
274
|
-
const items =
|
|
276
|
+
const items = [];
|
|
275
277
|
switch (blockType) {
|
|
276
278
|
case 1: {
|
|
277
279
|
for (let i = 0; i < itemCount; i++) {
|
|
@@ -281,11 +283,13 @@ export class BlockView {
|
|
|
281
283
|
offset += 4;
|
|
282
284
|
const score = dataView.getFloat32(offset, true);
|
|
283
285
|
offset += 4;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
if (!req || coordFilter(start, end, req.start, req.end)) {
|
|
287
|
+
items.push({
|
|
288
|
+
start,
|
|
289
|
+
end,
|
|
290
|
+
score,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
289
293
|
}
|
|
290
294
|
break;
|
|
291
295
|
}
|
|
@@ -295,11 +299,14 @@ export class BlockView {
|
|
|
295
299
|
offset += 4;
|
|
296
300
|
const score = dataView.getFloat32(offset, true);
|
|
297
301
|
offset += 4;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
+
}
|
|
303
310
|
}
|
|
304
311
|
break;
|
|
305
312
|
}
|
|
@@ -308,36 +315,49 @@ export class BlockView {
|
|
|
308
315
|
const score = dataView.getFloat32(offset, true);
|
|
309
316
|
offset += 4;
|
|
310
317
|
const start = blockStart + i * itemStep;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
+
}
|
|
316
326
|
}
|
|
317
327
|
break;
|
|
318
328
|
}
|
|
319
329
|
}
|
|
320
|
-
return
|
|
321
|
-
? items.filter(f => coordFilter(f.start, f.end, req.start, req.end))
|
|
322
|
-
: items;
|
|
330
|
+
return items;
|
|
323
331
|
}
|
|
324
332
|
async readFeatures(observer, blocks, opts = {}) {
|
|
325
333
|
try {
|
|
326
|
-
const { blockType,
|
|
334
|
+
const { blockType, uncompressBufSize } = this;
|
|
327
335
|
const { signal, request } = opts;
|
|
328
336
|
const blockGroupsToFetch = groupBlocks(blocks);
|
|
329
|
-
checkAbortSignal(signal);
|
|
330
337
|
await Promise.all(blockGroupsToFetch.map(async (blockGroup) => {
|
|
331
|
-
checkAbortSignal(signal);
|
|
332
338
|
const { length, offset } = blockGroup;
|
|
333
339
|
const data = await this.featureCache.get(`${length}_${offset}`, blockGroup, signal);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
340
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
341
|
+
offset: block.offset - blockGroup.offset,
|
|
342
|
+
length: block.length,
|
|
343
|
+
}));
|
|
344
|
+
let decompressedData;
|
|
345
|
+
let decompressedOffsets;
|
|
346
|
+
if (uncompressBufSize > 0) {
|
|
347
|
+
const result = await unzipBatch(data, localBlocks, uncompressBufSize);
|
|
348
|
+
decompressedData = result.data;
|
|
349
|
+
decompressedOffsets = result.offsets;
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
decompressedData = data;
|
|
353
|
+
decompressedOffsets = localBlocks.map(b => b.offset);
|
|
354
|
+
decompressedOffsets.push(data.length);
|
|
355
|
+
}
|
|
356
|
+
for (let i = 0; i < blockGroup.blocks.length; i++) {
|
|
357
|
+
const block = blockGroup.blocks[i];
|
|
358
|
+
const start = decompressedOffsets[i];
|
|
359
|
+
const end = decompressedOffsets[i + 1];
|
|
360
|
+
const resultData = decompressedData.subarray(start, end);
|
|
341
361
|
switch (blockType) {
|
|
342
362
|
case 'summary': {
|
|
343
363
|
observer.next(this.parseSummaryBlock(resultData, 0, request));
|
|
@@ -348,7 +368,7 @@ export class BlockView {
|
|
|
348
368
|
break;
|
|
349
369
|
}
|
|
350
370
|
case 'bigbed': {
|
|
351
|
-
observer.next(this.parseBigBedBlock(resultData, 0,
|
|
371
|
+
observer.next(this.parseBigBedBlock(resultData, 0, block.offset * (1 << 8), request));
|
|
352
372
|
break;
|
|
353
373
|
}
|
|
354
374
|
default: {
|
|
@@ -363,5 +383,167 @@ export class BlockView {
|
|
|
363
383
|
observer.error(e);
|
|
364
384
|
}
|
|
365
385
|
}
|
|
386
|
+
async readBigWigFeaturesAsArrays(blocks, opts = {}) {
|
|
387
|
+
const { uncompressBufSize } = this;
|
|
388
|
+
const { signal, request } = opts;
|
|
389
|
+
const blockGroupsToFetch = groupBlocks(blocks);
|
|
390
|
+
const allStarts = [];
|
|
391
|
+
const allEnds = [];
|
|
392
|
+
const allScores = [];
|
|
393
|
+
let totalCount = 0;
|
|
394
|
+
await Promise.all(blockGroupsToFetch.map(async (blockGroup) => {
|
|
395
|
+
const { length, offset } = blockGroup;
|
|
396
|
+
const data = await this.featureCache.get(`${length}_${offset}`, blockGroup, signal);
|
|
397
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
398
|
+
offset: block.offset - blockGroup.offset,
|
|
399
|
+
length: block.length,
|
|
400
|
+
}));
|
|
401
|
+
if (uncompressBufSize > 0) {
|
|
402
|
+
const result = await decompressAndParseBigWigBlocks(data, localBlocks, uncompressBufSize, request?.start ?? 0, request?.end ?? 0);
|
|
403
|
+
if (result.starts.length > 0) {
|
|
404
|
+
allStarts.push(result.starts);
|
|
405
|
+
allEnds.push(result.ends);
|
|
406
|
+
allScores.push(result.scores);
|
|
407
|
+
totalCount += result.starts.length;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
for (const block of localBlocks) {
|
|
412
|
+
const blockData = data.subarray(block.offset, block.offset + block.length);
|
|
413
|
+
const features = this.parseBigWigBlock(blockData, 0, request);
|
|
414
|
+
if (features.length > 0) {
|
|
415
|
+
const starts = new Int32Array(features.length);
|
|
416
|
+
const ends = new Int32Array(features.length);
|
|
417
|
+
const scores = new Float32Array(features.length);
|
|
418
|
+
for (let i = 0; i < features.length; i++) {
|
|
419
|
+
const f = features[i];
|
|
420
|
+
starts[i] = f.start;
|
|
421
|
+
ends[i] = f.end;
|
|
422
|
+
scores[i] = f.score;
|
|
423
|
+
}
|
|
424
|
+
allStarts.push(starts);
|
|
425
|
+
allEnds.push(ends);
|
|
426
|
+
allScores.push(scores);
|
|
427
|
+
totalCount += features.length;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}));
|
|
432
|
+
if (allStarts.length === 0) {
|
|
433
|
+
return {
|
|
434
|
+
starts: new Int32Array(0),
|
|
435
|
+
ends: new Int32Array(0),
|
|
436
|
+
scores: new Float32Array(0),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (allStarts.length === 1) {
|
|
440
|
+
return {
|
|
441
|
+
starts: allStarts[0],
|
|
442
|
+
ends: allEnds[0],
|
|
443
|
+
scores: allScores[0],
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const starts = new Int32Array(totalCount);
|
|
447
|
+
const ends = new Int32Array(totalCount);
|
|
448
|
+
const scores = new Float32Array(totalCount);
|
|
449
|
+
let offset = 0;
|
|
450
|
+
for (let i = 0; i < allStarts.length; i++) {
|
|
451
|
+
starts.set(allStarts[i], offset);
|
|
452
|
+
ends.set(allEnds[i], offset);
|
|
453
|
+
scores.set(allScores[i], offset);
|
|
454
|
+
offset += allStarts[i].length;
|
|
455
|
+
}
|
|
456
|
+
return { starts, ends, scores };
|
|
457
|
+
}
|
|
458
|
+
async readSummaryFeaturesAsArrays(blocks, opts = {}) {
|
|
459
|
+
const { uncompressBufSize } = this;
|
|
460
|
+
const { signal, request } = opts;
|
|
461
|
+
const blockGroupsToFetch = groupBlocks(blocks);
|
|
462
|
+
const allStarts = [];
|
|
463
|
+
const allEnds = [];
|
|
464
|
+
const allScores = [];
|
|
465
|
+
const allMinScores = [];
|
|
466
|
+
const allMaxScores = [];
|
|
467
|
+
let totalCount = 0;
|
|
468
|
+
await Promise.all(blockGroupsToFetch.map(async (blockGroup) => {
|
|
469
|
+
const { length, offset } = blockGroup;
|
|
470
|
+
const data = await this.featureCache.get(`${length}_${offset}`, blockGroup, signal);
|
|
471
|
+
const localBlocks = blockGroup.blocks.map(block => ({
|
|
472
|
+
offset: block.offset - blockGroup.offset,
|
|
473
|
+
length: block.length,
|
|
474
|
+
}));
|
|
475
|
+
if (uncompressBufSize > 0) {
|
|
476
|
+
const result = await decompressAndParseSummaryBlocks(data, localBlocks, uncompressBufSize, request?.chrId ?? 0, request?.start ?? 0, request?.end ?? 0);
|
|
477
|
+
if (result.starts.length > 0) {
|
|
478
|
+
allStarts.push(result.starts);
|
|
479
|
+
allEnds.push(result.ends);
|
|
480
|
+
allScores.push(result.scores);
|
|
481
|
+
allMinScores.push(result.minScores);
|
|
482
|
+
allMaxScores.push(result.maxScores);
|
|
483
|
+
totalCount += result.starts.length;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
for (const block of localBlocks) {
|
|
488
|
+
const blockData = data.subarray(block.offset, block.offset + block.length);
|
|
489
|
+
const features = this.parseSummaryBlock(blockData, 0, request);
|
|
490
|
+
if (features.length > 0) {
|
|
491
|
+
const starts = new Int32Array(features.length);
|
|
492
|
+
const ends = new Int32Array(features.length);
|
|
493
|
+
const scores = new Float32Array(features.length);
|
|
494
|
+
const minScores = new Float32Array(features.length);
|
|
495
|
+
const maxScores = new Float32Array(features.length);
|
|
496
|
+
for (let i = 0; i < features.length; i++) {
|
|
497
|
+
const f = features[i];
|
|
498
|
+
starts[i] = f.start;
|
|
499
|
+
ends[i] = f.end;
|
|
500
|
+
scores[i] = f.score ?? 0;
|
|
501
|
+
minScores[i] = f.minScore ?? 0;
|
|
502
|
+
maxScores[i] = f.maxScore ?? 0;
|
|
503
|
+
}
|
|
504
|
+
allStarts.push(starts);
|
|
505
|
+
allEnds.push(ends);
|
|
506
|
+
allScores.push(scores);
|
|
507
|
+
allMinScores.push(minScores);
|
|
508
|
+
allMaxScores.push(maxScores);
|
|
509
|
+
totalCount += features.length;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}));
|
|
514
|
+
if (allStarts.length === 0) {
|
|
515
|
+
return {
|
|
516
|
+
starts: new Int32Array(0),
|
|
517
|
+
ends: new Int32Array(0),
|
|
518
|
+
scores: new Float32Array(0),
|
|
519
|
+
minScores: new Float32Array(0),
|
|
520
|
+
maxScores: new Float32Array(0),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (allStarts.length === 1) {
|
|
524
|
+
return {
|
|
525
|
+
starts: allStarts[0],
|
|
526
|
+
ends: allEnds[0],
|
|
527
|
+
scores: allScores[0],
|
|
528
|
+
minScores: allMinScores[0],
|
|
529
|
+
maxScores: allMaxScores[0],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
const starts = new Int32Array(totalCount);
|
|
533
|
+
const ends = new Int32Array(totalCount);
|
|
534
|
+
const scores = new Float32Array(totalCount);
|
|
535
|
+
const minScores = new Float32Array(totalCount);
|
|
536
|
+
const maxScores = new Float32Array(totalCount);
|
|
537
|
+
let offset = 0;
|
|
538
|
+
for (let i = 0; i < allStarts.length; i++) {
|
|
539
|
+
starts.set(allStarts[i], offset);
|
|
540
|
+
ends.set(allEnds[i], offset);
|
|
541
|
+
scores.set(allScores[i], offset);
|
|
542
|
+
minScores.set(allMinScores[i], offset);
|
|
543
|
+
maxScores.set(allMaxScores[i], offset);
|
|
544
|
+
offset += allStarts[i].length;
|
|
545
|
+
}
|
|
546
|
+
return { starts, ends, scores, minScores, maxScores };
|
|
547
|
+
}
|
|
366
548
|
}
|
|
367
549
|
//# sourceMappingURL=block-view.js.map
|