@jbrowse/plugin-gtf 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/GtfAdapter/GtfAdapter.d.ts +3 -20
- package/esm/GtfAdapter/GtfAdapter.js +38 -143
- package/esm/GtfAdapter/configSchema.d.ts +36 -10
- package/esm/GtfAdapter/configSchema.js +48 -13
- package/esm/GtfAdapter/index.js +3 -2
- package/esm/GtfTabixAdapter/GtfTabixAdapter.d.ts +12 -0
- package/esm/GtfTabixAdapter/GtfTabixAdapter.js +68 -0
- package/esm/GtfTabixAdapter/configSchema.d.ts +76 -0
- package/esm/GtfTabixAdapter/configSchema.js +86 -0
- package/esm/GtfTabixAdapter/index.d.ts +2 -0
- package/esm/GtfTabixAdapter/index.js +11 -0
- package/esm/index.js +3 -3
- package/esm/util.d.ts +75 -5
- package/esm/util.js +464 -51
- package/package.json +8 -17
- package/esm/GuessAdapter/index.d.ts +0 -2
- package/esm/GuessAdapter/index.js +0 -15
|
@@ -1,28 +1,11 @@
|
|
|
1
1
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
-
import {
|
|
2
|
+
import type { GtfAdapterConfig } from './configSchema.ts';
|
|
3
3
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
|
-
import type { Feature
|
|
4
|
+
import type { Feature } from '@jbrowse/core/util';
|
|
5
5
|
import type { Region } from '@jbrowse/core/util/types';
|
|
6
|
-
|
|
7
|
-
type StatusCallback = (arg: string) => void;
|
|
8
|
-
type SimpleFeat = SimpleFeatureSerialized;
|
|
9
|
-
export default class GtfAdapter extends BaseFeatureDataAdapter {
|
|
10
|
-
calculatedIntervalTreeMap: Record<string, IntervalTree<SimpleFeat>>;
|
|
11
|
-
gtfFeatures?: Promise<{
|
|
12
|
-
header: string;
|
|
13
|
-
intervalTreeMap: Record<string, (sc?: StatusCallback) => IntervalTree<SimpleFeat>>;
|
|
14
|
-
}>;
|
|
15
|
-
private loadDataP;
|
|
6
|
+
export default class GtfAdapter extends BaseFeatureDataAdapter<GtfAdapterConfig> {
|
|
16
7
|
private loadData;
|
|
17
8
|
getRefNames(opts?: BaseOptions): Promise<string[]>;
|
|
18
9
|
getHeader(opts?: BaseOptions): Promise<string>;
|
|
19
10
|
getFeatures(query: Region, opts?: BaseOptions): import("rxjs").Observable<Feature>;
|
|
20
|
-
getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery, }: {
|
|
21
|
-
query: Region;
|
|
22
|
-
opts: BaseOptions;
|
|
23
|
-
observer: Observer<Feature>;
|
|
24
|
-
allowRedispatch: boolean;
|
|
25
|
-
originalQuery?: Region;
|
|
26
|
-
}): Promise<void>;
|
|
27
11
|
}
|
|
28
|
-
export {};
|
|
@@ -1,65 +1,33 @@
|
|
|
1
|
-
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
-
import {
|
|
1
|
+
import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
+
import { SimpleFeature, fetchAndMaybeUnzip } from '@jbrowse/core/util';
|
|
3
3
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
4
|
-
import {
|
|
4
|
+
import { groupLinesByRef, makeFeatureIntervalTreeMap, } from '@jbrowse/core/util/parseLineByLine';
|
|
5
5
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
6
|
-
import {
|
|
7
|
-
import { featureData } from "../util.js";
|
|
6
|
+
import { aggregateGtfFeatures, parseGtfToFeatures } from '../util.js';
|
|
8
7
|
export default class GtfAdapter extends BaseFeatureDataAdapter {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}, opts?.statusCallback);
|
|
33
|
-
const intervalTreeMap = Object.fromEntries(Object.entries(featureMap).map(([refName, lines]) => [
|
|
34
|
-
refName,
|
|
35
|
-
(sc) => {
|
|
36
|
-
if (!this.calculatedIntervalTreeMap[refName]) {
|
|
37
|
-
sc?.('Parsing GTF data');
|
|
38
|
-
const intervalTree = new IntervalTree();
|
|
39
|
-
for (const obj of parseStringSync(lines)
|
|
40
|
-
.flat()
|
|
41
|
-
.map((f, i) => featureData(f, `${this.id}-${refName}-${i}`))) {
|
|
42
|
-
intervalTree.insert([obj.start, obj.end], obj);
|
|
43
|
-
}
|
|
44
|
-
this.calculatedIntervalTreeMap[refName] = intervalTree;
|
|
45
|
-
}
|
|
46
|
-
return this.calculatedIntervalTreeMap[refName];
|
|
47
|
-
},
|
|
48
|
-
]));
|
|
49
|
-
return {
|
|
50
|
-
header: headerLines.join('\n'),
|
|
51
|
-
intervalTreeMap,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
async loadData(opts = {}) {
|
|
55
|
-
if (!this.gtfFeatures) {
|
|
56
|
-
this.gtfFeatures = this.loadDataP(opts).catch((e) => {
|
|
57
|
-
this.gtfFeatures = undefined;
|
|
58
|
-
throw e;
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
return this.gtfFeatures;
|
|
62
|
-
}
|
|
8
|
+
// the whole file is resident after one load, so genes are aggregated once
|
|
9
|
+
// here and stored in the interval tree already spanning all their
|
|
10
|
+
// transcripts; getFeatures is then a plain tree search with no per-query
|
|
11
|
+
// aggregation or redispatch. Progress comes from inside the load itself
|
|
12
|
+
// rather than a label wrapped around it
|
|
13
|
+
loadData = cachedSetup({
|
|
14
|
+
setup: async (opts) => {
|
|
15
|
+
const aggregateField = this.getConf('aggregateField');
|
|
16
|
+
const buffer = await fetchAndMaybeUnzip(openLocation(this.getConf('gtfLocation'), this.pluginManager), opts);
|
|
17
|
+
const { headerLines, linesByRef } = groupLinesByRef(buffer, opts.statusCallback);
|
|
18
|
+
const intervalTreeMap = makeFeatureIntervalTreeMap(linesByRef, (lines, refName) => aggregateGtfFeatures({
|
|
19
|
+
feats: parseGtfToFeatures(lines.map(line => ({ line })), (_record, i) => `${this.id}-${refName}-${i}`),
|
|
20
|
+
aggregateField,
|
|
21
|
+
refName,
|
|
22
|
+
idPrefix: this.id,
|
|
23
|
+
// whole-ref bounds so nothing is clipped at load; the tree search
|
|
24
|
+
// in getFeatures does the per-query clipping instead
|
|
25
|
+
regionStart: 0,
|
|
26
|
+
regionEnd: Number.MAX_SAFE_INTEGER,
|
|
27
|
+
}), 'Parsing GTF data');
|
|
28
|
+
return { header: headerLines.join('\n'), intervalTreeMap };
|
|
29
|
+
},
|
|
30
|
+
});
|
|
63
31
|
async getRefNames(opts = {}) {
|
|
64
32
|
const { intervalTreeMap } = await this.loadData(opts);
|
|
65
33
|
return Object.keys(intervalTreeMap);
|
|
@@ -71,94 +39,21 @@ export default class GtfAdapter extends BaseFeatureDataAdapter {
|
|
|
71
39
|
getFeatures(query, opts = {}) {
|
|
72
40
|
return ObservableCreate(async (observer) => {
|
|
73
41
|
try {
|
|
74
|
-
await this.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
42
|
+
const { intervalTreeMap } = await this.loadData(opts);
|
|
43
|
+
const tree = intervalTreeMap[query.refName];
|
|
44
|
+
if (tree) {
|
|
45
|
+
for (const feat of tree(opts.statusCallback).search([
|
|
46
|
+
query.start,
|
|
47
|
+
query.end,
|
|
48
|
+
])) {
|
|
49
|
+
observer.next(new SimpleFeature({ id: feat.uniqueId, data: feat }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
observer.complete();
|
|
80
53
|
}
|
|
81
54
|
catch (e) {
|
|
82
55
|
observer.error(e);
|
|
83
56
|
}
|
|
84
57
|
}, opts.stopToken);
|
|
85
58
|
}
|
|
86
|
-
async getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery = query, }) {
|
|
87
|
-
const aggregateField = this.getConf('aggregateField');
|
|
88
|
-
const { start, end, refName } = query;
|
|
89
|
-
const { intervalTreeMap } = await this.loadData(opts);
|
|
90
|
-
const feats = intervalTreeMap[refName]?.(opts.statusCallback).search([
|
|
91
|
-
start,
|
|
92
|
-
end,
|
|
93
|
-
]);
|
|
94
|
-
if (feats) {
|
|
95
|
-
if (allowRedispatch && feats.length) {
|
|
96
|
-
let minStart = Number.POSITIVE_INFINITY;
|
|
97
|
-
let maxEnd = Number.NEGATIVE_INFINITY;
|
|
98
|
-
let hasAnyAggregateField = false;
|
|
99
|
-
for (const feat of feats) {
|
|
100
|
-
if (feat.start < minStart) {
|
|
101
|
-
minStart = feat.start;
|
|
102
|
-
}
|
|
103
|
-
if (feat.end > maxEnd) {
|
|
104
|
-
maxEnd = feat.end;
|
|
105
|
-
}
|
|
106
|
-
if (feat[aggregateField]) {
|
|
107
|
-
hasAnyAggregateField = true;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (hasAnyAggregateField &&
|
|
111
|
-
(maxEnd > query.end || minStart < query.start)) {
|
|
112
|
-
await this.getFeaturesHelper({
|
|
113
|
-
query: {
|
|
114
|
-
...query,
|
|
115
|
-
start: minStart - 500_000,
|
|
116
|
-
end: maxEnd + 500_000,
|
|
117
|
-
},
|
|
118
|
-
opts,
|
|
119
|
-
observer,
|
|
120
|
-
allowRedispatch: false,
|
|
121
|
-
originalQuery: query,
|
|
122
|
-
});
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
const parentAggregation = {};
|
|
127
|
-
for (const feat of feats) {
|
|
128
|
-
const aggr = feat[aggregateField];
|
|
129
|
-
if (!parentAggregation[aggr]) {
|
|
130
|
-
parentAggregation[aggr] = [];
|
|
131
|
-
}
|
|
132
|
-
if (aggr) {
|
|
133
|
-
parentAggregation[aggr].push(feat);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
observer.next(new SimpleFeature({
|
|
137
|
-
id: feat.uniqueId,
|
|
138
|
-
data: feat,
|
|
139
|
-
}));
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
for (const [name, subfeatures] of Object.entries(parentAggregation)) {
|
|
143
|
-
const s = min(subfeatures.map(f => f.start));
|
|
144
|
-
const e = max(subfeatures.map(f => f.end));
|
|
145
|
-
if (doesIntersect2(s, e, originalQuery.start, originalQuery.end)) {
|
|
146
|
-
const { uniqueId, strand } = subfeatures[0];
|
|
147
|
-
observer.next(new SimpleFeature({
|
|
148
|
-
id: `${uniqueId}-parent`,
|
|
149
|
-
data: {
|
|
150
|
-
type: 'gene',
|
|
151
|
-
subfeatures,
|
|
152
|
-
strand,
|
|
153
|
-
name,
|
|
154
|
-
start: s,
|
|
155
|
-
end: e,
|
|
156
|
-
refName: query.refName,
|
|
157
|
-
},
|
|
158
|
-
}));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
observer.complete();
|
|
163
|
-
}
|
|
164
59
|
}
|
|
@@ -1,16 +1,42 @@
|
|
|
1
|
+
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* #config GtfAdapter
|
|
5
|
+
* #category adapter
|
|
6
|
+
* #trackType FeatureTrack
|
|
7
|
+
* #fileFormat feature | GTF (plain) | Loaded entirely into memory; for small files
|
|
8
|
+
* used to load plain-text GTF files (optionally gzipped). Loads the whole file
|
|
9
|
+
* into memory, so prefer the GtfTabixAdapter for large files.
|
|
10
|
+
*
|
|
11
|
+
* #example
|
|
12
|
+
* The `uri` shorthand works for plain or gzipped GTF:
|
|
13
|
+
* ```js
|
|
14
|
+
* {
|
|
15
|
+
* type: 'GtfAdapter',
|
|
16
|
+
* uri: 'https://example.com/genes.gtf',
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
1
20
|
declare const GtfAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
21
|
+
/**
|
|
22
|
+
* #slot
|
|
23
|
+
*/
|
|
24
|
+
readonly gtfLocation: {
|
|
25
|
+
readonly type: "fileLocation";
|
|
26
|
+
readonly description: "path to gtf file, also allows for gzipped gtf";
|
|
27
|
+
readonly defaultValue: {
|
|
28
|
+
readonly uri: "/path/to/my.gtf";
|
|
29
|
+
readonly locationType: "UriLocation";
|
|
8
30
|
};
|
|
9
31
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
32
|
+
/**
|
|
33
|
+
* #slot
|
|
34
|
+
*/
|
|
35
|
+
readonly aggregateField: {
|
|
36
|
+
readonly type: "string";
|
|
37
|
+
readonly description: "attribute naming the parent gene that transcripts are aggregated into. transcripts are grouped by gene_id where the file has one (gene names are not unique within a reference), so this is the gene label, and the grouping key only for files with no gene_id";
|
|
38
|
+
readonly defaultValue: "gene_name";
|
|
14
39
|
};
|
|
15
40
|
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
41
|
+
export type GtfAdapterConfig = Instance<typeof GtfAdapter>;
|
|
16
42
|
export default GtfAdapter;
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
function
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return snap.uri
|
|
4
|
+
? {
|
|
5
|
+
...snap,
|
|
6
|
+
gtfLocation: {
|
|
7
|
+
uri: snap.uri,
|
|
8
|
+
baseUri: snap.baseUri,
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
: snap;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* #config GtfAdapter
|
|
15
|
+
* #category adapter
|
|
16
|
+
* #trackType FeatureTrack
|
|
17
|
+
* #fileFormat feature | GTF (plain) | Loaded entirely into memory; for small files
|
|
18
|
+
* used to load plain-text GTF files (optionally gzipped). Loads the whole file
|
|
19
|
+
* into memory, so prefer the GtfTabixAdapter for large files.
|
|
20
|
+
*
|
|
21
|
+
* #example
|
|
22
|
+
* The `uri` shorthand works for plain or gzipped GTF:
|
|
23
|
+
* ```js
|
|
24
|
+
* {
|
|
25
|
+
* type: 'GtfAdapter',
|
|
26
|
+
* uri: 'https://example.com/genes.gtf',
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
3
30
|
const GtfAdapter = ConfigurationSchema('GtfAdapter', {
|
|
31
|
+
/**
|
|
32
|
+
* #slot
|
|
33
|
+
*/
|
|
4
34
|
gtfLocation: {
|
|
5
35
|
type: 'fileLocation',
|
|
6
36
|
description: 'path to gtf file, also allows for gzipped gtf',
|
|
@@ -9,23 +39,28 @@ const GtfAdapter = ConfigurationSchema('GtfAdapter', {
|
|
|
9
39
|
locationType: 'UriLocation',
|
|
10
40
|
},
|
|
11
41
|
},
|
|
42
|
+
/**
|
|
43
|
+
* #slot
|
|
44
|
+
*/
|
|
12
45
|
aggregateField: {
|
|
13
46
|
type: 'string',
|
|
14
|
-
description: '
|
|
47
|
+
description: 'attribute naming the parent gene that transcripts are aggregated into. transcripts are grouped by gene_id where the file has one (gene names are not unique within a reference), so this is the gene label, and the grouping key only for files with no gene_id',
|
|
15
48
|
defaultValue: 'gene_name',
|
|
16
49
|
},
|
|
17
50
|
}, {
|
|
18
51
|
explicitlyTyped: true,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
/**
|
|
53
|
+
* #preProcessSnapshot
|
|
54
|
+
*
|
|
55
|
+
*
|
|
56
|
+
* preprocessor to allow minimal config:
|
|
57
|
+
* ```json
|
|
58
|
+
* {
|
|
59
|
+
* "type": "GtfAdapter",
|
|
60
|
+
* "uri": "yourfile.gtf"
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
30
65
|
});
|
|
31
66
|
export default GtfAdapter;
|
package/esm/GtfAdapter/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AdapterType } from '@jbrowse/core/pluggableElementTypes';
|
|
2
|
-
import configSchema from
|
|
2
|
+
import configSchema, { normalizeSnapshot } from './configSchema.js';
|
|
3
3
|
export default function GtfAdapterF(pluginManager) {
|
|
4
4
|
pluginManager.addAdapterType(() => new AdapterType({
|
|
5
5
|
name: 'GtfAdapter',
|
|
6
6
|
displayName: 'GTF adapter',
|
|
7
|
+
normalizeSnapshot,
|
|
7
8
|
configSchema,
|
|
8
|
-
getAdapterClass: () => import(
|
|
9
|
+
getAdapterClass: () => import('./GtfAdapter.js').then(r => r.default),
|
|
9
10
|
}));
|
|
10
11
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
|
+
import type { GtfTabixAdapterConfig } from './configSchema.ts';
|
|
3
|
+
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
|
+
import type { Feature } from '@jbrowse/core/util';
|
|
5
|
+
import type { Region } from '@jbrowse/core/util/types';
|
|
6
|
+
export default class GtfTabixAdapter extends BaseFeatureDataAdapter<GtfTabixAdapterConfig> {
|
|
7
|
+
private configure;
|
|
8
|
+
getRefNames(opts?: BaseOptions): Promise<string[]>;
|
|
9
|
+
getHeader(opts?: BaseOptions): Promise<string>;
|
|
10
|
+
getRegionByteSize(regions: Region[], opts?: BaseOptions): Promise<number>;
|
|
11
|
+
getFeatures(query: Region, opts?: BaseOptions): import("rxjs").Observable<Feature>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { TabixIndexedFile } from '@gmod/tabix';
|
|
2
|
+
import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
3
|
+
import { SimpleFeature, downloadStatus } from '@jbrowse/core/util';
|
|
4
|
+
import { sharedBgzfWorkerPool } from '@jbrowse/core/util/bgzfWorkerPool';
|
|
5
|
+
import { decompressedBytesBudget } from '@jbrowse/core/util/cacheBudgets';
|
|
6
|
+
import { openLocation, openTabixIndexFilehandle } from '@jbrowse/core/util/io';
|
|
7
|
+
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
8
|
+
import { readTabixLinesRedispatched } from '@jbrowse/core/util/tabix';
|
|
9
|
+
import { aggregateGtfFeatures, parseGtfToFeatures } from '../util.js';
|
|
10
|
+
export default class GtfTabixAdapter extends BaseFeatureDataAdapter {
|
|
11
|
+
configure = cachedSetup({
|
|
12
|
+
label: 'Downloading index',
|
|
13
|
+
setup: async (_opts, onProgress) => {
|
|
14
|
+
const gtf = new TabixIndexedFile({
|
|
15
|
+
filehandle: openLocation(this.getConf('gtfGzLocation'), this.pluginManager),
|
|
16
|
+
...openTabixIndexFilehandle(this.getConf(['index', 'location']), this.getConf(['index', 'indexType']), this.pluginManager),
|
|
17
|
+
chunkCacheBudget: decompressedBytesBudget,
|
|
18
|
+
bgzfWorkerPool: sharedBgzfWorkerPool(),
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
gtf,
|
|
22
|
+
dontRedispatchSet: new Set(this.getConf('dontRedispatch')),
|
|
23
|
+
// the index is a whole-file read, so its byte ticks turn the
|
|
24
|
+
// "Downloading index" label into a determinate bar
|
|
25
|
+
header: await gtf.getHeader({ onProgress }),
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
async getRefNames(opts = {}) {
|
|
30
|
+
const { gtf } = await this.configure(opts);
|
|
31
|
+
return downloadStatus('Downloading index', opts.statusCallback, onProgress => gtf.getReferenceSequenceNames({ ...opts, onProgress }));
|
|
32
|
+
}
|
|
33
|
+
async getHeader(opts = {}) {
|
|
34
|
+
const { header } = await this.configure(opts);
|
|
35
|
+
return header;
|
|
36
|
+
}
|
|
37
|
+
// Index-only compressed-byte estimate (no feature download), used by the
|
|
38
|
+
// feature-fetch RPC to short-circuit an over-budget region before pulling
|
|
39
|
+
// every line — see executeRenderFeatureData.
|
|
40
|
+
async getRegionByteSize(regions, opts = {}) {
|
|
41
|
+
const { gtf } = await this.configure(opts);
|
|
42
|
+
return gtf.bytesForRegions(regions, opts);
|
|
43
|
+
}
|
|
44
|
+
getFeatures(query, opts = {}) {
|
|
45
|
+
return ObservableCreate(async (observer) => {
|
|
46
|
+
try {
|
|
47
|
+
const { gtf, dontRedispatchSet } = await this.configure(opts);
|
|
48
|
+
const lines = await readTabixLinesRedispatched(gtf, query, dontRedispatchSet, opts);
|
|
49
|
+
const feats = parseGtfToFeatures(lines, record => `${this.id}-offset-${record.offset}`);
|
|
50
|
+
const aggregated = aggregateGtfFeatures({
|
|
51
|
+
feats,
|
|
52
|
+
aggregateField: this.getConf('aggregateField'),
|
|
53
|
+
refName: query.refName,
|
|
54
|
+
idPrefix: this.id,
|
|
55
|
+
regionStart: query.start,
|
|
56
|
+
regionEnd: query.end,
|
|
57
|
+
});
|
|
58
|
+
for (const feat of aggregated) {
|
|
59
|
+
observer.next(new SimpleFeature({ id: feat.uniqueId, data: feat }));
|
|
60
|
+
}
|
|
61
|
+
observer.complete();
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
observer.error(e);
|
|
65
|
+
}
|
|
66
|
+
}, opts.stopToken);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { Instance } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* #config GtfTabixAdapter
|
|
5
|
+
* #category adapter
|
|
6
|
+
* #trackType FeatureTrack
|
|
7
|
+
* #fileFormat feature | GTF (tabix)
|
|
8
|
+
* used to load bgzip-compressed, tabix-indexed GTF files
|
|
9
|
+
*
|
|
10
|
+
* #example
|
|
11
|
+
* The `uri` shorthand auto-resolves the `.tbi` index; add `csi: true` for a
|
|
12
|
+
* `.csi` index instead:
|
|
13
|
+
* ```js
|
|
14
|
+
* {
|
|
15
|
+
* type: 'GtfTabixAdapter',
|
|
16
|
+
* uri: 'https://example.com/genes.gtf.gz',
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare const GtfTabixAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
21
|
+
/**
|
|
22
|
+
* #slot
|
|
23
|
+
* location of the bgzip-compressed GTF, sorted by position. Must be bgzip
|
|
24
|
+
* rather than plain gzip, which tabix cannot index.
|
|
25
|
+
*/
|
|
26
|
+
readonly gtfGzLocation: {
|
|
27
|
+
readonly type: "fileLocation";
|
|
28
|
+
readonly defaultValue: {
|
|
29
|
+
readonly uri: "/path/to/my.gtf.gz";
|
|
30
|
+
readonly locationType: "UriLocation";
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
readonly index: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
34
|
+
readonly indexType: {
|
|
35
|
+
readonly model: import("@jbrowse/mobx-state-tree").ISimpleType<"CSI" | "TBI">;
|
|
36
|
+
readonly type: 'stringEnum';
|
|
37
|
+
readonly defaultValue: 'TBI';
|
|
38
|
+
readonly description: '`TBI` is the usual `tabix` output. `CSI` is required for a reference longer than 512 Mb, which TBI cannot address.';
|
|
39
|
+
};
|
|
40
|
+
readonly location: {
|
|
41
|
+
readonly type: 'fileLocation';
|
|
42
|
+
readonly defaultValue: {
|
|
43
|
+
readonly uri: '/path/to/my.gz.tbi';
|
|
44
|
+
readonly locationType: 'UriLocation';
|
|
45
|
+
};
|
|
46
|
+
readonly description: 'location of the tabix index. Only needed when it is not named `<file>.tbi` (or `.csi`), which is what the `uri` shorthand assumes.';
|
|
47
|
+
};
|
|
48
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
49
|
+
/**
|
|
50
|
+
* #slot
|
|
51
|
+
* the GtfTabixAdapter has to "redispatch" if it fetches a region and
|
|
52
|
+
* features it finds inside that region extend outside the region we
|
|
53
|
+
* requested. you can disable this for certain feature types to avoid
|
|
54
|
+
* fetching e.g. the entire chromosome
|
|
55
|
+
*
|
|
56
|
+
* the defaults are the whole-sequence records the common annotation sources
|
|
57
|
+
* emit: `region` (NCBI), `supercontig`/`scaffold` (Ensembl, for
|
|
58
|
+
* non-chromosomal sequences), plus `chromosome` and `contig`. They span an
|
|
59
|
+
* entire reference and have no children, so letting one expand the fetch
|
|
60
|
+
* would pull a whole chromosome to gain nothing
|
|
61
|
+
*/
|
|
62
|
+
readonly dontRedispatch: {
|
|
63
|
+
readonly type: "stringArray";
|
|
64
|
+
readonly defaultValue: readonly ["chromosome", "region", "contig", "supercontig", "scaffold"];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* #slot
|
|
68
|
+
*/
|
|
69
|
+
readonly aggregateField: {
|
|
70
|
+
readonly type: "string";
|
|
71
|
+
readonly description: "attribute naming the parent gene that transcripts are aggregated into. transcripts are grouped by gene_id where the file has one (gene names are not unique within a reference), so this is the gene label, and the grouping key only for files with no gene_id";
|
|
72
|
+
readonly defaultValue: "gene_name";
|
|
73
|
+
};
|
|
74
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
75
|
+
export type GtfTabixAdapterConfig = Instance<typeof GtfTabixAdapter>;
|
|
76
|
+
export default GtfTabixAdapter;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ConfigurationSchema, expandTabixShorthand, tabixIndexFields, } from '@jbrowse/core/configuration';
|
|
2
|
+
export function normalizeSnapshot(snap) {
|
|
3
|
+
return expandTabixShorthand(snap, 'gtfGzLocation');
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* #config GtfTabixAdapter
|
|
7
|
+
* #category adapter
|
|
8
|
+
* #trackType FeatureTrack
|
|
9
|
+
* #fileFormat feature | GTF (tabix)
|
|
10
|
+
* used to load bgzip-compressed, tabix-indexed GTF files
|
|
11
|
+
*
|
|
12
|
+
* #example
|
|
13
|
+
* The `uri` shorthand auto-resolves the `.tbi` index; add `csi: true` for a
|
|
14
|
+
* `.csi` index instead:
|
|
15
|
+
* ```js
|
|
16
|
+
* {
|
|
17
|
+
* type: 'GtfTabixAdapter',
|
|
18
|
+
* uri: 'https://example.com/genes.gtf.gz',
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
const GtfTabixAdapter = ConfigurationSchema('GtfTabixAdapter', {
|
|
23
|
+
/**
|
|
24
|
+
* #slot
|
|
25
|
+
* location of the bgzip-compressed GTF, sorted by position. Must be bgzip
|
|
26
|
+
* rather than plain gzip, which tabix cannot index.
|
|
27
|
+
*/
|
|
28
|
+
gtfGzLocation: {
|
|
29
|
+
type: 'fileLocation',
|
|
30
|
+
defaultValue: {
|
|
31
|
+
uri: '/path/to/my.gtf.gz',
|
|
32
|
+
locationType: 'UriLocation',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
index: ConfigurationSchema('TabixIndex', { ...tabixIndexFields }),
|
|
36
|
+
/**
|
|
37
|
+
* #slot
|
|
38
|
+
* the GtfTabixAdapter has to "redispatch" if it fetches a region and
|
|
39
|
+
* features it finds inside that region extend outside the region we
|
|
40
|
+
* requested. you can disable this for certain feature types to avoid
|
|
41
|
+
* fetching e.g. the entire chromosome
|
|
42
|
+
*
|
|
43
|
+
* the defaults are the whole-sequence records the common annotation sources
|
|
44
|
+
* emit: `region` (NCBI), `supercontig`/`scaffold` (Ensembl, for
|
|
45
|
+
* non-chromosomal sequences), plus `chromosome` and `contig`. They span an
|
|
46
|
+
* entire reference and have no children, so letting one expand the fetch
|
|
47
|
+
* would pull a whole chromosome to gain nothing
|
|
48
|
+
*/
|
|
49
|
+
dontRedispatch: {
|
|
50
|
+
type: 'stringArray',
|
|
51
|
+
defaultValue: [
|
|
52
|
+
'chromosome',
|
|
53
|
+
'region',
|
|
54
|
+
'contig',
|
|
55
|
+
'supercontig',
|
|
56
|
+
'scaffold',
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* #slot
|
|
61
|
+
*/
|
|
62
|
+
aggregateField: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'attribute naming the parent gene that transcripts are aggregated into. transcripts are grouped by gene_id where the file has one (gene names are not unique within a reference), so this is the gene label, and the grouping key only for files with no gene_id',
|
|
65
|
+
defaultValue: 'gene_name',
|
|
66
|
+
},
|
|
67
|
+
}, {
|
|
68
|
+
explicitlyTyped: true,
|
|
69
|
+
/**
|
|
70
|
+
* #preProcessSnapshot
|
|
71
|
+
*
|
|
72
|
+
*
|
|
73
|
+
* preprocessor to allow minimal config, assumes tbi index at
|
|
74
|
+
* yourfile.gtf.gz.tbi (or .csi if csi:true):
|
|
75
|
+
*
|
|
76
|
+
* ```json
|
|
77
|
+
* {
|
|
78
|
+
* "type": "GtfTabixAdapter",
|
|
79
|
+
* "uri": "yourfile.gtf.gz",
|
|
80
|
+
* "csi": true
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
preProcessSnapshot: normalizeSnapshot,
|
|
85
|
+
});
|
|
86
|
+
export default GtfTabixAdapter;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AdapterType } from '@jbrowse/core/pluggableElementTypes';
|
|
2
|
+
import configSchema, { normalizeSnapshot } from './configSchema.js';
|
|
3
|
+
export default function GtfTabixAdapterF(pluginManager) {
|
|
4
|
+
pluginManager.addAdapterType(() => new AdapterType({
|
|
5
|
+
name: 'GtfTabixAdapter',
|
|
6
|
+
displayName: 'GTF tabix adapter',
|
|
7
|
+
normalizeSnapshot,
|
|
8
|
+
configSchema,
|
|
9
|
+
getAdapterClass: () => import('./GtfTabixAdapter.js').then(r => r.default),
|
|
10
|
+
}));
|
|
11
|
+
}
|
package/esm/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import Plugin from '@jbrowse/core/Plugin';
|
|
2
|
-
import GtfAdapterF from
|
|
3
|
-
import
|
|
2
|
+
import GtfAdapterF from './GtfAdapter/index.js';
|
|
3
|
+
import GtfTabixAdapterF from './GtfTabixAdapter/index.js';
|
|
4
4
|
export default class GtfPlugin extends Plugin {
|
|
5
5
|
name = 'GTFPlugin';
|
|
6
6
|
install(pluginManager) {
|
|
7
7
|
GtfAdapterF(pluginManager);
|
|
8
|
-
|
|
8
|
+
GtfTabixAdapterF(pluginManager);
|
|
9
9
|
}
|
|
10
10
|
}
|