@gmod/bbi 8.1.2 → 9.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +0 -24
  2. package/dist/array-feature-view.d.ts +1 -1
  3. package/dist/array-feature-view.js.map +1 -1
  4. package/dist/bbi.d.ts +5 -25
  5. package/dist/bbi.js +31 -133
  6. package/dist/bbi.js.map +1 -1
  7. package/dist/bigbed.d.ts +0 -1
  8. package/dist/bigbed.js +20 -51
  9. package/dist/bigbed.js.map +1 -1
  10. package/dist/bigwig.d.ts +1 -2
  11. package/dist/bigwig.js +1 -2
  12. package/dist/bigwig.js.map +1 -1
  13. package/dist/block-view.d.ts +7 -16
  14. package/dist/block-view.js +369 -443
  15. package/dist/block-view.js.map +1 -1
  16. package/dist/parse-bigwig.d.ts +2 -2
  17. package/dist/parse-bigwig.js +5 -5
  18. package/dist/parse-bigwig.js.map +1 -1
  19. package/dist/range.d.ts +1 -15
  20. package/dist/range.js +16 -49
  21. package/dist/range.js.map +1 -1
  22. package/dist/util.d.ts +0 -22
  23. package/dist/util.js +0 -46
  24. package/dist/util.js.map +1 -1
  25. package/dist/wasm/inflate-wasm-inlined.js +3 -3
  26. package/dist/wasm/inflate-wasm-inlined.js.map +1 -1
  27. package/dist/wasm/inflate_wasm_bg.d.ts +1 -1
  28. package/dist/wasm/inflate_wasm_bg.js +2 -2
  29. package/esm/array-feature-view.d.ts +1 -1
  30. package/esm/array-feature-view.js.map +1 -1
  31. package/esm/bbi.d.ts +5 -25
  32. package/esm/bbi.js +31 -133
  33. package/esm/bbi.js.map +1 -1
  34. package/esm/bigbed.d.ts +0 -1
  35. package/esm/bigbed.js +20 -50
  36. package/esm/bigbed.js.map +1 -1
  37. package/esm/bigwig.d.ts +1 -2
  38. package/esm/bigwig.js +1 -2
  39. package/esm/bigwig.js.map +1 -1
  40. package/esm/block-view.d.ts +7 -16
  41. package/esm/block-view.js +369 -443
  42. package/esm/block-view.js.map +1 -1
  43. package/esm/parse-bigwig.d.ts +2 -2
  44. package/esm/parse-bigwig.js +5 -5
  45. package/esm/parse-bigwig.js.map +1 -1
  46. package/esm/range.d.ts +1 -15
  47. package/esm/range.js +15 -48
  48. package/esm/range.js.map +1 -1
  49. package/esm/util.d.ts +0 -22
  50. package/esm/util.js +0 -42
  51. package/esm/util.js.map +1 -1
  52. package/esm/wasm/inflate-wasm-inlined.js +3 -3
  53. package/esm/wasm/inflate-wasm-inlined.js.map +1 -1
  54. package/esm/wasm/inflate_wasm_bg.d.ts +1 -1
  55. package/esm/wasm/inflate_wasm_bg.js +1 -1
  56. package/package.json +21 -24
  57. package/src/array-feature-view.ts +5 -2
  58. package/src/bbi.ts +50 -153
  59. package/src/bigbed.ts +23 -55
  60. package/src/bigwig.ts +1 -3
  61. package/src/block-view.ts +525 -639
  62. package/src/parse-bigwig.ts +7 -9
  63. package/src/range.ts +19 -58
  64. package/src/util.ts +0 -46
  65. package/src/wasm/inflate-wasm-inlined.js +3 -3
  66. package/src/wasm/inflate_wasm_bg.js +1 -1
  67. package/src/wasm/inflate_wasm_bg.wasm +0 -0
  68. package/CHANGELOG.md +0 -350
package/src/bbi.ts CHANGED
@@ -1,6 +1,4 @@
1
1
  import { LocalFile, RemoteFile } from 'generic-filehandle2'
2
- import { Observable, firstValueFrom } from 'rxjs'
3
- import { toArray } from 'rxjs/operators'
4
2
 
5
3
  import { BlockView } from './block-view.ts'
6
4
 
@@ -8,7 +6,6 @@ import type {
8
6
  BigWigFeatureArrays,
9
7
  BigWigHeader,
10
8
  BigWigHeaderWithRefNames,
11
- Feature,
12
9
  RefInfo,
13
10
  RequestOptions2,
14
11
  RequestOptions,
@@ -89,13 +86,12 @@ export abstract class BBI {
89
86
  const b = await this.bbi.read(requestSize, 0, opts)
90
87
  const dataView = getDataView(b)
91
88
 
92
- const r1 = dataView.getInt32(0, true)
93
- if (r1 !== BIG_WIG_MAGIC && r1 !== BIG_BED_MAGIC) {
94
- throw new Error('not a BigWig/BigBed file')
95
- }
96
89
  let offset = 0
97
90
  const magic = dataView.getInt32(offset, true)
98
91
  offset += 4
92
+ if (magic !== BIG_WIG_MAGIC && magic !== BIG_BED_MAGIC) {
93
+ throw new Error('not a BigWig/BigBed file')
94
+ }
99
95
  const version = dataView.getUint16(offset, true)
100
96
  offset += 2
101
97
  const numZoomLevels = dataView.getUint16(offset, true)
@@ -159,7 +155,6 @@ export abstract class BBI {
159
155
  const scoreSum = dataView.getFloat64(offset, true)
160
156
  offset += 8
161
157
  const scoreSumSquares = dataView.getFloat64(offset, true)
162
- offset += 8
163
158
 
164
159
  totalSummary = {
165
160
  scoreMin,
@@ -209,39 +204,23 @@ export abstract class BBI {
209
204
  const dataView = getDataView(
210
205
  await this.bbi.read(32, chromosomeTreeOffset, opts),
211
206
  )
212
- let offset = 0
213
- // const magic = dataView.getUint32(offset, true) // unused
214
- offset += 4
215
- // const blockSize = dataView.getUint32(offset, true) // unused
216
- offset += 4
217
- const keySize = dataView.getUint32(offset, true)
218
- offset += 4
219
- const valSize = dataView.getUint32(offset, true)
220
- offset += 4
221
- // const itemCount = dataView.getBigUint64(offset, true) // unused
222
- offset += 8
207
+ const keySize = dataView.getUint32(8, true)
208
+ const valSize = dataView.getUint32(12, true)
223
209
 
224
210
  // Recursively traverses the B+ tree to populate chromosome name-to-ID mappings
225
211
  const readBPlusTreeNode = async (currentOffset: number) => {
226
- const b = await this.bbi.read(4, currentOffset)
227
- const dataView = getDataView(b)
228
- let offset = 0
229
- const isLeafNode = dataView.getUint8(offset)
230
- offset += 1
231
- // const reserved = dataView.getUint8(offset) // unused
232
- offset += 1
233
- const count = dataView.getUint16(offset, true)
234
- offset += 2
212
+ const header = getDataView(await this.bbi.read(4, currentOffset))
213
+ const isLeafNode = header.getUint8(0)
214
+ const count = header.getUint16(2, true)
235
215
 
236
216
  // Leaf nodes contain the actual chromosome name-to-ID mappings
237
217
  if (isLeafNode) {
238
218
  const b = await this.bbi.read(
239
219
  count * (keySize + valSize),
240
- currentOffset + offset,
220
+ currentOffset + 4,
241
221
  )
242
222
  const dataView = getDataView(b)
243
- offset = 0
244
-
223
+ let offset = 0
245
224
  for (let n = 0; n < count; n++) {
246
225
  const keyEnd = b.indexOf(0, offset)
247
226
  const effectiveKeyEnd =
@@ -254,22 +233,16 @@ export abstract class BBI {
254
233
  offset += 4
255
234
  const refSize = dataView.getUint32(offset, true)
256
235
  offset += 4
257
-
258
236
  refsByName[this.renameRefSeqs(key)] = refId
259
- refsByNumber[refId] = {
260
- name: key,
261
- id: refId,
262
- length: refSize,
263
- }
237
+ refsByNumber[refId] = { name: key, id: refId, length: refSize }
264
238
  }
265
239
  } else {
266
240
  // Non-leaf nodes contain pointers to child nodes
267
- const nextNodes = []
268
241
  const dataView = getDataView(
269
- await this.bbi.read(count * (keySize + 8), currentOffset + offset),
242
+ await this.bbi.read(count * (keySize + 8), currentOffset + 4),
270
243
  )
271
- offset = 0
272
-
244
+ const nextNodes = []
245
+ let offset = 0
273
246
  for (let n = 0; n < count; n++) {
274
247
  offset += keySize
275
248
  const childOffset = Number(dataView.getBigUint64(offset, true))
@@ -286,6 +259,29 @@ export abstract class BBI {
286
259
  }
287
260
  }
288
261
 
262
+ private viewCache = new Map<string, BlockView>()
263
+
264
+ protected getOrCreateBlockView(
265
+ refsByName: Record<string, number>,
266
+ rTreeOffset: number,
267
+ uncompressBufSize: number,
268
+ blockType: string,
269
+ ) {
270
+ const key = `${rTreeOffset}_${blockType}`
271
+ let view = this.viewCache.get(key)
272
+ if (!view) {
273
+ view = new BlockView(
274
+ this.bbi,
275
+ refsByName,
276
+ rTreeOffset,
277
+ uncompressBufSize,
278
+ blockType,
279
+ )
280
+ this.viewCache.set(key, view)
281
+ }
282
+ return view
283
+ }
284
+
289
285
  /*
290
286
  * fetches the "unzoomed" view of the bigwig data. this is the default for bigbed
291
287
  * @param abortSignal - a signal to optionally abort this operation
@@ -293,8 +289,7 @@ export abstract class BBI {
293
289
  protected async getUnzoomedView(opts?: RequestOptions) {
294
290
  const { unzoomedIndexOffset, refsByName, uncompressBufSize, fileType } =
295
291
  await this.getHeader(opts)
296
- return new BlockView(
297
- this.bbi,
292
+ return this.getOrCreateBlockView(
298
293
  refsByName,
299
294
  unzoomedIndexOffset,
300
295
  uncompressBufSize,
@@ -310,44 +305,10 @@ export abstract class BBI {
310
305
  opts?: RequestOptions,
311
306
  ): Promise<BlockView>
312
307
 
313
- /**
314
- * Gets features from a BigWig file
315
- *
316
- * @param refName - The chromosome name
317
- *
318
- * @param start - The start of a region
319
- *
320
- * @param end - The end of a region
321
- *
322
- * @param opts - An object containing basesPerSpan (e.g. pixels per basepair)
323
- * or scale used to infer the zoomLevel to use
324
- */
325
- public async getFeatureStream(
326
- refName: string,
327
- start: number,
328
- end: number,
329
- opts?: RequestOptions2,
330
- ) {
331
- await this.getHeader(opts)
332
- const chrName = this.renameRefSeqs(refName)
333
- let view: BlockView
308
+ private async _getView(opts?: RequestOptions2) {
334
309
  const { basesPerSpan, scale } = opts || {}
335
-
336
- if (basesPerSpan) {
337
- view = await this.getView(1 / basesPerSpan, opts)
338
- } else if (scale) {
339
- view = await this.getView(scale, opts)
340
- } else {
341
- view = await this.getView(1, opts)
342
- }
343
-
344
- return new Observable<Feature[]>(observer => {
345
- view
346
- .readWigData(chrName, start, end, observer, opts)
347
- .catch((e: unknown) => {
348
- observer.error(e)
349
- })
350
- })
310
+ const viewScale = basesPerSpan ? 1 / basesPerSpan : (scale ?? 1)
311
+ return this.getView(viewScale, opts)
351
312
  }
352
313
 
353
314
  public async getFeatures(
@@ -356,86 +317,22 @@ export abstract class BBI {
356
317
  end: number,
357
318
  opts?: RequestOptions2,
358
319
  ) {
359
- const ob = await this.getFeatureStream(refName, start, end, opts)
360
-
361
- const arrays = await firstValueFrom(ob.pipe(toArray()))
362
- const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0)
363
- const result = new Array(totalLength)
364
- let index = 0
365
- for (const arr of arrays) {
366
- for (const item of arr) {
367
- result[index++] = item
368
- }
369
- }
370
- return result
320
+ const view = await this._getView(opts)
321
+ return view.readWigData(this.renameRefSeqs(refName), start, end, opts)
371
322
  }
372
323
 
373
- /**
374
- * Gets features from a BigWig file as typed arrays (more efficient than getFeatures)
375
- *
376
- * @param refName - The chromosome name
377
- * @param start - The start of a region
378
- * @param end - The end of a region
379
- * @param opts - Options including basesPerSpan or scale
380
- * @returns Promise with typed arrays: starts, ends, scores (and minScores/maxScores for summary data)
381
- */
382
324
  public async getFeaturesAsArrays(
383
325
  refName: string,
384
326
  start: number,
385
327
  end: number,
386
328
  opts?: RequestOptions2,
387
329
  ): Promise<BigWigFeatureArrays | SummaryFeatureArrays> {
388
- const features = await this.getFeatures(refName, start, end, opts)
389
- const count = features.length
390
-
391
- if (count === 0) {
392
- return {
393
- starts: new Int32Array(0),
394
- ends: new Int32Array(0),
395
- scores: new Float32Array(0),
396
- isSummary: false as const,
397
- }
398
- }
399
-
400
- const hasSummary = features[0]?.summary === true
401
-
402
- if (hasSummary) {
403
- const starts = new Int32Array(count)
404
- const ends = new Int32Array(count)
405
- const scores = new Float32Array(count)
406
- const minScores = new Float32Array(count)
407
- const maxScores = new Float32Array(count)
408
-
409
- for (let i = 0; i < count; i++) {
410
- const f = features[i]!
411
- starts[i] = f.start
412
- ends[i] = f.end
413
- scores[i] = f.score ?? 0
414
- minScores[i] = f.minScore ?? 0
415
- maxScores[i] = f.maxScore ?? 0
416
- }
417
-
418
- return {
419
- starts,
420
- ends,
421
- scores,
422
- minScores,
423
- maxScores,
424
- isSummary: true as const,
425
- }
426
- }
427
-
428
- const starts = new Int32Array(count)
429
- const ends = new Int32Array(count)
430
- const scores = new Float32Array(count)
431
-
432
- for (let i = 0; i < count; i++) {
433
- const f = features[i]!
434
- starts[i] = f.start
435
- ends[i] = f.end
436
- scores[i] = f.score ?? 0
437
- }
438
-
439
- return { starts, ends, scores, isSummary: false as const }
330
+ const view = await this._getView(opts)
331
+ return view.readWigDataAsArrays(
332
+ this.renameRefSeqs(refName),
333
+ start,
334
+ end,
335
+ opts,
336
+ )
440
337
  }
441
338
  }
package/src/bigbed.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import AbortablePromiseCache from '@gmod/abortable-promise-cache'
2
2
  import QuickLRU from '@jbrowse/quick-lru'
3
- import { Observable, firstValueFrom, merge } from 'rxjs'
4
- import { map, reduce } from 'rxjs/operators'
5
3
 
6
4
  import { BBI } from './bbi.ts'
7
5
 
8
- import type { Feature, RequestOptions } from './types.ts'
6
+ import type { RequestOptions } from './types.ts'
9
7
  import type { GenericFilehandle } from 'generic-filehandle2'
10
8
 
11
9
  const decoder = new TextDecoder('utf8')
@@ -24,10 +22,6 @@ interface Index {
24
22
  field: number
25
23
  }
26
24
 
27
- export function filterUndef<T>(ts: (T | undefined)[]): T[] {
28
- return ts.filter((t: T | undefined): t is T => !!t)
29
- }
30
-
31
25
  function getTabField(str: string, fieldIndex: number) {
32
26
  if (fieldIndex < 0) {
33
27
  return undefined
@@ -67,11 +61,9 @@ async function readBPlusTreeNode(
67
61
  const len = 4 + blockSize * (keySize + valSize)
68
62
  const buffer = await bbi.read(len, nodeOffset, opts)
69
63
  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
64
+ const nodeType = dataView.getInt8(0)
65
+ const cnt = dataView.getInt16(2, true)
66
+ let offset = 4
75
67
 
76
68
  // Non-leaf node (nodeType === 0): contains keys and child node pointers for navigation
77
69
  if (nodeType === 0) {
@@ -153,6 +145,7 @@ async function readBPlusTreeNode(
153
145
 
154
146
  return undefined
155
147
  }
148
+ return undefined
156
149
  }
157
150
 
158
151
  export class BigBed extends BBI {
@@ -186,13 +179,8 @@ export class BigBed extends BBI {
186
179
  const b = await this.bbi.read(64, extHeaderOffset)
187
180
 
188
181
  const dataView = new DataView(b.buffer, b.byteOffset, b.length)
189
- let offset = 0
190
- // const _size = dataView.getUint16(offset, true)
191
- offset += 2
192
- const count = dataView.getUint16(offset, true)
193
- offset += 2
194
- const dataOffset = Number(dataView.getBigUint64(offset, true))
195
- offset += 8
182
+ const count = dataView.getUint16(2, true)
183
+ const dataOffset = Number(dataView.getBigUint64(4, true))
196
184
 
197
185
  // no extra index is defined if count==0
198
186
  if (count === 0) {
@@ -208,14 +196,10 @@ export class BigBed extends BBI {
208
196
  for (let i = 0; i < count; i += 1) {
209
197
  const b = buffer.subarray(i * blocklen)
210
198
  const dataView = new DataView(b.buffer, b.byteOffset, b.length)
211
- let offset = 0
212
- const type = dataView.getInt16(offset, true)
213
- offset += 2
214
- const fieldcount = dataView.getInt16(offset, true)
215
- offset += 2
216
- const dataOffset = Number(dataView.getBigUint64(offset, true))
217
- offset += 8 + 4 // skip 8-byte offset + 4 reserved bytes
218
- const field = dataView.getInt16(offset, true)
199
+ const type = dataView.getInt16(0, true)
200
+ const fieldcount = dataView.getInt16(2, true)
201
+ const dataOffset = Number(dataView.getBigUint64(4, true))
202
+ const field = dataView.getInt16(16, true)
219
203
  indices.push({
220
204
  type,
221
205
  fieldcount,
@@ -249,17 +233,9 @@ export class BigBed extends BBI {
249
233
  const b = await this.bbi.read(32, offset2, opts)
250
234
 
251
235
  const dataView = new DataView(b.buffer, b.byteOffset, b.length)
252
- let offset = 0
253
- // const _magic = dataView.getInt32(offset, true)
254
- offset += 4
255
- const blockSize = dataView.getInt32(offset, true)
256
- offset += 4
257
- const keySize = dataView.getInt32(offset, true)
258
- offset += 4
259
- const valSize = dataView.getInt32(offset, true)
260
- offset += 4
261
- // const _itemCount = Number(dataView.getBigUint64(offset, true))
262
- offset += 8
236
+ const blockSize = dataView.getInt32(4, true)
237
+ const keySize = dataView.getInt32(8, true)
238
+ const valSize = dataView.getInt32(12, true)
263
239
 
264
240
  return readBPlusTreeNode(
265
241
  this.bbi,
@@ -272,7 +248,8 @@ export class BigBed extends BBI {
272
248
  opts,
273
249
  )
274
250
  })
275
- return filterUndef(await Promise.all(locs))
251
+ const results = await Promise.all(locs)
252
+ return results.filter((l): l is Loc => l !== undefined)
276
253
  }
277
254
 
278
255
  /*
@@ -292,23 +269,14 @@ export class BigBed extends BBI {
292
269
  return []
293
270
  }
294
271
  const view = await this.getUnzoomedView(opts)
295
- const res = blocks.map(block => {
296
- return new Observable<Feature[]>(observer => {
297
- view.readFeatures(observer, [block], opts).catch((e: unknown) => {
298
- observer.error(e)
299
- })
300
- }).pipe(
301
- reduce((acc, curr) => {
302
- acc.push(...curr)
303
- return acc
304
- }, [] as Feature[]),
305
- map(features => features.map(f => ({ ...f, field: block.field }))),
306
- )
307
- })
308
- const ret = await firstValueFrom(merge(...res))
309
- // Filter to features where the indexed field matches the search name
272
+ const results = await Promise.all(
273
+ blocks.map(async block => {
274
+ const features = await view.readFeatures([block], opts)
275
+ return features.map(f => ({ ...f, field: block.field }))
276
+ }),
277
+ )
310
278
  // field offset is adjusted by -3 to account for chrom, chromStart, chromEnd columns
311
- return ret.filter(f => {
279
+ return results.flat().filter(f => {
312
280
  if (!f.rest) {
313
281
  return false
314
282
  }
package/src/bigwig.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { BBI } from './bbi.ts'
2
- import { BlockView } from './block-view.ts'
3
2
 
4
3
  import type { RequestOptions } from './types.ts'
5
4
 
@@ -21,8 +20,7 @@ export class BigWig extends BBI {
21
20
  for (let i = maxLevel; i >= 0; i -= 1) {
22
21
  const zh = zoomLevels[i]
23
22
  if (zh && zh.reductionLevel <= 2 * basesPerPx) {
24
- return new BlockView(
25
- this.bbi,
23
+ return this.getOrCreateBlockView(
26
24
  refsByName,
27
25
  zh.indexOffset,
28
26
  uncompressBufSize,