@jbrowse/plugin-bed 4.2.1 → 5.0.0-beta.1
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/esm/BedAdapter/BedAdapter.d.ts +4 -19
- package/esm/BedAdapter/BedAdapter.js +31 -88
- package/esm/BedAdapter/configSchema.d.ts +89 -40
- package/esm/BedAdapter/configSchema.js +65 -13
- package/esm/BedAdapter/index.js +4 -2
- package/esm/BedGraphAdapter/BedGraphAdapter.d.ts +4 -9
- package/esm/BedGraphAdapter/BedGraphAdapter.js +31 -85
- package/esm/BedGraphAdapter/configSchema.d.ts +35 -9
- package/esm/BedGraphAdapter/configSchema.js +47 -12
- package/esm/BedGraphAdapter/index.js +3 -2
- package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.d.ts +5 -5
- package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.js +62 -57
- package/esm/BedGraphTabixAdapter/configSchema.d.ts +48 -19
- package/esm/BedGraphTabixAdapter/configSchema.js +42 -34
- package/esm/BedGraphTabixAdapter/index.js +3 -2
- package/esm/BedTabixAdapter/BedTabixAdapter.d.ts +34 -38
- package/esm/BedTabixAdapter/BedTabixAdapter.js +82 -71
- package/esm/BedTabixAdapter/configSchema.d.ts +68 -31
- package/esm/BedTabixAdapter/configSchema.js +51 -35
- package/esm/BedTabixAdapter/index.js +4 -2
- package/esm/BedpeAdapter/BedpeAdapter.d.ts +4 -10
- package/esm/BedpeAdapter/BedpeAdapter.js +28 -69
- package/esm/BedpeAdapter/configSchema.d.ts +33 -10
- package/esm/BedpeAdapter/configSchema.js +45 -12
- package/esm/BedpeAdapter/index.js +4 -2
- package/esm/BedpeAdapter/util.js +22 -23
- package/esm/BigBedAdapter/BigBedAdapter.d.ts +8 -7
- package/esm/BigBedAdapter/BigBedAdapter.js +99 -105
- package/esm/BigBedAdapter/configSchema.d.ts +47 -17
- package/esm/BigBedAdapter/configSchema.js +51 -12
- package/esm/BigBedAdapter/index.js +3 -2
- package/esm/StarFusionAdapter/StarFusionAdapter.d.ts +15 -0
- package/esm/StarFusionAdapter/StarFusionAdapter.js +121 -0
- package/esm/StarFusionAdapter/configSchema.d.ts +31 -0
- package/esm/StarFusionAdapter/configSchema.js +52 -0
- package/esm/StarFusionAdapter/index.d.ts +2 -0
- package/esm/StarFusionAdapter/index.js +12 -0
- package/esm/adapterUtil.d.ts +5 -0
- package/esm/adapterUtil.js +28 -0
- package/esm/bedGraphUtil.d.ts +10 -0
- package/esm/bedGraphUtil.js +18 -0
- package/esm/generateBedMethylFeature.d.ts +1 -1
- package/esm/generateBedMethylFeature.js +19 -6
- package/esm/generateRastairMethylFeature.d.ts +28 -0
- package/esm/generateRastairMethylFeature.js +38 -0
- package/esm/generateRepeatMaskerFeature.d.ts +4 -51
- package/esm/generateRepeatMaskerFeature.js +28 -36
- package/esm/generateUcscTranscript.d.ts +13 -5
- package/esm/generateUcscTranscript.js +58 -26
- package/esm/index.d.ts +2 -0
- package/esm/index.js +10 -8
- package/esm/util.d.ts +32 -27
- package/esm/util.js +116 -84
- package/package.json +9 -13
- package/esm/GuessAdapter/index.d.ts +0 -2
- package/esm/GuessAdapter/index.js +0 -65
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
function
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return snap.uri
|
|
4
|
+
? {
|
|
5
|
+
...snap,
|
|
6
|
+
bedpeLocation: {
|
|
7
|
+
uri: snap.uri,
|
|
8
|
+
baseUri: snap.baseUri,
|
|
9
|
+
locationType: 'UriLocation',
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
: snap;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* #config BedpeAdapter
|
|
16
|
+
* #trackType VariantTrack
|
|
17
|
+
* #fileFormat variants | BEDPE | Paired/breakend records, e.g. SV calls or Hi-C loops
|
|
18
|
+
* intended for SVs in a single assembly
|
|
19
|
+
*
|
|
20
|
+
* #example
|
|
21
|
+
* ```js
|
|
22
|
+
* {
|
|
23
|
+
* type: 'BedpeAdapter',
|
|
24
|
+
* uri: 'https://example.com/sv.bedpe',
|
|
25
|
+
* }
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
3
28
|
const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
|
|
29
|
+
/**
|
|
30
|
+
* #slot
|
|
31
|
+
*/
|
|
4
32
|
bedpeLocation: {
|
|
5
33
|
type: 'fileLocation',
|
|
6
34
|
description: 'can be plaintext or gzipped, not indexed so loaded into memory on startup',
|
|
@@ -9,6 +37,9 @@ const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
|
|
|
9
37
|
locationType: 'UriLocation',
|
|
10
38
|
},
|
|
11
39
|
},
|
|
40
|
+
/**
|
|
41
|
+
* #slot
|
|
42
|
+
*/
|
|
12
43
|
columnNames: {
|
|
13
44
|
type: 'stringArray',
|
|
14
45
|
description: 'List of column names',
|
|
@@ -16,16 +47,18 @@ const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
|
|
|
16
47
|
},
|
|
17
48
|
}, {
|
|
18
49
|
explicitlyTyped: true,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
50
|
+
/**
|
|
51
|
+
* #preProcessSnapshot
|
|
52
|
+
*
|
|
53
|
+
*
|
|
54
|
+
* preprocessor to allow minimal config:
|
|
55
|
+
* ```json
|
|
56
|
+
* {
|
|
57
|
+
* "type": "BedpeAdapter",
|
|
58
|
+
* "uri": "yourfile.bedpe.gz"
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
30
63
|
});
|
|
31
64
|
export default BedpeAdapter;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType';
|
|
2
|
-
import configSchema from
|
|
2
|
+
import configSchema, { normalizeSnapshot } from './configSchema.js';
|
|
3
3
|
export default function BedpeAdapterF(pluginManager) {
|
|
4
4
|
pluginManager.addAdapterType(() => new AdapterType({
|
|
5
5
|
name: 'BedpeAdapter',
|
|
6
6
|
displayName: 'BEDPE adapter',
|
|
7
7
|
configSchema,
|
|
8
|
-
getAdapterClass: () => import(
|
|
8
|
+
getAdapterClass: () => import('./BedpeAdapter.js').then(r => r.default),
|
|
9
|
+
locationKey: 'bedpeLocation',
|
|
10
|
+
normalizeSnapshot,
|
|
9
11
|
}));
|
|
10
12
|
}
|
package/esm/BedpeAdapter/util.js
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
import { SimpleFeature } from '@jbrowse/core/util';
|
|
2
|
+
import { parseStrand } from '../util.js';
|
|
2
3
|
const svTypes = new Set(['DUP', 'TRA', 'INV', 'CNV', 'DEL']);
|
|
3
4
|
export function featureData(line, uniqueId, flip, names) {
|
|
4
5
|
const l = line.split('\t');
|
|
5
6
|
const ref1 = l[flip ? 3 : 0];
|
|
6
7
|
const start1 = +l[flip ? 4 : 1];
|
|
7
8
|
const end1 = +l[flip ? 5 : 2];
|
|
8
|
-
const ref2 = l[
|
|
9
|
-
const start2 = +l[
|
|
10
|
-
const end2 = +l[
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
9
|
+
const ref2 = l[flip ? 0 : 3];
|
|
10
|
+
const start2 = +l[flip ? 1 : 4];
|
|
11
|
+
const end2 = +l[flip ? 2 : 5];
|
|
12
|
+
// '.' is BED's "missing" marker, so a file that leaves the standard name and
|
|
13
|
+
// score columns unset must not produce the literal string '.' and a NaN
|
|
14
|
+
// score. Same rule `defaultParser` applies on the plain-BED path.
|
|
15
|
+
const name = l[6] === '.' ? undefined : l[6];
|
|
16
|
+
const score = l[7] && l[7] !== '.' ? +l[7] : undefined;
|
|
17
|
+
// Columns 9 and 10 ride with the blocks they describe. Left behind on a
|
|
18
|
+
// flipped row they anchored the feature at one end and gave it the other
|
|
19
|
+
// end's orientation, which every consumer reading a junction edge off the
|
|
20
|
+
// strand then got backwards for that half of the record.
|
|
21
|
+
const strand1 = parseStrand(l[flip ? 9 : 8]);
|
|
22
|
+
const strand2 = parseStrand(l[flip ? 8 : 9]);
|
|
15
23
|
const extra = l.slice(10);
|
|
16
24
|
const rest = names
|
|
17
25
|
? Object.fromEntries(names.slice(10).map((n, idx) => [n, extra[idx]]))
|
|
18
26
|
: {};
|
|
19
27
|
const ALT = svTypes.has(extra[0]) ? `<${extra[0]}>` : undefined;
|
|
28
|
+
// `name` and `score` are spread only when the file actually sets them, which
|
|
29
|
+
// is what lets a column past 10 of the same name through. juicer's bedpe is
|
|
30
|
+
// the case that needs it: Arrowhead writes '.' in both standard columns and
|
|
31
|
+
// its corner score in column 12, also called `score`, so an unconditional
|
|
32
|
+
// positional key shadowed the only column the caller ranks domains by.
|
|
20
33
|
return new SimpleFeature({
|
|
21
34
|
...rest,
|
|
22
35
|
start: start1,
|
|
@@ -24,8 +37,8 @@ export function featureData(line, uniqueId, flip, names) {
|
|
|
24
37
|
type: 'paired_feature',
|
|
25
38
|
refName: ref1,
|
|
26
39
|
strand: strand1,
|
|
27
|
-
name,
|
|
28
|
-
score,
|
|
40
|
+
...(name === undefined ? {} : { name }),
|
|
41
|
+
...(score === undefined ? {} : { score }),
|
|
29
42
|
uniqueId,
|
|
30
43
|
mate: {
|
|
31
44
|
refName: ref2,
|
|
@@ -36,17 +49,3 @@ export function featureData(line, uniqueId, flip, names) {
|
|
|
36
49
|
...(ALT ? { ALT: [ALT] } : {}),
|
|
37
50
|
});
|
|
38
51
|
}
|
|
39
|
-
function parseStrand(strand) {
|
|
40
|
-
if (strand === '+') {
|
|
41
|
-
return 1;
|
|
42
|
-
}
|
|
43
|
-
else if (strand === '-') {
|
|
44
|
-
return -1;
|
|
45
|
-
}
|
|
46
|
-
else if (strand === '.') {
|
|
47
|
-
return 0;
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
import { BigBed } from '@gmod/bbi';
|
|
2
2
|
import BED from '@gmod/bed';
|
|
3
3
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
|
+
import type { BigBedAdapterConfig } from './configSchema.ts';
|
|
4
5
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
5
6
|
import type { Feature } from '@jbrowse/core/util';
|
|
6
7
|
import type { Region } from '@jbrowse/core/util/types';
|
|
7
8
|
import type { Observer } from 'rxjs';
|
|
8
|
-
export default class BigBedAdapter extends BaseFeatureDataAdapter {
|
|
9
|
-
|
|
10
|
-
configurePre(opts?: BaseOptions): Promise<{
|
|
9
|
+
export default class BigBedAdapter extends BaseFeatureDataAdapter<BigBedAdapterConfig> {
|
|
10
|
+
configure: (opts?: BaseOptions) => Promise<{
|
|
11
11
|
bigbed: BigBed;
|
|
12
12
|
header: import("@gmod/bbi").BigWigHeaderWithRefNames;
|
|
13
13
|
parser: BED;
|
|
14
14
|
}>;
|
|
15
|
-
|
|
15
|
+
configurePre(opts?: BaseOptions): Promise<{
|
|
16
16
|
bigbed: BigBed;
|
|
17
|
-
header:
|
|
17
|
+
header: import("@gmod/bbi").BigWigHeaderWithRefNames;
|
|
18
18
|
parser: BED;
|
|
19
19
|
}>;
|
|
20
20
|
getRefNames(opts?: BaseOptions): Promise<string[]>;
|
|
21
|
+
getRegionByteSize(regions: Region[], opts?: BaseOptions): Promise<number>;
|
|
21
22
|
getRefNameAliases(opts?: BaseOptions): Promise<{
|
|
22
23
|
refName: unknown;
|
|
23
|
-
aliases:
|
|
24
|
+
aliases: string[];
|
|
24
25
|
override: boolean;
|
|
25
26
|
}[]>;
|
|
26
27
|
getData(): Promise<Feature[]>;
|
|
27
28
|
getHeader(opts?: BaseOptions): Promise<{
|
|
28
29
|
version: number;
|
|
29
|
-
fileType:
|
|
30
|
+
fileType: "bigbed" | "bigwig";
|
|
30
31
|
autoSql: {};
|
|
31
32
|
fields: {
|
|
32
33
|
[k: string]: string;
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { BigBed } from '@gmod/bbi';
|
|
2
2
|
import BED from '@gmod/bed';
|
|
3
|
-
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
|
-
import { SimpleFeature, doesIntersect2, max, min, updateStatus, } from '@jbrowse/core/util';
|
|
3
|
+
import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
|
+
import { SimpleFeature, doesIntersect2, downloadStatus, max, min, updateStatus, } from '@jbrowse/core/util';
|
|
5
5
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
6
6
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
7
|
+
import { withStopTokenSignal } from '@jbrowse/core/util/stopToken';
|
|
7
8
|
import { firstValueFrom, toArray } from 'rxjs';
|
|
8
|
-
import {
|
|
9
|
+
import { featureData } from '../util.js';
|
|
9
10
|
export default class BigBedAdapter extends BaseFeatureDataAdapter {
|
|
10
|
-
|
|
11
|
+
// `label` on cachedSetup so "Downloading header" shows only while the first
|
|
12
|
+
// fetch is in flight; every later getFeatures resolves from the memo with no
|
|
13
|
+
// re-flash.
|
|
14
|
+
configure = cachedSetup({
|
|
15
|
+
label: 'Downloading header',
|
|
16
|
+
setup: opts => this.configurePre(opts),
|
|
17
|
+
});
|
|
11
18
|
async configurePre(opts) {
|
|
12
19
|
const pm = this.pluginManager;
|
|
13
20
|
const bigbed = new BigBed({
|
|
@@ -23,19 +30,19 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
|
|
|
23
30
|
parser,
|
|
24
31
|
};
|
|
25
32
|
}
|
|
26
|
-
async configure(opts) {
|
|
27
|
-
if (!this.cachedP) {
|
|
28
|
-
this.cachedP = this.configurePre(opts).catch((e) => {
|
|
29
|
-
this.cachedP = undefined;
|
|
30
|
-
throw e;
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
return this.cachedP;
|
|
34
|
-
}
|
|
35
33
|
async getRefNames(opts) {
|
|
36
34
|
const { header } = await this.configure(opts);
|
|
37
35
|
return Object.keys(header.refsByName);
|
|
38
36
|
}
|
|
37
|
+
// Compressed download-size estimate straight from the bbi R-tree index (block
|
|
38
|
+
// byte lengths, no data blocks read), so a display can byte-gate an over-large
|
|
39
|
+
// BigBed fetch before it starts. Overrides the base no-estimate default, which
|
|
40
|
+
// left BigBed tracks ungated.
|
|
41
|
+
async getRegionByteSize(regions, opts) {
|
|
42
|
+
const { bigbed } = await this.configure(opts);
|
|
43
|
+
return bigbed.getRegionByteSizeMulti(regions, opts);
|
|
44
|
+
}
|
|
45
|
+
// allow using BigBedAdapter for aliases with chromAlias.bb file from UCSC
|
|
39
46
|
async getRefNameAliases(opts) {
|
|
40
47
|
const { header } = await this.configure(opts);
|
|
41
48
|
const ret = await Promise.all(Object.keys(header.refsByName).map(async (refName) => (await firstValueFrom(this.getFeatures({
|
|
@@ -48,22 +55,20 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
|
|
|
48
55
|
.map(r => r.toJSON())
|
|
49
56
|
.map(r => ({
|
|
50
57
|
refName: r.ucsc,
|
|
51
|
-
|
|
58
|
+
// chromAlias.bb columns are sparse: a chrom may lack an ncbi/refseq/
|
|
59
|
+
// genbank name, returned as empty strings that are not valid refNames
|
|
60
|
+
aliases: [r.ncbi, r.refseq, r.genbank].filter((alias) => !!alias),
|
|
52
61
|
override: true,
|
|
53
62
|
}));
|
|
54
63
|
}
|
|
55
64
|
async getData() {
|
|
56
65
|
const refNames = await this.getRefNames();
|
|
57
|
-
const features =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end: Number.MAX_SAFE_INTEGER,
|
|
64
|
-
}).pipe(toArray()));
|
|
65
|
-
features.push(f);
|
|
66
|
-
}
|
|
66
|
+
const features = await Promise.all(refNames.map(refName => firstValueFrom(this.getFeatures({
|
|
67
|
+
assemblyName: 'unknown',
|
|
68
|
+
refName,
|
|
69
|
+
start: 0,
|
|
70
|
+
end: Number.MAX_SAFE_INTEGER,
|
|
71
|
+
}).pipe(toArray()))));
|
|
67
72
|
return features.flat();
|
|
68
73
|
}
|
|
69
74
|
async getHeader(opts) {
|
|
@@ -83,77 +88,69 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
|
|
|
83
88
|
return Object.fromEntries(fields.map(({ name, comment }) => [name, comment]));
|
|
84
89
|
}
|
|
85
90
|
async getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery = query, }) {
|
|
86
|
-
const { statusCallback
|
|
91
|
+
const { statusCallback } = opts;
|
|
87
92
|
const scoreColumn = this.getConf('scoreColumn');
|
|
88
93
|
const aggregateField = this.getConf('aggregateField');
|
|
89
94
|
const disableGeneHeuristic = this.getConf('disableGeneHeuristic');
|
|
90
|
-
const { parser, bigbed } = await
|
|
91
|
-
|
|
95
|
+
const { parser, bigbed } = await this.configure(opts);
|
|
96
|
+
// downloadStatus, not updateStatus: bbi knows the total block bytes up front
|
|
97
|
+
// (they come from the R-tree index) so this reports a determinate fraction
|
|
98
|
+
// rather than an indeterminate spinner. Matters most here — BigBed is a
|
|
99
|
+
// full-feature download, which is why getRegionByteSize gates it above.
|
|
100
|
+
const feats = await withStopTokenSignal(opts.stopToken, signal => downloadStatus('Downloading features', statusCallback, onProgress => bigbed.getFeatures(query.refName, query.start, query.end, {
|
|
92
101
|
basesPerSpan: query.end - query.start,
|
|
93
|
-
|
|
102
|
+
onProgress,
|
|
103
|
+
signal,
|
|
104
|
+
})));
|
|
94
105
|
await updateStatus('Processing features', statusCallback, async () => {
|
|
95
106
|
const parentAggregation = {};
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
107
|
+
const singletons = [];
|
|
108
|
+
let minAggStart = Infinity;
|
|
109
|
+
let maxAggEnd = -Infinity;
|
|
100
110
|
for (const feat of feats) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
];
|
|
107
|
-
const data = parser.parseLine(splitLine, {
|
|
108
|
-
uniqueId: feat.uniqueId,
|
|
109
|
-
});
|
|
110
|
-
const aggr = data[aggregateField];
|
|
111
|
-
const aggrIsNotNone = aggr && aggr !== 'none';
|
|
112
|
-
if (aggrIsNotNone && !parentAggregation[aggr]) {
|
|
113
|
-
parentAggregation[aggr] = [];
|
|
114
|
-
}
|
|
115
|
-
const { uniqueId, type, chrom, chromStart, chromEnd, description, chromStarts: chromStarts2, blockStarts: blockStarts2, blockSizes: blockSizes2, score: score2, blockCount, thickStart, thickEnd, strand, ...rest } = data;
|
|
116
|
-
const f = featureData2({
|
|
117
|
-
...rest,
|
|
111
|
+
// empty chrom/start/end placeholders keep `rest` columns aligned to the
|
|
112
|
+
// autoSql schema (which begins at chrom); featureData reads back the
|
|
113
|
+
// numeric start/end/refName passed below, so positions 0-2 are unused
|
|
114
|
+
const splitLine = ['', '', '', ...(feat.rest?.split('\t') ?? [])];
|
|
115
|
+
const f = featureData({
|
|
118
116
|
scoreColumn,
|
|
119
117
|
splitLine,
|
|
120
118
|
parser,
|
|
121
|
-
uniqueId,
|
|
119
|
+
uniqueId: feat.uniqueId,
|
|
122
120
|
start: feat.start,
|
|
123
121
|
end: feat.end,
|
|
124
122
|
refName: query.refName,
|
|
125
123
|
disableGeneHeuristic,
|
|
126
124
|
});
|
|
125
|
+
const aggr = f[aggregateField];
|
|
126
|
+
const aggrIsNotNone = typeof aggr === 'string' && aggr && aggr !== 'none';
|
|
127
127
|
if (aggrIsNotNone) {
|
|
128
|
+
parentAggregation[aggr] ??= [];
|
|
128
129
|
parentAggregation[aggr].push(f);
|
|
129
|
-
|
|
130
|
+
if (f.start < minAggStart) {
|
|
131
|
+
minAggStart = f.start;
|
|
132
|
+
}
|
|
133
|
+
if (f.end > maxAggEnd) {
|
|
134
|
+
maxAggEnd = f.end;
|
|
135
|
+
}
|
|
130
136
|
}
|
|
131
137
|
else {
|
|
132
|
-
|
|
133
|
-
observer.next(new SimpleFeature({
|
|
134
|
-
id: `${this.id}-${uniqueId}`,
|
|
135
|
-
data: f,
|
|
136
|
-
}));
|
|
137
|
-
}
|
|
138
|
+
singletons.push(f);
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
|
-
if (allowRedispatch &&
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (feat.start < minStart) {
|
|
145
|
-
minStart = feat.start;
|
|
146
|
-
}
|
|
147
|
-
if (feat.end > maxEnd) {
|
|
148
|
-
maxEnd = feat.end;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (maxEnd > query.end || minStart < query.start) {
|
|
141
|
+
if (allowRedispatch && maxAggEnd > -Infinity) {
|
|
142
|
+
if (maxAggEnd > query.end || minAggStart < query.start) {
|
|
143
|
+
// redispatch re-fetches a superset region, so emitting singletons
|
|
144
|
+
// here too would double-emit them; defer emission to the recursion
|
|
152
145
|
await this.getFeaturesHelper({
|
|
153
146
|
query: {
|
|
154
147
|
...query,
|
|
155
|
-
|
|
156
|
-
|
|
148
|
+
// extend query to catch gene subfeatures outside the current view;
|
|
149
|
+
// 500 kbp heuristic covers most genes/transcripts. Clamped at 0 so
|
|
150
|
+
// a gene near the start of a contig doesn't produce a negative
|
|
151
|
+
// region start.
|
|
152
|
+
start: Math.max(0, minAggStart - 500_000),
|
|
153
|
+
end: maxAggEnd + 500_000,
|
|
157
154
|
},
|
|
158
155
|
opts,
|
|
159
156
|
observer,
|
|
@@ -163,59 +160,56 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
|
|
|
163
160
|
return;
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
for (const f of singletons) {
|
|
164
|
+
if (doesIntersect2(f.start, f.end, originalQuery.start, originalQuery.end)) {
|
|
165
|
+
observer.next(new SimpleFeature({
|
|
166
|
+
id: `${this.id}-${f.uniqueId}`,
|
|
167
|
+
data: f,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const [name, subfeatures] of Object.entries(parentAggregation)) {
|
|
172
|
+
const subs = subfeatures.sort((a, b) => a.uniqueId.localeCompare(b.uniqueId));
|
|
173
|
+
const sortedByStart = [...subs].sort((a, b) => a.start - b.start);
|
|
174
|
+
const hasOverlaps = sortedByStart.some((f, i) => i > 0 && sortedByStart[i - 1].end > f.start);
|
|
175
|
+
// overlapping subs → one gene parent; non-overlapping → one parent per sub
|
|
176
|
+
// (handles bacterial GFF where two genes share a name but are distinct loci)
|
|
177
|
+
const groups = hasOverlaps ? [subs] : subs.map(sub => [sub]);
|
|
178
|
+
for (const group of groups) {
|
|
179
|
+
// gate on each parent's own extent: a distant non-overlapping locus
|
|
180
|
+
// sharing this name must not ride in on a sibling that intersects
|
|
181
|
+
const groupStart = min(group.map(f => f.start));
|
|
182
|
+
const groupEnd = max(group.map(f => f.end));
|
|
183
|
+
if (doesIntersect2(groupStart, groupEnd, originalQuery.start, originalQuery.end)) {
|
|
172
184
|
observer.next(new SimpleFeature({
|
|
173
|
-
id: `${this.id}-${
|
|
185
|
+
id: `${this.id}-${group[0].uniqueId}-parent`,
|
|
174
186
|
data: {
|
|
175
187
|
type: 'gene',
|
|
176
|
-
subfeatures:
|
|
177
|
-
strand:
|
|
188
|
+
subfeatures: group,
|
|
189
|
+
strand: group.find(f => f.strand !== 0)?.strand ?? 1,
|
|
178
190
|
name,
|
|
179
|
-
start:
|
|
180
|
-
end:
|
|
191
|
+
start: groupStart,
|
|
192
|
+
end: groupEnd,
|
|
181
193
|
refName: query.refName,
|
|
182
194
|
},
|
|
183
195
|
}));
|
|
184
196
|
}
|
|
185
|
-
else {
|
|
186
|
-
for (const sub of subs) {
|
|
187
|
-
observer.next(new SimpleFeature({
|
|
188
|
-
id: `${this.id}-${sub.uniqueId}-parent`,
|
|
189
|
-
data: {
|
|
190
|
-
type: 'gene',
|
|
191
|
-
subfeatures: [sub],
|
|
192
|
-
strand: subs[0]?.strand || 1,
|
|
193
|
-
name,
|
|
194
|
-
start: sub.start,
|
|
195
|
-
end: sub.end,
|
|
196
|
-
refName: query.refName,
|
|
197
|
-
},
|
|
198
|
-
}));
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
197
|
}
|
|
202
|
-
}
|
|
198
|
+
}
|
|
203
199
|
});
|
|
204
|
-
observer.complete();
|
|
205
200
|
}
|
|
206
201
|
getFeatures(query, opts = {}) {
|
|
207
202
|
return ObservableCreate(async (observer) => {
|
|
208
203
|
try {
|
|
209
204
|
await this.getFeaturesHelper({
|
|
210
|
-
query
|
|
211
|
-
...query,
|
|
212
|
-
start: query.start,
|
|
213
|
-
end: query.end,
|
|
214
|
-
},
|
|
205
|
+
query,
|
|
215
206
|
opts,
|
|
216
207
|
observer,
|
|
217
208
|
allowRedispatch: true,
|
|
218
209
|
});
|
|
210
|
+
// complete once here, not in the helper: a redispatch recurses into the
|
|
211
|
+
// helper, so completing there would fire complete() twice
|
|
212
|
+
observer.complete();
|
|
219
213
|
}
|
|
220
214
|
catch (e) {
|
|
221
215
|
observer.error(e);
|
|
@@ -1,25 +1,55 @@
|
|
|
1
|
+
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* #config BigBedAdapter
|
|
5
|
+
* #trackType FeatureTrack
|
|
6
|
+
* #fileFormat feature | BigBed
|
|
7
|
+
*
|
|
8
|
+
* #example
|
|
9
|
+
* ```js
|
|
10
|
+
* {
|
|
11
|
+
* type: 'BigBedAdapter',
|
|
12
|
+
* uri: 'https://example.com/features.bb',
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
1
16
|
declare const BigBedAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
17
|
+
/**
|
|
18
|
+
* #slot
|
|
19
|
+
* location of the BigBed file. It carries its own index and summary zoom
|
|
20
|
+
* levels, so there is no separate index to configure.
|
|
21
|
+
*/
|
|
22
|
+
readonly bigBedLocation: {
|
|
23
|
+
readonly type: "fileLocation";
|
|
24
|
+
readonly defaultValue: {
|
|
25
|
+
readonly uri: "/path/to/my.bb";
|
|
26
|
+
readonly locationType: "UriLocation";
|
|
7
27
|
};
|
|
8
28
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
29
|
+
/**
|
|
30
|
+
* #slot
|
|
31
|
+
*/
|
|
32
|
+
readonly scoreColumn: {
|
|
33
|
+
readonly type: "string";
|
|
34
|
+
readonly description: "The column to use as a \"score\" attribute";
|
|
35
|
+
readonly defaultValue: "";
|
|
13
36
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
/**
|
|
38
|
+
* #slot
|
|
39
|
+
*/
|
|
40
|
+
readonly aggregateField: {
|
|
41
|
+
readonly type: "string";
|
|
42
|
+
readonly description: "An attribute to aggregate features with";
|
|
43
|
+
readonly defaultValue: "geneName2";
|
|
18
44
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
45
|
+
/**
|
|
46
|
+
* #slot
|
|
47
|
+
*/
|
|
48
|
+
readonly disableGeneHeuristic: {
|
|
49
|
+
readonly type: "boolean";
|
|
50
|
+
readonly description: "Disable the heuristic that auto-detects BED12 features as gene/transcript structures. Useful for files that have BED12-like structure but are not genes (e.g. tandem duplications)";
|
|
51
|
+
readonly defaultValue: false;
|
|
23
52
|
};
|
|
24
53
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
54
|
+
export type BigBedAdapterConfig = Instance<typeof BigBedAdapter>;
|
|
25
55
|
export default BigBedAdapter;
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
function
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return snap.uri
|
|
4
|
+
? {
|
|
5
|
+
...snap,
|
|
6
|
+
bigBedLocation: {
|
|
7
|
+
uri: snap.uri,
|
|
8
|
+
baseUri: snap.baseUri,
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
: snap;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* #config BigBedAdapter
|
|
15
|
+
* #trackType FeatureTrack
|
|
16
|
+
* #fileFormat feature | BigBed
|
|
17
|
+
*
|
|
18
|
+
* #example
|
|
19
|
+
* ```js
|
|
20
|
+
* {
|
|
21
|
+
* type: 'BigBedAdapter',
|
|
22
|
+
* uri: 'https://example.com/features.bb',
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
3
26
|
const BigBedAdapter = ConfigurationSchema('BigBedAdapter', {
|
|
27
|
+
/**
|
|
28
|
+
* #slot
|
|
29
|
+
* location of the BigBed file. It carries its own index and summary zoom
|
|
30
|
+
* levels, so there is no separate index to configure.
|
|
31
|
+
*/
|
|
4
32
|
bigBedLocation: {
|
|
5
33
|
type: 'fileLocation',
|
|
6
34
|
defaultValue: {
|
|
@@ -8,16 +36,25 @@ const BigBedAdapter = ConfigurationSchema('BigBedAdapter', {
|
|
|
8
36
|
locationType: 'UriLocation',
|
|
9
37
|
},
|
|
10
38
|
},
|
|
39
|
+
/**
|
|
40
|
+
* #slot
|
|
41
|
+
*/
|
|
11
42
|
scoreColumn: {
|
|
12
43
|
type: 'string',
|
|
13
44
|
description: 'The column to use as a "score" attribute',
|
|
14
45
|
defaultValue: '',
|
|
15
46
|
},
|
|
47
|
+
/**
|
|
48
|
+
* #slot
|
|
49
|
+
*/
|
|
16
50
|
aggregateField: {
|
|
17
51
|
type: 'string',
|
|
18
52
|
description: 'An attribute to aggregate features with',
|
|
19
53
|
defaultValue: 'geneName2',
|
|
20
54
|
},
|
|
55
|
+
/**
|
|
56
|
+
* #slot
|
|
57
|
+
*/
|
|
21
58
|
disableGeneHeuristic: {
|
|
22
59
|
type: 'boolean',
|
|
23
60
|
description: 'Disable the heuristic that auto-detects BED12 features as gene/transcript structures. Useful for files that have BED12-like structure but are not genes (e.g. tandem duplications)',
|
|
@@ -25,16 +62,18 @@ const BigBedAdapter = ConfigurationSchema('BigBedAdapter', {
|
|
|
25
62
|
},
|
|
26
63
|
}, {
|
|
27
64
|
explicitlyTyped: true,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
65
|
+
/**
|
|
66
|
+
* #preProcessSnapshot
|
|
67
|
+
*
|
|
68
|
+
*
|
|
69
|
+
* preprocessor to allow minimal config:
|
|
70
|
+
* ```json
|
|
71
|
+
* {
|
|
72
|
+
* "type": "BigBedAdapter",
|
|
73
|
+
* "uri": "yourfile.bigBed"
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
39
78
|
});
|
|
40
79
|
export default BigBedAdapter;
|