@jbrowse/plugin-bed 4.3.0 → 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 +100 -107
- 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 -41
- package/esm/generateUcscTranscript.d.ts +15 -7
- package/esm/generateUcscTranscript.js +58 -28
- package/esm/index.d.ts +2 -0
- package/esm/index.js +10 -8
- package/esm/util.d.ts +32 -27
- package/esm/util.js +127 -97
- package/package.json +9 -13
- package/esm/GuessAdapter/index.d.ts +0 -2
- package/esm/GuessAdapter/index.js +0 -71
|
@@ -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,78 +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
|
-
const aggrKey = typeof aggr === 'string' ? aggr : `${aggr}`;
|
|
113
|
-
if (aggrIsNotNone && !parentAggregation[aggrKey]) {
|
|
114
|
-
parentAggregation[aggrKey] = [];
|
|
115
|
-
}
|
|
116
|
-
const { uniqueId, type, chrom, chromStart, chromEnd, description, chromStarts: chromStarts2, blockStarts: blockStarts2, blockSizes: blockSizes2, score: score2, blockCount, thickStart, thickEnd, strand, ...rest } = data;
|
|
117
|
-
const f = featureData2({
|
|
118
|
-
...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({
|
|
119
116
|
scoreColumn,
|
|
120
117
|
splitLine,
|
|
121
118
|
parser,
|
|
122
|
-
uniqueId: uniqueId,
|
|
119
|
+
uniqueId: feat.uniqueId,
|
|
123
120
|
start: feat.start,
|
|
124
121
|
end: feat.end,
|
|
125
122
|
refName: query.refName,
|
|
126
123
|
disableGeneHeuristic,
|
|
127
124
|
});
|
|
125
|
+
const aggr = f[aggregateField];
|
|
126
|
+
const aggrIsNotNone = typeof aggr === 'string' && aggr && aggr !== 'none';
|
|
128
127
|
if (aggrIsNotNone) {
|
|
129
|
-
parentAggregation[
|
|
130
|
-
|
|
128
|
+
parentAggregation[aggr] ??= [];
|
|
129
|
+
parentAggregation[aggr].push(f);
|
|
130
|
+
if (f.start < minAggStart) {
|
|
131
|
+
minAggStart = f.start;
|
|
132
|
+
}
|
|
133
|
+
if (f.end > maxAggEnd) {
|
|
134
|
+
maxAggEnd = f.end;
|
|
135
|
+
}
|
|
131
136
|
}
|
|
132
137
|
else {
|
|
133
|
-
|
|
134
|
-
observer.next(new SimpleFeature({
|
|
135
|
-
id: `${this.id}-${uniqueId}`,
|
|
136
|
-
data: f,
|
|
137
|
-
}));
|
|
138
|
-
}
|
|
138
|
+
singletons.push(f);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
-
if (allowRedispatch &&
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (feat.start < minStart) {
|
|
146
|
-
minStart = feat.start;
|
|
147
|
-
}
|
|
148
|
-
if (feat.end > maxEnd) {
|
|
149
|
-
maxEnd = feat.end;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
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
|
|
153
145
|
await this.getFeaturesHelper({
|
|
154
146
|
query: {
|
|
155
147
|
...query,
|
|
156
|
-
|
|
157
|
-
|
|
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,
|
|
158
154
|
},
|
|
159
155
|
opts,
|
|
160
156
|
observer,
|
|
@@ -164,59 +160,56 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
|
|
|
164
160
|
return;
|
|
165
161
|
}
|
|
166
162
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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)) {
|
|
173
184
|
observer.next(new SimpleFeature({
|
|
174
|
-
id: `${this.id}-${
|
|
185
|
+
id: `${this.id}-${group[0].uniqueId}-parent`,
|
|
175
186
|
data: {
|
|
176
187
|
type: 'gene',
|
|
177
|
-
subfeatures:
|
|
178
|
-
strand:
|
|
188
|
+
subfeatures: group,
|
|
189
|
+
strand: group.find(f => f.strand !== 0)?.strand ?? 1,
|
|
179
190
|
name,
|
|
180
|
-
start:
|
|
181
|
-
end:
|
|
191
|
+
start: groupStart,
|
|
192
|
+
end: groupEnd,
|
|
182
193
|
refName: query.refName,
|
|
183
194
|
},
|
|
184
195
|
}));
|
|
185
196
|
}
|
|
186
|
-
else {
|
|
187
|
-
for (const sub of subs) {
|
|
188
|
-
observer.next(new SimpleFeature({
|
|
189
|
-
id: `${this.id}-${sub.uniqueId}-parent`,
|
|
190
|
-
data: {
|
|
191
|
-
type: 'gene',
|
|
192
|
-
subfeatures: [sub],
|
|
193
|
-
strand: subs[0]?.strand || 1,
|
|
194
|
-
name,
|
|
195
|
-
start: sub.start,
|
|
196
|
-
end: sub.end,
|
|
197
|
-
refName: query.refName,
|
|
198
|
-
},
|
|
199
|
-
}));
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
197
|
}
|
|
203
|
-
}
|
|
198
|
+
}
|
|
204
199
|
});
|
|
205
|
-
observer.complete();
|
|
206
200
|
}
|
|
207
201
|
getFeatures(query, opts = {}) {
|
|
208
202
|
return ObservableCreate(async (observer) => {
|
|
209
203
|
try {
|
|
210
204
|
await this.getFeaturesHelper({
|
|
211
|
-
query
|
|
212
|
-
...query,
|
|
213
|
-
start: query.start,
|
|
214
|
-
end: query.end,
|
|
215
|
-
},
|
|
205
|
+
query,
|
|
216
206
|
opts,
|
|
217
207
|
observer,
|
|
218
208
|
allowRedispatch: true,
|
|
219
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();
|
|
220
213
|
}
|
|
221
214
|
catch (e) {
|
|
222
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;
|