@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/src/bbi.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { toArray } from 'rxjs/operators'
|
|
|
5
5
|
import { BlockView } from './block-view.ts'
|
|
6
6
|
|
|
7
7
|
import type {
|
|
8
|
+
BigWigFeatureArrays,
|
|
8
9
|
BigWigHeader,
|
|
9
10
|
BigWigHeaderWithRefNames,
|
|
10
11
|
Feature,
|
|
@@ -12,6 +13,7 @@ import type {
|
|
|
12
13
|
RequestOptions2,
|
|
13
14
|
RequestOptions,
|
|
14
15
|
Statistics,
|
|
16
|
+
SummaryFeatureArrays,
|
|
15
17
|
ZoomLevel,
|
|
16
18
|
} from './types.ts'
|
|
17
19
|
import type { GenericFilehandle } from 'generic-filehandle2'
|
|
@@ -19,6 +21,8 @@ import type { GenericFilehandle } from 'generic-filehandle2'
|
|
|
19
21
|
const BIG_WIG_MAGIC = -2003829722
|
|
20
22
|
const BIG_BED_MAGIC = -2021002517
|
|
21
23
|
|
|
24
|
+
const decoder = new TextDecoder('utf8')
|
|
25
|
+
|
|
22
26
|
function getDataView(buffer: Uint8Array) {
|
|
23
27
|
return new DataView(buffer.buffer, buffer.byteOffset, buffer.length)
|
|
24
28
|
}
|
|
@@ -71,7 +75,7 @@ export abstract class BBI {
|
|
|
71
75
|
|
|
72
76
|
private async _getHeader(opts?: RequestOptions) {
|
|
73
77
|
const header = await this._getMainHeader(opts)
|
|
74
|
-
const chroms = await this.
|
|
78
|
+
const chroms = await this._readChromosomeTree(header, opts)
|
|
75
79
|
return {
|
|
76
80
|
...header,
|
|
77
81
|
...chroms,
|
|
@@ -96,7 +100,8 @@ export abstract class BBI {
|
|
|
96
100
|
offset += 2
|
|
97
101
|
const numZoomLevels = dataView.getUint16(offset, true)
|
|
98
102
|
offset += 2
|
|
99
|
-
|
|
103
|
+
// Offset to the B+ tree that maps chromosome names to integer IDs
|
|
104
|
+
const chromosomeTreeOffset = Number(dataView.getBigUint64(offset, true))
|
|
100
105
|
offset += 8
|
|
101
106
|
const unzoomedDataOffset = Number(dataView.getBigUint64(offset, true))
|
|
102
107
|
offset += 8
|
|
@@ -142,7 +147,7 @@ export abstract class BBI {
|
|
|
142
147
|
|
|
143
148
|
let totalSummary: Statistics
|
|
144
149
|
if (totalSummaryOffset) {
|
|
145
|
-
const b2 = b.subarray(
|
|
150
|
+
const b2 = b.subarray(totalSummaryOffset)
|
|
146
151
|
let offset = 0
|
|
147
152
|
const dataView = getDataView(b2)
|
|
148
153
|
const basesCovered = Number(dataView.getBigUint64(offset, true))
|
|
@@ -166,7 +171,6 @@ export abstract class BBI {
|
|
|
166
171
|
} else {
|
|
167
172
|
throw new Error('no stats')
|
|
168
173
|
}
|
|
169
|
-
const decoder = new TextDecoder('utf8')
|
|
170
174
|
|
|
171
175
|
return {
|
|
172
176
|
zoomLevels,
|
|
@@ -178,7 +182,7 @@ export abstract class BBI {
|
|
|
178
182
|
definedFieldCount,
|
|
179
183
|
uncompressBufSize,
|
|
180
184
|
asOffset,
|
|
181
|
-
|
|
185
|
+
chromosomeTreeOffset,
|
|
182
186
|
totalSummaryOffset,
|
|
183
187
|
unzoomedDataOffset,
|
|
184
188
|
unzoomedIndexOffset,
|
|
@@ -190,16 +194,21 @@ export abstract class BBI {
|
|
|
190
194
|
}
|
|
191
195
|
}
|
|
192
196
|
|
|
193
|
-
|
|
197
|
+
// Reads the B+ tree that maps chromosome names to integer IDs
|
|
198
|
+
// This is part of the "cirTree" (combined ID R-tree) structure, which uses
|
|
199
|
+
// integer chromosome IDs instead of strings for more efficient spatial indexing
|
|
200
|
+
private async _readChromosomeTree(
|
|
194
201
|
header: BigWigHeader,
|
|
195
202
|
opts?: { signal?: AbortSignal },
|
|
196
203
|
) {
|
|
197
|
-
const refsByNumber:
|
|
198
|
-
const refsByName
|
|
204
|
+
const refsByNumber: RefInfo[] = []
|
|
205
|
+
const refsByName = {} as Record<string, number>
|
|
199
206
|
|
|
200
|
-
const
|
|
207
|
+
const chromosomeTreeOffset = header.chromosomeTreeOffset
|
|
201
208
|
|
|
202
|
-
const dataView = getDataView(
|
|
209
|
+
const dataView = getDataView(
|
|
210
|
+
await this.bbi.read(32, chromosomeTreeOffset, opts),
|
|
211
|
+
)
|
|
203
212
|
let offset = 0
|
|
204
213
|
// const magic = dataView.getUint32(offset, true) // unused
|
|
205
214
|
offset += 4
|
|
@@ -212,9 +221,8 @@ export abstract class BBI {
|
|
|
212
221
|
// const itemCount = dataView.getBigUint64(offset, true) // unused
|
|
213
222
|
offset += 8
|
|
214
223
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const bptReadNode = async (currentOffset: number) => {
|
|
224
|
+
// Recursively traverses the B+ tree to populate chromosome name-to-ID mappings
|
|
225
|
+
const readBPlusTreeNode = async (currentOffset: number) => {
|
|
218
226
|
const b = await this.bbi.read(4, currentOffset)
|
|
219
227
|
const dataView = getDataView(b)
|
|
220
228
|
let offset = 0
|
|
@@ -225,6 +233,7 @@ export abstract class BBI {
|
|
|
225
233
|
const count = dataView.getUint16(offset, true)
|
|
226
234
|
offset += 2
|
|
227
235
|
|
|
236
|
+
// Leaf nodes contain the actual chromosome name-to-ID mappings
|
|
228
237
|
if (isLeafNode) {
|
|
229
238
|
const b = await this.bbi.read(
|
|
230
239
|
count * (keySize + valSize),
|
|
@@ -234,9 +243,10 @@ export abstract class BBI {
|
|
|
234
243
|
offset = 0
|
|
235
244
|
|
|
236
245
|
for (let n = 0; n < count; n++) {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
.
|
|
246
|
+
const keyEnd = b.indexOf(0, offset)
|
|
247
|
+
const key = decoder.decode(
|
|
248
|
+
b.subarray(offset, keyEnd !== -1 ? keyEnd : offset + keySize),
|
|
249
|
+
)
|
|
240
250
|
offset += keySize
|
|
241
251
|
const refId = dataView.getUint32(offset, true)
|
|
242
252
|
offset += 4
|
|
@@ -251,6 +261,7 @@ export abstract class BBI {
|
|
|
251
261
|
}
|
|
252
262
|
}
|
|
253
263
|
} else {
|
|
264
|
+
// Non-leaf nodes contain pointers to child nodes
|
|
254
265
|
const nextNodes = []
|
|
255
266
|
const dataView = getDataView(
|
|
256
267
|
await this.bbi.read(count * (keySize + 8), currentOffset + offset),
|
|
@@ -261,12 +272,12 @@ export abstract class BBI {
|
|
|
261
272
|
offset += keySize
|
|
262
273
|
const childOffset = Number(dataView.getBigUint64(offset, true))
|
|
263
274
|
offset += 8
|
|
264
|
-
nextNodes.push(
|
|
275
|
+
nextNodes.push(readBPlusTreeNode(childOffset))
|
|
265
276
|
}
|
|
266
277
|
await Promise.all(nextNodes)
|
|
267
278
|
}
|
|
268
279
|
}
|
|
269
|
-
await
|
|
280
|
+
await readBPlusTreeNode(chromosomeTreeOffset + 32)
|
|
270
281
|
return {
|
|
271
282
|
refsByName,
|
|
272
283
|
refsByNumber,
|
|
@@ -284,7 +295,7 @@ export abstract class BBI {
|
|
|
284
295
|
this.bbi,
|
|
285
296
|
refsByName,
|
|
286
297
|
unzoomedIndexOffset,
|
|
287
|
-
uncompressBufSize
|
|
298
|
+
uncompressBufSize,
|
|
288
299
|
fileType,
|
|
289
300
|
)
|
|
290
301
|
}
|
|
@@ -345,7 +356,76 @@ export abstract class BBI {
|
|
|
345
356
|
) {
|
|
346
357
|
const ob = await this.getFeatureStream(refName, start, end, opts)
|
|
347
358
|
|
|
348
|
-
const
|
|
349
|
-
|
|
359
|
+
const arrays = await firstValueFrom(ob.pipe(toArray()))
|
|
360
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0)
|
|
361
|
+
const result = new Array(totalLength)
|
|
362
|
+
let index = 0
|
|
363
|
+
for (const arr of arrays) {
|
|
364
|
+
for (const item of arr) {
|
|
365
|
+
result[index++] = item
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return result
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Gets features from a BigWig file as typed arrays (more efficient than getFeatures)
|
|
373
|
+
*
|
|
374
|
+
* @param refName - The chromosome name
|
|
375
|
+
* @param start - The start of a region
|
|
376
|
+
* @param end - The end of a region
|
|
377
|
+
* @param opts - Options including basesPerSpan or scale
|
|
378
|
+
* @returns Promise with typed arrays: starts, ends, scores (and minScores/maxScores for summary data)
|
|
379
|
+
*/
|
|
380
|
+
public async getFeaturesAsArrays(
|
|
381
|
+
refName: string,
|
|
382
|
+
start: number,
|
|
383
|
+
end: number,
|
|
384
|
+
opts?: RequestOptions2,
|
|
385
|
+
): Promise<BigWigFeatureArrays | SummaryFeatureArrays> {
|
|
386
|
+
const features = await this.getFeatures(refName, start, end, opts)
|
|
387
|
+
const count = features.length
|
|
388
|
+
|
|
389
|
+
if (count === 0) {
|
|
390
|
+
return {
|
|
391
|
+
starts: new Int32Array(0),
|
|
392
|
+
ends: new Int32Array(0),
|
|
393
|
+
scores: new Float32Array(0),
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const hasSummary = features[0]?.summary === true
|
|
398
|
+
|
|
399
|
+
if (hasSummary) {
|
|
400
|
+
const starts = new Int32Array(count)
|
|
401
|
+
const ends = new Int32Array(count)
|
|
402
|
+
const scores = new Float32Array(count)
|
|
403
|
+
const minScores = new Float32Array(count)
|
|
404
|
+
const maxScores = new Float32Array(count)
|
|
405
|
+
|
|
406
|
+
for (let i = 0; i < count; i++) {
|
|
407
|
+
const f = features[i]!
|
|
408
|
+
starts[i] = f.start
|
|
409
|
+
ends[i] = f.end
|
|
410
|
+
scores[i] = f.score ?? 0
|
|
411
|
+
minScores[i] = f.minScore ?? 0
|
|
412
|
+
maxScores[i] = f.maxScore ?? 0
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return { starts, ends, scores, minScores, maxScores }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const starts = new Int32Array(count)
|
|
419
|
+
const ends = new Int32Array(count)
|
|
420
|
+
const scores = new Float32Array(count)
|
|
421
|
+
|
|
422
|
+
for (let i = 0; i < count; i++) {
|
|
423
|
+
const f = features[i]!
|
|
424
|
+
starts[i] = f.start
|
|
425
|
+
ends[i] = f.end
|
|
426
|
+
scores[i] = f.score ?? 0
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return { starts, ends, scores }
|
|
350
430
|
}
|
|
351
431
|
}
|
package/src/bigbed.ts
CHANGED
|
@@ -6,6 +6,9 @@ import { map, reduce } from 'rxjs/operators'
|
|
|
6
6
|
import { BBI } from './bbi.ts'
|
|
7
7
|
|
|
8
8
|
import type { Feature, RequestOptions } from './types.ts'
|
|
9
|
+
import type { GenericFilehandle } from 'generic-filehandle2'
|
|
10
|
+
|
|
11
|
+
const decoder = new TextDecoder('utf8')
|
|
9
12
|
|
|
10
13
|
interface Loc {
|
|
11
14
|
key: string
|
|
@@ -25,6 +28,133 @@ export function filterUndef<T>(ts: (T | undefined)[]): T[] {
|
|
|
25
28
|
return ts.filter((t: T | undefined): t is T => !!t)
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
function getTabField(str: string, fieldIndex: number) {
|
|
32
|
+
if (fieldIndex < 0) {
|
|
33
|
+
return undefined
|
|
34
|
+
}
|
|
35
|
+
let start = 0
|
|
36
|
+
for (let i = 0; i < fieldIndex; i++) {
|
|
37
|
+
start = str.indexOf('\t', start)
|
|
38
|
+
if (start === -1) {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
start++
|
|
42
|
+
}
|
|
43
|
+
const end = str.indexOf('\t', start)
|
|
44
|
+
return end === -1 ? str.slice(start) : str.slice(start, end)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Parses a null-terminated string key from a B+ tree node
|
|
48
|
+
function parseKey(buffer: Uint8Array, offset: number, keySize: number) {
|
|
49
|
+
const keyEnd = buffer.indexOf(0, offset)
|
|
50
|
+
return decoder.decode(
|
|
51
|
+
buffer.subarray(offset, keyEnd !== -1 ? keyEnd : offset + keySize),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Recursively traverses a B+ tree to search for a specific name in the BigBed extraIndex
|
|
56
|
+
// B+ trees are balanced tree structures optimized for disk-based searches
|
|
57
|
+
async function readBPlusTreeNode(
|
|
58
|
+
bbi: GenericFilehandle,
|
|
59
|
+
nodeOffset: number,
|
|
60
|
+
blockSize: number,
|
|
61
|
+
keySize: number,
|
|
62
|
+
valSize: number,
|
|
63
|
+
name: string,
|
|
64
|
+
field: number,
|
|
65
|
+
opts: RequestOptions,
|
|
66
|
+
): Promise<Loc | undefined> {
|
|
67
|
+
const len = 4 + blockSize * (keySize + valSize)
|
|
68
|
+
const buffer = await bbi.read(len, nodeOffset, opts)
|
|
69
|
+
const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.length)
|
|
70
|
+
let offset = 0
|
|
71
|
+
const nodeType = dataView.getInt8(offset)
|
|
72
|
+
offset += 2 // skip nodeType byte + 1 reserved byte
|
|
73
|
+
const cnt = dataView.getInt16(offset, true)
|
|
74
|
+
offset += 2
|
|
75
|
+
|
|
76
|
+
// Non-leaf node (nodeType === 0): contains keys and child node pointers for navigation
|
|
77
|
+
if (nodeType === 0) {
|
|
78
|
+
const leafkeys = []
|
|
79
|
+
for (let i = 0; i < cnt; i++) {
|
|
80
|
+
const key = parseKey(buffer, offset, keySize)
|
|
81
|
+
offset += keySize
|
|
82
|
+
const dataOffset = Number(dataView.getBigUint64(offset, true))
|
|
83
|
+
offset += 8
|
|
84
|
+
leafkeys.push({
|
|
85
|
+
key,
|
|
86
|
+
offset: dataOffset,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Binary search to find the appropriate child node
|
|
91
|
+
let left = 0
|
|
92
|
+
let right = leafkeys.length - 1
|
|
93
|
+
let targetIndex = leafkeys.length - 1
|
|
94
|
+
|
|
95
|
+
while (left <= right) {
|
|
96
|
+
const mid = Math.floor((left + right) / 2)
|
|
97
|
+
const cmp = name.localeCompare(leafkeys[mid]!.key)
|
|
98
|
+
|
|
99
|
+
if (cmp < 0) {
|
|
100
|
+
targetIndex = mid - 1
|
|
101
|
+
right = mid - 1
|
|
102
|
+
} else {
|
|
103
|
+
left = mid + 1
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const childOffset =
|
|
108
|
+
targetIndex >= 0 ? leafkeys[targetIndex]!.offset : leafkeys[0]!.offset
|
|
109
|
+
return readBPlusTreeNode(
|
|
110
|
+
bbi,
|
|
111
|
+
childOffset,
|
|
112
|
+
blockSize,
|
|
113
|
+
keySize,
|
|
114
|
+
valSize,
|
|
115
|
+
name,
|
|
116
|
+
field,
|
|
117
|
+
opts,
|
|
118
|
+
)
|
|
119
|
+
} else if (nodeType === 1) {
|
|
120
|
+
// Leaf node (nodeType === 1): contains actual key-value data
|
|
121
|
+
const keys = []
|
|
122
|
+
for (let i = 0; i < cnt; i++) {
|
|
123
|
+
const key = parseKey(buffer, offset, keySize)
|
|
124
|
+
offset += keySize
|
|
125
|
+
const dataOffset = Number(dataView.getBigUint64(offset, true))
|
|
126
|
+
offset += 8
|
|
127
|
+
const length = dataView.getUint32(offset, true)
|
|
128
|
+
offset += 4
|
|
129
|
+
offset += 4 // skip reserved
|
|
130
|
+
keys.push({
|
|
131
|
+
key,
|
|
132
|
+
offset: dataOffset,
|
|
133
|
+
length,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Binary search for exact key match in sorted leaf node
|
|
138
|
+
let left = 0
|
|
139
|
+
let right = keys.length - 1
|
|
140
|
+
|
|
141
|
+
while (left <= right) {
|
|
142
|
+
const mid = Math.floor((left + right) / 2)
|
|
143
|
+
const cmp = name.localeCompare(keys[mid]!.key)
|
|
144
|
+
|
|
145
|
+
if (cmp === 0) {
|
|
146
|
+
return { ...keys[mid]!, field }
|
|
147
|
+
} else if (cmp < 0) {
|
|
148
|
+
right = mid - 1
|
|
149
|
+
} else {
|
|
150
|
+
left = mid + 1
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return undefined
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
28
158
|
export class BigBed extends BBI {
|
|
29
159
|
public readIndicesCache = new AbortablePromiseCache<RequestOptions, Index[]>({
|
|
30
160
|
cache: new QuickLRU({ maxSize: 1 }),
|
|
@@ -53,7 +183,7 @@ export class BigBed extends BBI {
|
|
|
53
183
|
*/
|
|
54
184
|
private async _readIndices(opts: RequestOptions) {
|
|
55
185
|
const { extHeaderOffset } = await this.getHeader(opts)
|
|
56
|
-
const b = await this.bbi.read(64,
|
|
186
|
+
const b = await this.bbi.read(64, extHeaderOffset)
|
|
57
187
|
|
|
58
188
|
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
59
189
|
let offset = 0
|
|
@@ -71,9 +201,9 @@ export class BigBed extends BBI {
|
|
|
71
201
|
|
|
72
202
|
const blocklen = 20
|
|
73
203
|
const len = blocklen * count
|
|
74
|
-
const buffer = await this.bbi.read(len,
|
|
204
|
+
const buffer = await this.bbi.read(len, dataOffset)
|
|
75
205
|
|
|
76
|
-
const indices
|
|
206
|
+
const indices: Index[] = []
|
|
77
207
|
|
|
78
208
|
for (let i = 0; i < count; i += 1) {
|
|
79
209
|
const b = buffer.subarray(i * blocklen)
|
|
@@ -84,9 +214,14 @@ export class BigBed extends BBI {
|
|
|
84
214
|
const fieldcount = dataView.getInt16(offset, true)
|
|
85
215
|
offset += 2
|
|
86
216
|
const dataOffset = Number(dataView.getBigUint64(offset, true))
|
|
87
|
-
offset += 8 + 4 //4
|
|
217
|
+
offset += 8 + 4 // skip 8-byte offset + 4 reserved bytes
|
|
88
218
|
const field = dataView.getInt16(offset, true)
|
|
89
|
-
indices.push({
|
|
219
|
+
indices.push({
|
|
220
|
+
type,
|
|
221
|
+
fieldcount,
|
|
222
|
+
offset: dataOffset,
|
|
223
|
+
field,
|
|
224
|
+
})
|
|
90
225
|
}
|
|
91
226
|
return indices
|
|
92
227
|
}
|
|
@@ -109,7 +244,6 @@ export class BigBed extends BBI {
|
|
|
109
244
|
if (indices.length === 0) {
|
|
110
245
|
return []
|
|
111
246
|
}
|
|
112
|
-
const decoder = new TextDecoder('utf8')
|
|
113
247
|
const locs = indices.map(async index => {
|
|
114
248
|
const { offset: offset2, field } = index
|
|
115
249
|
const b = await this.bbi.read(32, offset2, opts)
|
|
@@ -127,74 +261,16 @@ export class BigBed extends BBI {
|
|
|
127
261
|
// const _itemCount = Number(dataView.getBigUint64(offset, true))
|
|
128
262
|
offset += 8
|
|
129
263
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
offset += 2
|
|
141
|
-
const keys = []
|
|
142
|
-
if (nodeType === 0) {
|
|
143
|
-
const leafkeys = []
|
|
144
|
-
for (let i = 0; i < cnt; i++) {
|
|
145
|
-
const key = decoder
|
|
146
|
-
.decode(b.subarray(offset, offset + keySize))
|
|
147
|
-
.replaceAll('\0', '')
|
|
148
|
-
offset += keySize
|
|
149
|
-
const dataOffset = Number(dataView.getBigUint64(offset, true))
|
|
150
|
-
offset += 8
|
|
151
|
-
leafkeys.push({
|
|
152
|
-
key,
|
|
153
|
-
offset: dataOffset,
|
|
154
|
-
})
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let lastOffset = 0
|
|
158
|
-
for (const { key, offset } of leafkeys) {
|
|
159
|
-
if (name.localeCompare(key) < 0 && lastOffset) {
|
|
160
|
-
return bptReadNode(lastOffset)
|
|
161
|
-
}
|
|
162
|
-
lastOffset = offset
|
|
163
|
-
}
|
|
164
|
-
return bptReadNode(lastOffset)
|
|
165
|
-
} else if (nodeType === 1) {
|
|
166
|
-
for (let i = 0; i < cnt; i++) {
|
|
167
|
-
const key = decoder
|
|
168
|
-
.decode(b.subarray(offset, offset + keySize))
|
|
169
|
-
.replaceAll('\0', '')
|
|
170
|
-
offset += keySize
|
|
171
|
-
const dataOffset = Number(dataView.getBigUint64(offset, true))
|
|
172
|
-
offset += 8
|
|
173
|
-
const length = dataView.getUint32(offset, true)
|
|
174
|
-
offset += 4
|
|
175
|
-
const reserved = dataView.getUint32(offset, true)
|
|
176
|
-
offset += 4
|
|
177
|
-
keys.push({
|
|
178
|
-
key,
|
|
179
|
-
offset: dataOffset,
|
|
180
|
-
length,
|
|
181
|
-
reserved,
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for (const n of keys) {
|
|
186
|
-
if (n.key === name) {
|
|
187
|
-
return {
|
|
188
|
-
...n,
|
|
189
|
-
field,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return undefined
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
return bptReadNode(offset2 + 32)
|
|
264
|
+
return readBPlusTreeNode(
|
|
265
|
+
this.bbi,
|
|
266
|
+
offset2 + 32,
|
|
267
|
+
blockSize,
|
|
268
|
+
keySize,
|
|
269
|
+
valSize,
|
|
270
|
+
name,
|
|
271
|
+
field,
|
|
272
|
+
opts,
|
|
273
|
+
)
|
|
198
274
|
})
|
|
199
275
|
return filterUndef(await Promise.all(locs))
|
|
200
276
|
}
|
|
@@ -206,7 +282,7 @@ export class BigBed extends BBI {
|
|
|
206
282
|
*
|
|
207
283
|
* @param name - the name to search for
|
|
208
284
|
*
|
|
209
|
-
* @param opts - options object with optional
|
|
285
|
+
* @param opts - options object with optional AbortSignal
|
|
210
286
|
*
|
|
211
287
|
* @return array of Feature
|
|
212
288
|
*/
|
|
@@ -222,16 +298,22 @@ export class BigBed extends BBI {
|
|
|
222
298
|
observer.error(e)
|
|
223
299
|
})
|
|
224
300
|
}).pipe(
|
|
225
|
-
reduce((acc, curr) =>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return x
|
|
231
|
-
}),
|
|
301
|
+
reduce((acc, curr) => {
|
|
302
|
+
acc.push(...curr)
|
|
303
|
+
return acc
|
|
304
|
+
}, [] as Feature[]),
|
|
305
|
+
map(features => features.map(f => ({ ...f, field: block.field }))),
|
|
232
306
|
)
|
|
233
307
|
})
|
|
234
308
|
const ret = await firstValueFrom(merge(...res))
|
|
235
|
-
|
|
309
|
+
// Filter to features where the indexed field matches the search name
|
|
310
|
+
// field offset is adjusted by -3 to account for chrom, chromStart, chromEnd columns
|
|
311
|
+
return ret.filter(f => {
|
|
312
|
+
if (!f.rest) {
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
const fieldIndex = (f.field || 0) - 3
|
|
316
|
+
return getTabField(f.rest, fieldIndex) === name
|
|
317
|
+
})
|
|
236
318
|
}
|
|
237
319
|
}
|
package/src/bigwig.ts
CHANGED
|
@@ -20,13 +20,12 @@ export class BigWig extends BBI {
|
|
|
20
20
|
|
|
21
21
|
for (let i = maxLevel; i >= 0; i -= 1) {
|
|
22
22
|
const zh = zoomLevels[i]
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
24
23
|
if (zh && zh.reductionLevel <= 2 * basesPerPx) {
|
|
25
24
|
return new BlockView(
|
|
26
25
|
this.bbi,
|
|
27
26
|
refsByName,
|
|
28
27
|
zh.indexOffset,
|
|
29
|
-
uncompressBufSize
|
|
28
|
+
uncompressBufSize,
|
|
30
29
|
'summary',
|
|
31
30
|
)
|
|
32
31
|
}
|