@gmod/bbi 1.0.33 → 2.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 +20 -3
- package/dist/bbi.d.ts +2 -2
- package/dist/bbi.js +56 -59
- package/dist/bbi.js.map +1 -0
- package/dist/bigbed.d.ts +1 -2
- package/dist/bigbed.js +23 -20
- package/dist/bigbed.js.map +1 -0
- package/dist/bigwig.d.ts +1 -3
- package/dist/bigwig.js +5 -8
- package/dist/bigwig.js.map +1 -0
- package/dist/blockView.d.ts +8 -9
- package/dist/blockView.js +153 -92
- package/dist/blockView.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/range.js +2 -0
- package/dist/range.js.map +1 -0
- package/dist/unzip-pako.d.ts +1 -1
- package/dist/unzip-pako.js +2 -1
- package/dist/unzip-pako.js.map +1 -0
- package/dist/unzip.js +1 -0
- package/dist/unzip.js.map +1 -0
- package/dist/util.d.ts +11 -1
- package/dist/util.js +10 -4
- package/dist/util.js.map +1 -0
- package/esm/bbi.d.ts +2 -2
- package/esm/bbi.js +62 -67
- package/esm/bbi.js.map +1 -0
- package/esm/bigbed.d.ts +1 -2
- package/esm/bigbed.js +42 -46
- package/esm/bigbed.js.map +1 -0
- package/esm/bigwig.d.ts +1 -3
- package/esm/bigwig.js +7 -14
- package/esm/bigwig.js.map +1 -0
- package/esm/blockView.d.ts +8 -9
- package/esm/blockView.js +166 -116
- package/esm/blockView.js.map +1 -0
- package/esm/index.js +3 -7
- package/esm/index.js.map +1 -0
- package/esm/range.js +3 -4
- package/esm/range.js.map +1 -0
- package/esm/unzip-pako.d.ts +1 -1
- package/esm/unzip-pako.js +4 -7
- package/esm/unzip-pako.js.map +1 -0
- package/esm/unzip.js +3 -5
- package/esm/unzip.js.map +1 -0
- package/esm/util.d.ts +11 -1
- package/esm/util.js +14 -15
- package/esm/util.js.map +1 -0
- package/package.json +13 -13
- package/src/bbi.ts +375 -0
- package/src/bigbed.ts +244 -0
- package/src/bigwig.ts +38 -0
- package/src/blockView.ts +496 -0
- package/src/declare.d.ts +2 -0
- package/src/index.ts +3 -0
- package/src/range.ts +142 -0
- package/src/unzip-pako.ts +5 -0
- package/src/unzip.ts +2 -0
- package/src/util.ts +83 -0
package/src/blockView.ts
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { Observer } from 'rxjs'
|
|
2
|
+
import { Parser } from 'binary-parser'
|
|
3
|
+
import AbortablePromiseCache from 'abortable-promise-cache'
|
|
4
|
+
import { GenericFilehandle } from 'generic-filehandle'
|
|
5
|
+
import QuickLRU from 'quick-lru'
|
|
6
|
+
|
|
7
|
+
// locals
|
|
8
|
+
import Range from './range'
|
|
9
|
+
import { unzip } from './unzip'
|
|
10
|
+
import { Feature } from './bbi'
|
|
11
|
+
import { groupBlocks, checkAbortSignal } from './util'
|
|
12
|
+
|
|
13
|
+
interface CoordRequest {
|
|
14
|
+
chrId: number
|
|
15
|
+
start: number
|
|
16
|
+
end: number
|
|
17
|
+
}
|
|
18
|
+
interface DataBlock {
|
|
19
|
+
startChrom: number
|
|
20
|
+
endChrom: number
|
|
21
|
+
startBase: number
|
|
22
|
+
endBase: number
|
|
23
|
+
validCnt: number
|
|
24
|
+
minVal: number
|
|
25
|
+
maxVal: number
|
|
26
|
+
sumData: number
|
|
27
|
+
sumSqData: number
|
|
28
|
+
}
|
|
29
|
+
interface ReadData {
|
|
30
|
+
offset: number
|
|
31
|
+
length: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Options {
|
|
35
|
+
signal?: AbortSignal
|
|
36
|
+
request?: CoordRequest
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const BIG_WIG_TYPE_GRAPH = 1
|
|
40
|
+
const BIG_WIG_TYPE_VSTEP = 2
|
|
41
|
+
const BIG_WIG_TYPE_FSTEP = 3
|
|
42
|
+
|
|
43
|
+
function coordFilter(s1: number, e1: number, s2: number, e2: number): boolean {
|
|
44
|
+
return s1 < e2 && e1 >= s2
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getParsers(isBigEndian: boolean) {
|
|
48
|
+
const le = isBigEndian ? 'big' : 'little'
|
|
49
|
+
const summaryParser = new Parser()
|
|
50
|
+
.endianess(le)
|
|
51
|
+
.uint32('chromId')
|
|
52
|
+
.uint32('start')
|
|
53
|
+
.uint32('end')
|
|
54
|
+
.uint32('validCnt')
|
|
55
|
+
.floatle('minScore')
|
|
56
|
+
.floatle('maxScore')
|
|
57
|
+
.floatle('sumData')
|
|
58
|
+
.floatle('sumSqData')
|
|
59
|
+
.saveOffset('offset')
|
|
60
|
+
|
|
61
|
+
const leafParser = new Parser()
|
|
62
|
+
.endianess(le)
|
|
63
|
+
.uint8('isLeaf')
|
|
64
|
+
.skip(1)
|
|
65
|
+
.uint16('cnt')
|
|
66
|
+
.choice({
|
|
67
|
+
tag: 'isLeaf',
|
|
68
|
+
choices: {
|
|
69
|
+
1: new Parser().endianess(le).array('blocksToFetch', {
|
|
70
|
+
length: 'cnt',
|
|
71
|
+
type: new Parser()
|
|
72
|
+
.endianess(le)
|
|
73
|
+
.uint32('startChrom')
|
|
74
|
+
.uint32('startBase')
|
|
75
|
+
.uint32('endChrom')
|
|
76
|
+
.uint32('endBase')
|
|
77
|
+
.uint64('blockOffset')
|
|
78
|
+
.uint64('blockSize')
|
|
79
|
+
.saveOffset('offset'),
|
|
80
|
+
}),
|
|
81
|
+
0: new Parser().array('recurOffsets', {
|
|
82
|
+
length: 'cnt',
|
|
83
|
+
type: new Parser()
|
|
84
|
+
.endianess(le)
|
|
85
|
+
.uint32('startChrom')
|
|
86
|
+
.uint32('startBase')
|
|
87
|
+
.uint32('endChrom')
|
|
88
|
+
.uint32('endBase')
|
|
89
|
+
.uint64('blockOffset')
|
|
90
|
+
.saveOffset('offset'),
|
|
91
|
+
}),
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
const bigBedParser = new Parser()
|
|
95
|
+
.endianess(le)
|
|
96
|
+
.uint32('chromId')
|
|
97
|
+
.int32('start')
|
|
98
|
+
.int32('end')
|
|
99
|
+
.string('rest', {
|
|
100
|
+
zeroTerminated: true,
|
|
101
|
+
})
|
|
102
|
+
.saveOffset('offset')
|
|
103
|
+
|
|
104
|
+
const bigWigParser = new Parser()
|
|
105
|
+
.endianess(le)
|
|
106
|
+
.skip(4)
|
|
107
|
+
.int32('blockStart')
|
|
108
|
+
.skip(4)
|
|
109
|
+
.uint32('itemStep')
|
|
110
|
+
.uint32('itemSpan')
|
|
111
|
+
.uint8('blockType')
|
|
112
|
+
.skip(1)
|
|
113
|
+
.uint16('itemCount')
|
|
114
|
+
.choice({
|
|
115
|
+
tag: 'blockType',
|
|
116
|
+
choices: {
|
|
117
|
+
[BIG_WIG_TYPE_FSTEP]: new Parser().array('items', {
|
|
118
|
+
length: 'itemCount',
|
|
119
|
+
type: new Parser().floatle('score'),
|
|
120
|
+
}),
|
|
121
|
+
[BIG_WIG_TYPE_VSTEP]: new Parser().array('items', {
|
|
122
|
+
length: 'itemCount',
|
|
123
|
+
type: new Parser().endianess(le).int32('start').floatle('score'),
|
|
124
|
+
}),
|
|
125
|
+
[BIG_WIG_TYPE_GRAPH]: new Parser().array('items', {
|
|
126
|
+
length: 'itemCount',
|
|
127
|
+
type: new Parser()
|
|
128
|
+
.endianess(le)
|
|
129
|
+
.int32('start')
|
|
130
|
+
.int32('end')
|
|
131
|
+
.floatle('score'),
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
return {
|
|
136
|
+
bigWigParser,
|
|
137
|
+
bigBedParser,
|
|
138
|
+
summaryParser,
|
|
139
|
+
leafParser,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* View into a subset of the data in a BigWig file.
|
|
145
|
+
*
|
|
146
|
+
* Adapted by Robert Buels and Colin Diesh from bigwig.js in the Dalliance Genome
|
|
147
|
+
* Explorer by Thomas Down.
|
|
148
|
+
* @constructs
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
export class BlockView {
|
|
152
|
+
private cirTreePromise?: Promise<{ bytesRead: number; buffer: Buffer }>
|
|
153
|
+
|
|
154
|
+
private featureCache = new AbortablePromiseCache({
|
|
155
|
+
cache: new QuickLRU({ maxSize: 1000 }),
|
|
156
|
+
|
|
157
|
+
fill: async (requestData: ReadData, signal: AbortSignal) => {
|
|
158
|
+
const len = Number(requestData.length)
|
|
159
|
+
const off = Number(requestData.offset)
|
|
160
|
+
const { buffer } = await this.bbi.read(Buffer.alloc(len), 0, len, off, {
|
|
161
|
+
signal,
|
|
162
|
+
})
|
|
163
|
+
return buffer
|
|
164
|
+
},
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
private leafParser: ReturnType<typeof getParsers>['leafParser']
|
|
168
|
+
|
|
169
|
+
private bigBedParser: ReturnType<typeof getParsers>['bigBedParser']
|
|
170
|
+
|
|
171
|
+
public constructor(
|
|
172
|
+
private bbi: GenericFilehandle,
|
|
173
|
+
private refsByName: any,
|
|
174
|
+
private cirTreeOffset: number,
|
|
175
|
+
private isBigEndian: boolean,
|
|
176
|
+
private isCompressed: boolean,
|
|
177
|
+
private blockType: string,
|
|
178
|
+
) {
|
|
179
|
+
if (!(cirTreeOffset >= 0)) {
|
|
180
|
+
throw new Error('invalid cirTreeOffset!')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const parsers = getParsers(isBigEndian)
|
|
184
|
+
this.leafParser = parsers.leafParser
|
|
185
|
+
this.bigBedParser = parsers.bigBedParser
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
public async readWigData(
|
|
189
|
+
chrName: string,
|
|
190
|
+
start: number,
|
|
191
|
+
end: number,
|
|
192
|
+
observer: Observer<Feature[]>,
|
|
193
|
+
opts: Options,
|
|
194
|
+
) {
|
|
195
|
+
try {
|
|
196
|
+
const { refsByName, bbi, cirTreeOffset, isBigEndian } = this
|
|
197
|
+
const chrId = refsByName[chrName]
|
|
198
|
+
if (chrId === undefined) {
|
|
199
|
+
observer.complete()
|
|
200
|
+
}
|
|
201
|
+
const request = { chrId, start, end }
|
|
202
|
+
if (!this.cirTreePromise) {
|
|
203
|
+
const off = Number(cirTreeOffset)
|
|
204
|
+
this.cirTreePromise = bbi.read(Buffer.alloc(48), 0, 48, off, opts)
|
|
205
|
+
}
|
|
206
|
+
const { buffer } = await this.cirTreePromise
|
|
207
|
+
const cirBlockSize = isBigEndian
|
|
208
|
+
? buffer.readUInt32BE(4)
|
|
209
|
+
: buffer.readUInt32LE(4)
|
|
210
|
+
let blocksToFetch: any[] = []
|
|
211
|
+
let outstanding = 0
|
|
212
|
+
|
|
213
|
+
const cirFobRecur2 = (
|
|
214
|
+
cirBlockData: Buffer,
|
|
215
|
+
offset: number,
|
|
216
|
+
level: number,
|
|
217
|
+
) => {
|
|
218
|
+
try {
|
|
219
|
+
const data = cirBlockData.subarray(offset)
|
|
220
|
+
|
|
221
|
+
const p = this.leafParser.parse(data)
|
|
222
|
+
if (p.blocksToFetch) {
|
|
223
|
+
blocksToFetch = blocksToFetch.concat(
|
|
224
|
+
p.blocksToFetch
|
|
225
|
+
.filter(filterFeats)
|
|
226
|
+
.map((l: { blockOffset: bigint; blockSize: bigint }) => ({
|
|
227
|
+
offset: l.blockOffset,
|
|
228
|
+
length: l.blockSize,
|
|
229
|
+
})),
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
if (p.recurOffsets) {
|
|
233
|
+
const recurOffsets = p.recurOffsets
|
|
234
|
+
.filter(filterFeats)
|
|
235
|
+
.map((l: { blockOffset: bigint }) => Number(l.blockOffset))
|
|
236
|
+
if (recurOffsets.length > 0) {
|
|
237
|
+
cirFobRecur(recurOffsets, level + 1)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch (e) {
|
|
241
|
+
observer.error(e)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const filterFeats = (b: DataBlock) => {
|
|
246
|
+
const { startChrom, startBase, endChrom, endBase } = b
|
|
247
|
+
return (
|
|
248
|
+
(startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
|
|
249
|
+
(endChrom > chrId || (endChrom === chrId && endBase >= start))
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const cirFobStartFetch = async (
|
|
254
|
+
off: number[],
|
|
255
|
+
fr: Range,
|
|
256
|
+
level: number,
|
|
257
|
+
) => {
|
|
258
|
+
try {
|
|
259
|
+
const length = fr.max() - fr.min()
|
|
260
|
+
const offset = fr.min()
|
|
261
|
+
const resultBuffer: Buffer = await this.featureCache.get(
|
|
262
|
+
`${length}_${offset}`,
|
|
263
|
+
{ length, offset },
|
|
264
|
+
opts.signal,
|
|
265
|
+
)
|
|
266
|
+
for (let i = 0; i < off.length; i += 1) {
|
|
267
|
+
if (fr.contains(off[i])) {
|
|
268
|
+
cirFobRecur2(resultBuffer, off[i] - offset, level)
|
|
269
|
+
outstanding -= 1
|
|
270
|
+
if (outstanding === 0) {
|
|
271
|
+
this.readFeatures(observer, blocksToFetch, { ...opts, request })
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
observer.error(e)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const cirFobRecur = (offset: number[], level: number) => {
|
|
280
|
+
try {
|
|
281
|
+
outstanding += offset.length
|
|
282
|
+
|
|
283
|
+
const maxCirBlockSpan = 4 + Number(cirBlockSize) * 32 // Upper bound on size, based on a completely full leaf node.
|
|
284
|
+
let spans = new Range(offset[0], offset[0] + maxCirBlockSpan)
|
|
285
|
+
for (let i = 1; i < offset.length; i += 1) {
|
|
286
|
+
const blockSpan = new Range(offset[i], offset[i] + maxCirBlockSpan)
|
|
287
|
+
spans = spans.union(blockSpan)
|
|
288
|
+
}
|
|
289
|
+
spans.getRanges().map(fr => cirFobStartFetch(offset, fr, level))
|
|
290
|
+
} catch (e) {
|
|
291
|
+
observer.error(e)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return cirFobRecur([Number(cirTreeOffset) + 48], 1)
|
|
296
|
+
} catch (e) {
|
|
297
|
+
observer.error(e)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private parseSummaryBlock(
|
|
302
|
+
buffer: Buffer,
|
|
303
|
+
startOffset: number,
|
|
304
|
+
request?: CoordRequest,
|
|
305
|
+
) {
|
|
306
|
+
const features = [] as any[]
|
|
307
|
+
let offset = startOffset
|
|
308
|
+
|
|
309
|
+
const dataView = new DataView(
|
|
310
|
+
buffer.buffer,
|
|
311
|
+
buffer.byteOffset,
|
|
312
|
+
buffer.length,
|
|
313
|
+
)
|
|
314
|
+
while (offset < buffer.byteLength) {
|
|
315
|
+
// this was extracted from looking at the runtime code generated by
|
|
316
|
+
// binary-parser
|
|
317
|
+
const chromId = dataView.getUint32(offset, true)
|
|
318
|
+
offset += 4
|
|
319
|
+
const start = dataView.getUint32(offset, true)
|
|
320
|
+
offset += 4
|
|
321
|
+
const end = dataView.getUint32(offset, true)
|
|
322
|
+
offset += 4
|
|
323
|
+
const validCnt = dataView.getUint32(offset, true)
|
|
324
|
+
offset += 4
|
|
325
|
+
const minScore = dataView.getFloat32(offset, true)
|
|
326
|
+
offset += 4
|
|
327
|
+
const maxScore = dataView.getFloat32(offset, true)
|
|
328
|
+
offset += 4
|
|
329
|
+
const sumData = dataView.getFloat32(offset, true)
|
|
330
|
+
offset += 4
|
|
331
|
+
// unused
|
|
332
|
+
// const sumSqData = dataView.getFloat32(offset, true)
|
|
333
|
+
offset += 4
|
|
334
|
+
|
|
335
|
+
if (
|
|
336
|
+
request
|
|
337
|
+
? chromId === request.chrId &&
|
|
338
|
+
coordFilter(start, end, request.start, request.end)
|
|
339
|
+
: true
|
|
340
|
+
) {
|
|
341
|
+
features.push({
|
|
342
|
+
start,
|
|
343
|
+
end,
|
|
344
|
+
maxScore,
|
|
345
|
+
minScore,
|
|
346
|
+
summary: true,
|
|
347
|
+
score: sumData / (validCnt || 1),
|
|
348
|
+
})
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return features
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private parseBigBedBlock(
|
|
356
|
+
data: Buffer,
|
|
357
|
+
startOffset: number,
|
|
358
|
+
offset: number,
|
|
359
|
+
request?: CoordRequest,
|
|
360
|
+
) {
|
|
361
|
+
const items = [] as Feature[]
|
|
362
|
+
let currOffset = startOffset
|
|
363
|
+
while (currOffset < data.byteLength) {
|
|
364
|
+
const res = this.bigBedParser.parse(data.subarray(currOffset))
|
|
365
|
+
items.push({ ...res, uniqueId: `bb-${offset + currOffset}` })
|
|
366
|
+
currOffset += res.offset
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return request
|
|
370
|
+
? items.filter((f: any) =>
|
|
371
|
+
coordFilter(f.start, f.end, request.start, request.end),
|
|
372
|
+
)
|
|
373
|
+
: items
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private parseBigWigBlock(
|
|
377
|
+
buffer: Buffer,
|
|
378
|
+
startOffset: number,
|
|
379
|
+
request?: CoordRequest,
|
|
380
|
+
) {
|
|
381
|
+
const b = buffer.subarray(startOffset)
|
|
382
|
+
|
|
383
|
+
const dataView = new DataView(b.buffer, b.byteOffset, b.length)
|
|
384
|
+
let offset = 0
|
|
385
|
+
offset += 4
|
|
386
|
+
const blockStart = dataView.getInt32(offset, true)
|
|
387
|
+
offset += 8
|
|
388
|
+
const itemStep = dataView.getUint32(offset, true)
|
|
389
|
+
offset += 4
|
|
390
|
+
const itemSpan = dataView.getUint32(offset, true)
|
|
391
|
+
offset += 4
|
|
392
|
+
const blockType = dataView.getUint8(offset)
|
|
393
|
+
offset += 2
|
|
394
|
+
const itemCount = dataView.getUint16(offset, true)
|
|
395
|
+
offset += 2
|
|
396
|
+
const items = new Array(itemCount)
|
|
397
|
+
switch (blockType) {
|
|
398
|
+
case 1:
|
|
399
|
+
for (let i = 0; i < itemCount; i++) {
|
|
400
|
+
const start = dataView.getInt32(offset, true)
|
|
401
|
+
offset += 4
|
|
402
|
+
const end = dataView.getInt32(offset, true)
|
|
403
|
+
offset += 4
|
|
404
|
+
const score = dataView.getFloat32(offset, true)
|
|
405
|
+
offset += 4
|
|
406
|
+
items[i] = { start, end, score }
|
|
407
|
+
}
|
|
408
|
+
break
|
|
409
|
+
case 2:
|
|
410
|
+
for (let i = 0; i < itemCount; i++) {
|
|
411
|
+
const start = dataView.getInt32(offset, true)
|
|
412
|
+
offset += 4
|
|
413
|
+
const score = dataView.getFloat32(offset, true)
|
|
414
|
+
offset += 4
|
|
415
|
+
items[i] = { score, start, end: start + itemSpan }
|
|
416
|
+
}
|
|
417
|
+
break
|
|
418
|
+
case 3:
|
|
419
|
+
for (let i = 0; i < itemCount; i++) {
|
|
420
|
+
const score = dataView.getFloat32(offset, true)
|
|
421
|
+
offset += 4
|
|
422
|
+
const start = blockStart + i * itemStep
|
|
423
|
+
items[i] = { score, start, end: start + itemSpan }
|
|
424
|
+
}
|
|
425
|
+
break
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return request
|
|
429
|
+
? items.filter((f: any) =>
|
|
430
|
+
coordFilter(f.start, f.end, request.start, request.end),
|
|
431
|
+
)
|
|
432
|
+
: items
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
public async readFeatures(
|
|
436
|
+
observer: Observer<Feature[]>,
|
|
437
|
+
blocks: { offset: bigint; length: bigint }[],
|
|
438
|
+
opts: Options = {},
|
|
439
|
+
) {
|
|
440
|
+
try {
|
|
441
|
+
const { blockType, isCompressed } = this
|
|
442
|
+
const { signal, request } = opts
|
|
443
|
+
const blockGroupsToFetch = groupBlocks(blocks)
|
|
444
|
+
checkAbortSignal(signal)
|
|
445
|
+
await Promise.all(
|
|
446
|
+
blockGroupsToFetch.map(async blockGroup => {
|
|
447
|
+
checkAbortSignal(signal)
|
|
448
|
+
const { length, offset } = blockGroup
|
|
449
|
+
const data = await this.featureCache.get(
|
|
450
|
+
`${length}_${offset}`,
|
|
451
|
+
blockGroup,
|
|
452
|
+
signal,
|
|
453
|
+
)
|
|
454
|
+
blockGroup.blocks.forEach(block => {
|
|
455
|
+
checkAbortSignal(signal)
|
|
456
|
+
let blockOffset = Number(block.offset) - Number(blockGroup.offset)
|
|
457
|
+
let resultData = data
|
|
458
|
+
if (isCompressed) {
|
|
459
|
+
resultData = unzip(data.subarray(blockOffset))
|
|
460
|
+
blockOffset = 0
|
|
461
|
+
}
|
|
462
|
+
checkAbortSignal(signal)
|
|
463
|
+
|
|
464
|
+
switch (blockType) {
|
|
465
|
+
case 'summary':
|
|
466
|
+
observer.next(
|
|
467
|
+
this.parseSummaryBlock(resultData, blockOffset, request),
|
|
468
|
+
)
|
|
469
|
+
break
|
|
470
|
+
case 'bigwig':
|
|
471
|
+
observer.next(
|
|
472
|
+
this.parseBigWigBlock(resultData, blockOffset, request),
|
|
473
|
+
)
|
|
474
|
+
break
|
|
475
|
+
case 'bigbed':
|
|
476
|
+
observer.next(
|
|
477
|
+
this.parseBigBedBlock(
|
|
478
|
+
resultData,
|
|
479
|
+
blockOffset,
|
|
480
|
+
Number(block.offset) * (1 << 8),
|
|
481
|
+
request,
|
|
482
|
+
),
|
|
483
|
+
)
|
|
484
|
+
break
|
|
485
|
+
default:
|
|
486
|
+
console.warn(`Don't know what to do with ${blockType}`)
|
|
487
|
+
}
|
|
488
|
+
})
|
|
489
|
+
}),
|
|
490
|
+
)
|
|
491
|
+
observer.complete()
|
|
492
|
+
} catch (e) {
|
|
493
|
+
observer.error(e)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
package/src/declare.d.ts
ADDED
package/src/index.ts
ADDED
package/src/range.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* eslint prefer-rest-params:0, no-nested-ternary:0 */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adapted from a combination of Range and _Compound in the
|
|
5
|
+
* Dalliance Genome Explorer, (c) Thomas Down 2006-2010.
|
|
6
|
+
*/
|
|
7
|
+
export default class Range {
|
|
8
|
+
public ranges: any
|
|
9
|
+
|
|
10
|
+
public constructor(arg1: any, arg2?: any) {
|
|
11
|
+
this.ranges =
|
|
12
|
+
arguments.length === 2
|
|
13
|
+
? [{ min: arg1, max: arg2 }]
|
|
14
|
+
: 0 in arg1
|
|
15
|
+
? Object.assign({}, arg1)
|
|
16
|
+
: [arg1]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public min(): number {
|
|
20
|
+
return this.ranges[0].min
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public max(): number {
|
|
24
|
+
return this.ranges[this.ranges.length - 1].max
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public contains(pos: number): boolean {
|
|
28
|
+
for (let s = 0; s < this.ranges.length; s += 1) {
|
|
29
|
+
const r = this.ranges[s]
|
|
30
|
+
if (r.min <= pos && r.max >= pos) {
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public isContiguous(): boolean {
|
|
38
|
+
return this.ranges.length > 1
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public getRanges(): Range[] {
|
|
42
|
+
return this.ranges.map((r: Range) => new Range(r.min, r.max))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public toString(): string {
|
|
46
|
+
return this.ranges.map((r: Range) => `[${r.min}-${r.max}]`).join(',')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public union(s1: Range): Range {
|
|
50
|
+
const ranges = this.getRanges().concat(s1.getRanges()).sort(this.rangeOrder)
|
|
51
|
+
const oranges = []
|
|
52
|
+
let current = ranges[0]
|
|
53
|
+
|
|
54
|
+
for (let i = 1; i < ranges.length; i += 1) {
|
|
55
|
+
const nxt = ranges[i]
|
|
56
|
+
if (nxt.min() > current.max() + 1) {
|
|
57
|
+
oranges.push(current)
|
|
58
|
+
current = nxt
|
|
59
|
+
} else if (nxt.max() > current.max()) {
|
|
60
|
+
current = new Range(current.min(), nxt.max())
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
oranges.push(current)
|
|
64
|
+
|
|
65
|
+
if (oranges.length === 1) {
|
|
66
|
+
return oranges[0]
|
|
67
|
+
}
|
|
68
|
+
return new Range(oranges)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public intersection(arg: Range): Range {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
73
|
+
let s0 = this
|
|
74
|
+
let s1 = arg
|
|
75
|
+
const r0 = this.ranges()
|
|
76
|
+
const r1 = s1.ranges()
|
|
77
|
+
const l0 = r0.length
|
|
78
|
+
|
|
79
|
+
const l1 = r1.length
|
|
80
|
+
let i0 = 0
|
|
81
|
+
|
|
82
|
+
let i1 = 0
|
|
83
|
+
const or = []
|
|
84
|
+
|
|
85
|
+
while (i0 < l0 && i1 < l1) {
|
|
86
|
+
s0 = r0[i0]
|
|
87
|
+
s1 = r1[i1]
|
|
88
|
+
const lapMin = Math.max(s0.min(), s1.min())
|
|
89
|
+
const lapMax = Math.min(s0.max(), s1.max())
|
|
90
|
+
if (lapMax >= lapMin) {
|
|
91
|
+
or.push(new Range(lapMin, lapMax))
|
|
92
|
+
}
|
|
93
|
+
if (s0.max() > s1.max()) {
|
|
94
|
+
i1 += 1
|
|
95
|
+
} else {
|
|
96
|
+
i0 += 1
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (or.length === 0) {
|
|
101
|
+
throw new Error('found range of length 0')
|
|
102
|
+
}
|
|
103
|
+
if (or.length === 1) {
|
|
104
|
+
return or[0]
|
|
105
|
+
}
|
|
106
|
+
return new Range(or)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
public coverage(): number {
|
|
110
|
+
let tot = 0
|
|
111
|
+
const rl = this.ranges()
|
|
112
|
+
for (let ri = 0; ri < rl.length; ri += 1) {
|
|
113
|
+
const r = rl[ri]
|
|
114
|
+
tot += r.max() - r.min() + 1
|
|
115
|
+
}
|
|
116
|
+
return tot
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public rangeOrder(tmpa: Range, tmpb: Range): number {
|
|
120
|
+
let a = tmpa
|
|
121
|
+
let b = tmpb
|
|
122
|
+
if (arguments.length < 2) {
|
|
123
|
+
b = a
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
125
|
+
a = this
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (a.min() < b.min()) {
|
|
129
|
+
return -1
|
|
130
|
+
}
|
|
131
|
+
if (a.min() > b.min()) {
|
|
132
|
+
return 1
|
|
133
|
+
}
|
|
134
|
+
if (a.max() < b.max()) {
|
|
135
|
+
return -1
|
|
136
|
+
}
|
|
137
|
+
if (b.max() > a.max()) {
|
|
138
|
+
return 1
|
|
139
|
+
}
|
|
140
|
+
return 0
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/unzip.ts
ADDED