@gmod/bbi 1.0.30 → 1.0.33
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 +17 -57
- package/README.md +17 -27
- package/dist/bbi.js +354 -552
- package/dist/bigbed.d.ts +1 -1
- package/dist/bigbed.js +278 -470
- package/dist/bigwig.js +88 -112
- package/dist/blockView.js +403 -494
- package/dist/index.js +6 -41
- package/dist/range.js +115 -176
- package/dist/unzip-pako.d.ts +2 -0
- package/dist/unzip-pako.js +8 -0
- package/dist/unzip.d.ts +2 -0
- package/dist/unzip.js +5 -0
- package/dist/util.js +110 -116
- package/esm/bbi.d.ts +84 -0
- package/esm/bbi.js +259 -0
- package/esm/bigbed.d.ts +12 -0
- package/esm/bigbed.js +182 -0
- package/esm/bigwig.d.ts +13 -0
- package/esm/bigwig.js +35 -0
- package/esm/blockView.d.ts +42 -0
- package/esm/blockView.js +321 -0
- package/esm/index.d.ts +3 -0
- package/esm/index.js +7 -0
- package/esm/range.d.ts +18 -0
- package/esm/range.js +126 -0
- package/esm/unzip-pako.d.ts +2 -0
- package/esm/unzip-pako.js +8 -0
- package/esm/unzip.d.ts +2 -0
- package/esm/unzip.js +5 -0
- package/esm/util.d.ts +24 -0
- package/esm/util.js +74 -0
- package/package.json +29 -31
- package/dist/declares.d.js +0 -2
package/esm/bigbed.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BigBed = exports.filterUndef = void 0;
|
|
7
|
+
const binary_parser_1 = require("@gmod/binary-parser");
|
|
8
|
+
const rxjs_1 = require("rxjs");
|
|
9
|
+
const operators_1 = require("rxjs/operators");
|
|
10
|
+
const abortable_promise_cache_1 = __importDefault(require("abortable-promise-cache"));
|
|
11
|
+
const quick_lru_1 = __importDefault(require("quick-lru"));
|
|
12
|
+
const bbi_1 = require("./bbi");
|
|
13
|
+
function filterUndef(ts) {
|
|
14
|
+
return ts.filter((t) => !!t);
|
|
15
|
+
}
|
|
16
|
+
exports.filterUndef = filterUndef;
|
|
17
|
+
class BigBed extends bbi_1.BBI {
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
super(opts);
|
|
20
|
+
this.readIndicesCache = new abortable_promise_cache_1.default({
|
|
21
|
+
cache: new quick_lru_1.default({ maxSize: 1 }),
|
|
22
|
+
fill: async (args, signal) => {
|
|
23
|
+
return this._readIndices({ ...args, signal });
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
readIndices(opts = {}) {
|
|
28
|
+
const options = 'aborted' in opts ? { signal: opts } : opts;
|
|
29
|
+
return this.readIndicesCache.get(JSON.stringify(options), options, options.signal);
|
|
30
|
+
}
|
|
31
|
+
/*
|
|
32
|
+
* retrieve unzoomed view for any scale
|
|
33
|
+
* @param scale - unused
|
|
34
|
+
* @param abortSignal - an optional AbortSignal to kill operation
|
|
35
|
+
* @return promise for a BlockView
|
|
36
|
+
*/
|
|
37
|
+
async getView(scale, opts) {
|
|
38
|
+
return this.getUnzoomedView(opts);
|
|
39
|
+
}
|
|
40
|
+
/*
|
|
41
|
+
* parse the bigbed extraIndex fields
|
|
42
|
+
* @param abortSignal to abort operation
|
|
43
|
+
* @return a Promise for an array of Index data structure since there can be multiple extraIndexes in a bigbed, see bedToBigBed documentation
|
|
44
|
+
*/
|
|
45
|
+
async _readIndices(opts) {
|
|
46
|
+
const { extHeaderOffset, isBigEndian } = await this.getHeader(opts);
|
|
47
|
+
const { buffer: data } = await this.bbi.read(Buffer.alloc(64), 0, 64, extHeaderOffset);
|
|
48
|
+
const le = isBigEndian ? 'big' : 'little';
|
|
49
|
+
const ret = new binary_parser_1.Parser()
|
|
50
|
+
.endianess(le)
|
|
51
|
+
.uint16('size')
|
|
52
|
+
.uint16('count')
|
|
53
|
+
.uint64('offset')
|
|
54
|
+
.parse(data).result;
|
|
55
|
+
const { count, offset } = ret;
|
|
56
|
+
// no extra index is defined if count==0
|
|
57
|
+
if (count === 0) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const blocklen = 20;
|
|
61
|
+
const len = blocklen * count;
|
|
62
|
+
const { buffer } = await this.bbi.read(Buffer.alloc(len), 0, len, offset);
|
|
63
|
+
const extParser = new binary_parser_1.Parser()
|
|
64
|
+
.endianess(le)
|
|
65
|
+
.int16('type')
|
|
66
|
+
.int16('fieldcount')
|
|
67
|
+
.uint64('offset')
|
|
68
|
+
.skip(4)
|
|
69
|
+
.int16('field');
|
|
70
|
+
const indices = [];
|
|
71
|
+
for (let i = 0; i < count; i += 1) {
|
|
72
|
+
indices.push(extParser.parse(buffer.subarray(i * blocklen)).result);
|
|
73
|
+
}
|
|
74
|
+
return indices;
|
|
75
|
+
}
|
|
76
|
+
/*
|
|
77
|
+
* perform a search in the bigbed extraIndex to find which blocks in the bigbed data to look for the
|
|
78
|
+
* actual feature data
|
|
79
|
+
*
|
|
80
|
+
* @param name - the name to search for
|
|
81
|
+
* @param opts - a SearchOptions argument with optional signal
|
|
82
|
+
* @return a Promise for an array of bigbed block Loc entries
|
|
83
|
+
*/
|
|
84
|
+
async searchExtraIndexBlocks(name, opts = {}) {
|
|
85
|
+
const { isBigEndian } = await this.getHeader(opts);
|
|
86
|
+
const indices = await this.readIndices(opts);
|
|
87
|
+
if (!indices.length) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const locs = indices.map(async (index) => {
|
|
91
|
+
const { offset, field } = index;
|
|
92
|
+
const { buffer: data } = await this.bbi.read(Buffer.alloc(32), 0, 32, offset, opts);
|
|
93
|
+
const p = new binary_parser_1.Parser()
|
|
94
|
+
.endianess(isBigEndian ? 'big' : 'little')
|
|
95
|
+
.int32('magic')
|
|
96
|
+
.int32('blockSize')
|
|
97
|
+
.int32('keySize')
|
|
98
|
+
.int32('valSize')
|
|
99
|
+
.uint64('itemCount');
|
|
100
|
+
const { blockSize, keySize, valSize } = p.parse(data).result;
|
|
101
|
+
const bpt = new binary_parser_1.Parser()
|
|
102
|
+
.endianess(isBigEndian ? 'big' : 'little')
|
|
103
|
+
.int8('nodeType')
|
|
104
|
+
.skip(1)
|
|
105
|
+
.int16('cnt')
|
|
106
|
+
.choice({
|
|
107
|
+
tag: 'nodeType',
|
|
108
|
+
choices: {
|
|
109
|
+
0: new binary_parser_1.Parser().array('leafkeys', {
|
|
110
|
+
length: 'cnt',
|
|
111
|
+
type: new binary_parser_1.Parser()
|
|
112
|
+
.string('key', { length: keySize, stripNull: true })
|
|
113
|
+
.uint64('offset'),
|
|
114
|
+
}),
|
|
115
|
+
1: new binary_parser_1.Parser().array('keys', {
|
|
116
|
+
length: 'cnt',
|
|
117
|
+
type: new binary_parser_1.Parser()
|
|
118
|
+
.string('key', { length: keySize, stripNull: true })
|
|
119
|
+
.uint64('offset')
|
|
120
|
+
.uint32('length')
|
|
121
|
+
.uint32('reserved'),
|
|
122
|
+
}),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
const bptReadNode = async (nodeOffset) => {
|
|
126
|
+
const len = 4 + blockSize * (keySize + valSize);
|
|
127
|
+
const { buffer } = await this.bbi.read(Buffer.alloc(len), 0, len, nodeOffset, opts);
|
|
128
|
+
const node = bpt.parse(buffer).result;
|
|
129
|
+
if (node.leafkeys) {
|
|
130
|
+
let lastOffset;
|
|
131
|
+
for (let i = 0; i < node.leafkeys.length; i += 1) {
|
|
132
|
+
const { key } = node.leafkeys[i];
|
|
133
|
+
if (name.localeCompare(key) < 0 && lastOffset) {
|
|
134
|
+
return bptReadNode(lastOffset);
|
|
135
|
+
}
|
|
136
|
+
lastOffset = node.leafkeys[i].offset;
|
|
137
|
+
}
|
|
138
|
+
return bptReadNode(lastOffset);
|
|
139
|
+
}
|
|
140
|
+
for (let i = 0; i < node.keys.length; i += 1) {
|
|
141
|
+
if (node.keys[i].key === name) {
|
|
142
|
+
return { ...node.keys[i], field };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
};
|
|
147
|
+
const rootNodeOffset = 32;
|
|
148
|
+
return bptReadNode(offset + rootNodeOffset);
|
|
149
|
+
});
|
|
150
|
+
return filterUndef(await Promise.all(locs));
|
|
151
|
+
}
|
|
152
|
+
/*
|
|
153
|
+
* retrieve the features from the bigbed data that were found through the lookup of the extraIndex
|
|
154
|
+
* note that there can be multiple extraIndex, see the BigBed specification and the -extraIndex argument to bedToBigBed
|
|
155
|
+
*
|
|
156
|
+
* @param name - the name to search for
|
|
157
|
+
* @param opts - a SearchOptions argument with optional signal
|
|
158
|
+
* @return a Promise for an array of Feature
|
|
159
|
+
*/
|
|
160
|
+
async searchExtraIndex(name, opts = {}) {
|
|
161
|
+
const blocks = await this.searchExtraIndexBlocks(name, opts);
|
|
162
|
+
if (!blocks.length) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
const view = await this.getUnzoomedView(opts);
|
|
166
|
+
const res = blocks.map(block => {
|
|
167
|
+
return new rxjs_1.Observable((observer) => {
|
|
168
|
+
view.readFeatures(observer, [block], opts);
|
|
169
|
+
}).pipe((0, operators_1.reduce)((acc, curr) => acc.concat(curr)), (0, operators_1.map)(x => {
|
|
170
|
+
for (let i = 0; i < x.length; i += 1) {
|
|
171
|
+
x[i].field = block.field;
|
|
172
|
+
}
|
|
173
|
+
return x;
|
|
174
|
+
}));
|
|
175
|
+
});
|
|
176
|
+
const ret = await (0, rxjs_1.merge)(...res).toPromise();
|
|
177
|
+
return ret.filter((f) => {
|
|
178
|
+
return f.rest.split('\t')[f.field - 3] === name;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.BigBed = BigBed;
|
package/esm/bigwig.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlockView } from './blockView';
|
|
2
|
+
import { BBI, RequestOptions } from './bbi';
|
|
3
|
+
export declare class BigWig extends BBI {
|
|
4
|
+
/**
|
|
5
|
+
* Retrieves a BlockView of a specific zoomLevel
|
|
6
|
+
*
|
|
7
|
+
* @param refName - The chromosome name
|
|
8
|
+
* @param start - The start of a region
|
|
9
|
+
* @param end - The end of a region
|
|
10
|
+
* @param opts - An object containing basesPerSpan (e.g. pixels per basepair) or scale used to infer the zoomLevel to use
|
|
11
|
+
*/
|
|
12
|
+
protected getView(scale: number, opts: RequestOptions): Promise<BlockView>;
|
|
13
|
+
}
|
package/esm/bigwig.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BigWig = void 0;
|
|
4
|
+
const blockView_1 = require("./blockView");
|
|
5
|
+
const bbi_1 = require("./bbi");
|
|
6
|
+
class BigWig extends bbi_1.BBI {
|
|
7
|
+
/**
|
|
8
|
+
* Retrieves a BlockView of a specific zoomLevel
|
|
9
|
+
*
|
|
10
|
+
* @param refName - The chromosome name
|
|
11
|
+
* @param start - The start of a region
|
|
12
|
+
* @param end - The end of a region
|
|
13
|
+
* @param opts - An object containing basesPerSpan (e.g. pixels per basepair) or scale used to infer the zoomLevel to use
|
|
14
|
+
*/
|
|
15
|
+
async getView(scale, opts) {
|
|
16
|
+
const { zoomLevels, refsByName, fileSize, isBigEndian, uncompressBufSize } = await this.getHeader(opts);
|
|
17
|
+
const basesPerPx = 1 / scale;
|
|
18
|
+
let maxLevel = zoomLevels.length;
|
|
19
|
+
if (!fileSize) {
|
|
20
|
+
// if we don't know the file size, we can't fetch the highest zoom level :-(
|
|
21
|
+
maxLevel -= 1;
|
|
22
|
+
}
|
|
23
|
+
for (let i = maxLevel; i >= 0; i -= 1) {
|
|
24
|
+
const zh = zoomLevels[i];
|
|
25
|
+
if (zh && zh.reductionLevel <= 2 * basesPerPx) {
|
|
26
|
+
const indexLength = i < zoomLevels.length - 1
|
|
27
|
+
? zoomLevels[i + 1].dataOffset - zh.indexOffset
|
|
28
|
+
: fileSize - 4 - zh.indexOffset;
|
|
29
|
+
return new blockView_1.BlockView(this.bbi, refsByName, zh.indexOffset, indexLength, isBigEndian, uncompressBufSize > 0, 'summary');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return this.getUnzoomedView(opts);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.BigWig = BigWig;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Observer } from 'rxjs';
|
|
2
|
+
import { GenericFilehandle } from 'generic-filehandle';
|
|
3
|
+
import { Feature } from './bbi';
|
|
4
|
+
interface CoordRequest {
|
|
5
|
+
chrId: number;
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
}
|
|
9
|
+
interface Options {
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
request?: CoordRequest;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* View into a subset of the data in a BigWig file.
|
|
15
|
+
*
|
|
16
|
+
* Adapted by Robert Buels and Colin Diesh from bigwig.js in the Dalliance Genome
|
|
17
|
+
* Explorer by Thomas Down.
|
|
18
|
+
* @constructs
|
|
19
|
+
*/
|
|
20
|
+
export declare class BlockView {
|
|
21
|
+
private cirTreeOffset;
|
|
22
|
+
private cirTreeLength;
|
|
23
|
+
private bbi;
|
|
24
|
+
private isCompressed;
|
|
25
|
+
private isBigEndian;
|
|
26
|
+
private refsByName;
|
|
27
|
+
private blockType;
|
|
28
|
+
private cirTreePromise?;
|
|
29
|
+
private featureCache;
|
|
30
|
+
private leafParser;
|
|
31
|
+
private bigWigParser;
|
|
32
|
+
private bigBedParser;
|
|
33
|
+
private summaryParser;
|
|
34
|
+
constructor(bbi: GenericFilehandle, refsByName: any, cirTreeOffset: number, cirTreeLength: number, isBigEndian: boolean, isCompressed: boolean, blockType: string);
|
|
35
|
+
readWigData(chrName: string, start: number, end: number, observer: Observer<Feature[]>, opts: Options): Promise<void>;
|
|
36
|
+
private parseSummaryBlock;
|
|
37
|
+
private parseBigBedBlock;
|
|
38
|
+
private parseBigWigBlock;
|
|
39
|
+
private static coordFilter;
|
|
40
|
+
readFeatures(observer: Observer<Feature[]>, blocks: any, opts?: Options): Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export {};
|
package/esm/blockView.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.BlockView = void 0;
|
|
7
|
+
const binary_parser_1 = require("@gmod/binary-parser");
|
|
8
|
+
const abortable_promise_cache_1 = __importDefault(require("abortable-promise-cache"));
|
|
9
|
+
const unzip_1 = require("./unzip");
|
|
10
|
+
const quick_lru_1 = __importDefault(require("quick-lru"));
|
|
11
|
+
const range_1 = __importDefault(require("./range"));
|
|
12
|
+
const util_1 = require("./util");
|
|
13
|
+
const BIG_WIG_TYPE_GRAPH = 1;
|
|
14
|
+
const BIG_WIG_TYPE_VSTEP = 2;
|
|
15
|
+
const BIG_WIG_TYPE_FSTEP = 3;
|
|
16
|
+
function getParsers(isBigEndian) {
|
|
17
|
+
const le = isBigEndian ? 'big' : 'little';
|
|
18
|
+
const summaryParser = new binary_parser_1.Parser()
|
|
19
|
+
.endianess(le)
|
|
20
|
+
.uint32('chromId')
|
|
21
|
+
.uint32('start')
|
|
22
|
+
.uint32('end')
|
|
23
|
+
.uint32('validCnt')
|
|
24
|
+
.float('minScore')
|
|
25
|
+
.float('maxScore')
|
|
26
|
+
.float('sumData')
|
|
27
|
+
.float('sumSqData');
|
|
28
|
+
const leafParser = new binary_parser_1.Parser()
|
|
29
|
+
.endianess(le)
|
|
30
|
+
.uint8('isLeaf')
|
|
31
|
+
.skip(1)
|
|
32
|
+
.uint16('cnt')
|
|
33
|
+
.choice({
|
|
34
|
+
tag: 'isLeaf',
|
|
35
|
+
choices: {
|
|
36
|
+
1: new binary_parser_1.Parser().array('blocksToFetch', {
|
|
37
|
+
length: 'cnt',
|
|
38
|
+
type: new binary_parser_1.Parser()
|
|
39
|
+
.uint32('startChrom')
|
|
40
|
+
.uint32('startBase')
|
|
41
|
+
.uint32('endChrom')
|
|
42
|
+
.uint32('endBase')
|
|
43
|
+
.uint64('blockOffset')
|
|
44
|
+
.uint64('blockSize'),
|
|
45
|
+
}),
|
|
46
|
+
0: new binary_parser_1.Parser().array('recurOffsets', {
|
|
47
|
+
length: 'cnt',
|
|
48
|
+
type: new binary_parser_1.Parser()
|
|
49
|
+
.uint32('startChrom')
|
|
50
|
+
.uint32('startBase')
|
|
51
|
+
.uint32('endChrom')
|
|
52
|
+
.uint32('endBase')
|
|
53
|
+
.uint64('blockOffset'),
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const bigBedParser = new binary_parser_1.Parser()
|
|
58
|
+
.endianess(le)
|
|
59
|
+
.uint32('chromId')
|
|
60
|
+
.int32('start')
|
|
61
|
+
.int32('end')
|
|
62
|
+
.string('rest', {
|
|
63
|
+
zeroTerminated: true,
|
|
64
|
+
});
|
|
65
|
+
const bigWigParser = new binary_parser_1.Parser()
|
|
66
|
+
.endianess(le)
|
|
67
|
+
.skip(4)
|
|
68
|
+
.int32('blockStart')
|
|
69
|
+
.skip(4)
|
|
70
|
+
.uint32('itemStep')
|
|
71
|
+
.uint32('itemSpan')
|
|
72
|
+
.uint8('blockType')
|
|
73
|
+
.skip(1)
|
|
74
|
+
.uint16('itemCount')
|
|
75
|
+
.choice({
|
|
76
|
+
tag: 'blockType',
|
|
77
|
+
choices: {
|
|
78
|
+
[BIG_WIG_TYPE_FSTEP]: new binary_parser_1.Parser().array('items', {
|
|
79
|
+
length: 'itemCount',
|
|
80
|
+
type: new binary_parser_1.Parser().float('score'),
|
|
81
|
+
}),
|
|
82
|
+
[BIG_WIG_TYPE_VSTEP]: new binary_parser_1.Parser().array('items', {
|
|
83
|
+
length: 'itemCount',
|
|
84
|
+
type: new binary_parser_1.Parser().int32('start').float('score'),
|
|
85
|
+
}),
|
|
86
|
+
[BIG_WIG_TYPE_GRAPH]: new binary_parser_1.Parser().array('items', {
|
|
87
|
+
length: 'itemCount',
|
|
88
|
+
type: new binary_parser_1.Parser().int32('start').int32('end').float('score'),
|
|
89
|
+
}),
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
bigWigParser,
|
|
94
|
+
bigBedParser,
|
|
95
|
+
summaryParser,
|
|
96
|
+
leafParser,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* View into a subset of the data in a BigWig file.
|
|
101
|
+
*
|
|
102
|
+
* Adapted by Robert Buels and Colin Diesh from bigwig.js in the Dalliance Genome
|
|
103
|
+
* Explorer by Thomas Down.
|
|
104
|
+
* @constructs
|
|
105
|
+
*/
|
|
106
|
+
class BlockView {
|
|
107
|
+
constructor(bbi, refsByName, cirTreeOffset, cirTreeLength, isBigEndian, isCompressed, blockType) {
|
|
108
|
+
this.featureCache = new abortable_promise_cache_1.default({
|
|
109
|
+
cache: new quick_lru_1.default({ maxSize: 1000 }),
|
|
110
|
+
fill: async (requestData, signal) => {
|
|
111
|
+
const { length, offset } = requestData;
|
|
112
|
+
const { buffer } = await this.bbi.read(Buffer.alloc(length), 0, length, offset, { signal });
|
|
113
|
+
return buffer;
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
if (!(cirTreeOffset >= 0)) {
|
|
117
|
+
throw new Error('invalid cirTreeOffset!');
|
|
118
|
+
}
|
|
119
|
+
if (!(cirTreeLength > 0)) {
|
|
120
|
+
throw new Error('invalid cirTreeLength!');
|
|
121
|
+
}
|
|
122
|
+
this.cirTreeOffset = cirTreeOffset;
|
|
123
|
+
this.cirTreeLength = cirTreeLength;
|
|
124
|
+
this.isCompressed = isCompressed;
|
|
125
|
+
this.refsByName = refsByName;
|
|
126
|
+
this.isBigEndian = isBigEndian;
|
|
127
|
+
this.bbi = bbi;
|
|
128
|
+
this.blockType = blockType;
|
|
129
|
+
Object.assign(this, getParsers(isBigEndian));
|
|
130
|
+
}
|
|
131
|
+
async readWigData(chrName, start, end, observer, opts) {
|
|
132
|
+
try {
|
|
133
|
+
const { refsByName, bbi, cirTreeOffset, isBigEndian } = this;
|
|
134
|
+
const { signal } = opts;
|
|
135
|
+
const chrId = refsByName[chrName];
|
|
136
|
+
if (chrId === undefined) {
|
|
137
|
+
observer.complete();
|
|
138
|
+
}
|
|
139
|
+
const request = { chrId, start, end };
|
|
140
|
+
if (!this.cirTreePromise) {
|
|
141
|
+
this.cirTreePromise = bbi.read(Buffer.alloc(48), 0, 48, cirTreeOffset, {
|
|
142
|
+
signal,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const { buffer } = await this.cirTreePromise;
|
|
146
|
+
const cirBlockSize = isBigEndian
|
|
147
|
+
? buffer.readUInt32BE(4)
|
|
148
|
+
: buffer.readUInt32LE(4);
|
|
149
|
+
let blocksToFetch = [];
|
|
150
|
+
let outstanding = 0;
|
|
151
|
+
const cirFobRecur2 = (cirBlockData, offset, level) => {
|
|
152
|
+
try {
|
|
153
|
+
const data = cirBlockData.subarray(offset);
|
|
154
|
+
const p = this.leafParser.parse(data).result;
|
|
155
|
+
if (p.blocksToFetch) {
|
|
156
|
+
blocksToFetch = blocksToFetch.concat(p.blocksToFetch.filter(filterFeats).map((l) => ({
|
|
157
|
+
offset: l.blockOffset,
|
|
158
|
+
length: l.blockSize,
|
|
159
|
+
})));
|
|
160
|
+
}
|
|
161
|
+
if (p.recurOffsets) {
|
|
162
|
+
const recurOffsets = p.recurOffsets
|
|
163
|
+
.filter(filterFeats)
|
|
164
|
+
.map((l) => l.blockOffset);
|
|
165
|
+
if (recurOffsets.length > 0) {
|
|
166
|
+
cirFobRecur(recurOffsets, level + 1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
observer.error(e);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const filterFeats = (b) => {
|
|
175
|
+
const { startChrom, startBase, endChrom, endBase } = b;
|
|
176
|
+
return ((startChrom < chrId || (startChrom === chrId && startBase <= end)) &&
|
|
177
|
+
(endChrom > chrId || (endChrom === chrId && endBase >= start)));
|
|
178
|
+
};
|
|
179
|
+
const cirFobStartFetch = async (off, fr, level) => {
|
|
180
|
+
try {
|
|
181
|
+
const length = fr.max() - fr.min();
|
|
182
|
+
const offset = fr.min();
|
|
183
|
+
const resultBuffer = await this.featureCache.get(`${length}_${offset}`, { length, offset }, signal);
|
|
184
|
+
for (let i = 0; i < off.length; i += 1) {
|
|
185
|
+
if (fr.contains(off[i])) {
|
|
186
|
+
cirFobRecur2(resultBuffer, off[i] - offset, level);
|
|
187
|
+
outstanding -= 1;
|
|
188
|
+
if (outstanding === 0) {
|
|
189
|
+
this.readFeatures(observer, blocksToFetch, { ...opts, request });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
observer.error(e);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const cirFobRecur = (offset, level) => {
|
|
199
|
+
try {
|
|
200
|
+
outstanding += offset.length;
|
|
201
|
+
const maxCirBlockSpan = 4 + cirBlockSize * 32; // Upper bound on size, based on a completely full leaf node.
|
|
202
|
+
let spans = new range_1.default(offset[0], offset[0] + maxCirBlockSpan);
|
|
203
|
+
for (let i = 1; i < offset.length; i += 1) {
|
|
204
|
+
const blockSpan = new range_1.default(offset[i], offset[i] + maxCirBlockSpan);
|
|
205
|
+
spans = spans.union(blockSpan);
|
|
206
|
+
}
|
|
207
|
+
spans.getRanges().map(fr => cirFobStartFetch(offset, fr, level));
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
observer.error(e);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
return cirFobRecur([cirTreeOffset + 48], 1);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
observer.error(e);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
parseSummaryBlock(data, startOffset, request) {
|
|
220
|
+
const features = [];
|
|
221
|
+
let currOffset = startOffset;
|
|
222
|
+
while (currOffset < data.byteLength) {
|
|
223
|
+
const res = this.summaryParser.parse(data.subarray(currOffset));
|
|
224
|
+
features.push(res.result);
|
|
225
|
+
currOffset += res.offset;
|
|
226
|
+
}
|
|
227
|
+
let items = features;
|
|
228
|
+
if (request) {
|
|
229
|
+
items = items.filter(elt => elt.chromId === request.chrId);
|
|
230
|
+
}
|
|
231
|
+
const feats = items.map((elt) => ({
|
|
232
|
+
start: elt.start,
|
|
233
|
+
end: elt.end,
|
|
234
|
+
maxScore: elt.maxScore,
|
|
235
|
+
minScore: elt.minScore,
|
|
236
|
+
score: elt.sumData / (elt.validCnt || 1),
|
|
237
|
+
summary: true,
|
|
238
|
+
}));
|
|
239
|
+
return request
|
|
240
|
+
? feats.filter(f => BlockView.coordFilter(f, request))
|
|
241
|
+
: feats;
|
|
242
|
+
}
|
|
243
|
+
parseBigBedBlock(data, startOffset, offset, request) {
|
|
244
|
+
const items = [];
|
|
245
|
+
let currOffset = startOffset;
|
|
246
|
+
while (currOffset < data.byteLength) {
|
|
247
|
+
const res = this.bigBedParser.parse(data.subarray(currOffset));
|
|
248
|
+
res.result.uniqueId = `bb-${offset + currOffset}`;
|
|
249
|
+
items.push(res.result);
|
|
250
|
+
currOffset += res.offset;
|
|
251
|
+
}
|
|
252
|
+
return request
|
|
253
|
+
? items.filter((f) => BlockView.coordFilter(f, request))
|
|
254
|
+
: items;
|
|
255
|
+
}
|
|
256
|
+
parseBigWigBlock(bytes, startOffset, request) {
|
|
257
|
+
const data = bytes.subarray(startOffset);
|
|
258
|
+
const results = this.bigWigParser.parse(data).result;
|
|
259
|
+
const { items, itemSpan, itemStep, blockStart, blockType } = results;
|
|
260
|
+
if (blockType === BIG_WIG_TYPE_FSTEP) {
|
|
261
|
+
for (let i = 0; i < items.length; i++) {
|
|
262
|
+
items[i].start = blockStart + i * itemStep;
|
|
263
|
+
items[i].end = blockStart + i * itemStep + itemSpan;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else if (blockType === BIG_WIG_TYPE_VSTEP) {
|
|
267
|
+
for (let i = 0; i < items.length; i++) {
|
|
268
|
+
items[i].end = items[i].start + itemSpan;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return request
|
|
272
|
+
? items.filter((f) => BlockView.coordFilter(f, request))
|
|
273
|
+
: items;
|
|
274
|
+
}
|
|
275
|
+
static coordFilter(f, range) {
|
|
276
|
+
return f.start < range.end && f.end >= range.start;
|
|
277
|
+
}
|
|
278
|
+
async readFeatures(observer, blocks, opts = {}) {
|
|
279
|
+
try {
|
|
280
|
+
const { blockType, isCompressed } = this;
|
|
281
|
+
const { signal, request } = opts;
|
|
282
|
+
const blockGroupsToFetch = (0, util_1.groupBlocks)(blocks);
|
|
283
|
+
(0, util_1.checkAbortSignal)(signal);
|
|
284
|
+
await Promise.all(blockGroupsToFetch.map(async (blockGroup) => {
|
|
285
|
+
(0, util_1.checkAbortSignal)(signal);
|
|
286
|
+
const { length, offset } = blockGroup;
|
|
287
|
+
const data = await this.featureCache.get(`${length}_${offset}`, blockGroup, signal);
|
|
288
|
+
blockGroup.blocks.forEach((block) => {
|
|
289
|
+
(0, util_1.checkAbortSignal)(signal);
|
|
290
|
+
let blockOffset = block.offset - blockGroup.offset;
|
|
291
|
+
let resultData = data;
|
|
292
|
+
if (isCompressed) {
|
|
293
|
+
resultData = (0, unzip_1.unzip)(data.subarray(blockOffset));
|
|
294
|
+
blockOffset = 0;
|
|
295
|
+
}
|
|
296
|
+
(0, util_1.checkAbortSignal)(signal);
|
|
297
|
+
switch (blockType) {
|
|
298
|
+
case 'summary':
|
|
299
|
+
observer.next(this.parseSummaryBlock(resultData, blockOffset, request));
|
|
300
|
+
break;
|
|
301
|
+
case 'bigwig':
|
|
302
|
+
observer.next(this.parseBigWigBlock(resultData, blockOffset, request));
|
|
303
|
+
break;
|
|
304
|
+
case 'bigbed':
|
|
305
|
+
observer.next(this.parseBigBedBlock(resultData, blockOffset,
|
|
306
|
+
// eslint-disable-next-line no-bitwise
|
|
307
|
+
block.offset * (1 << 8), request));
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
console.warn(`Don't know what to do with ${blockType}`);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}));
|
|
314
|
+
observer.complete();
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
317
|
+
observer.error(e);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
exports.BlockView = BlockView;
|
package/esm/index.d.ts
ADDED
package/esm/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BigBed = exports.BigWig = void 0;
|
|
4
|
+
var bigwig_1 = require("./bigwig");
|
|
5
|
+
Object.defineProperty(exports, "BigWig", { enumerable: true, get: function () { return bigwig_1.BigWig; } });
|
|
6
|
+
var bigbed_1 = require("./bigbed");
|
|
7
|
+
Object.defineProperty(exports, "BigBed", { enumerable: true, get: function () { return bigbed_1.BigBed; } });
|
package/esm/range.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from a combination of Range and _Compound in the
|
|
3
|
+
* Dalliance Genome Explorer, (c) Thomas Down 2006-2010.
|
|
4
|
+
*/
|
|
5
|
+
export default class Range {
|
|
6
|
+
ranges: any;
|
|
7
|
+
constructor(arg1: any, arg2?: any);
|
|
8
|
+
min(): number;
|
|
9
|
+
max(): number;
|
|
10
|
+
contains(pos: number): boolean;
|
|
11
|
+
isContiguous(): boolean;
|
|
12
|
+
getRanges(): Range[];
|
|
13
|
+
toString(): string;
|
|
14
|
+
union(s1: Range): Range;
|
|
15
|
+
intersection(arg: Range): Range;
|
|
16
|
+
coverage(): number;
|
|
17
|
+
rangeOrder(tmpa: Range, tmpb: Range): number;
|
|
18
|
+
}
|