@jbrowse/plugin-bed 5.0.0-beta.1 → 5.0.0-beta.11
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 +1 -3
- package/esm/BedAdapter/BedAdapter.js +3 -10
- package/esm/BedAdapter/configSchema.d.ts +30 -52
- package/esm/BedAdapter/configSchema.js +2 -4
- package/esm/BedGraphAdapter/BedGraphAdapter.d.ts +1 -3
- package/esm/BedGraphAdapter/BedGraphAdapter.js +8 -10
- package/esm/BedGraphAdapter/configSchema.d.ts +15 -17
- package/esm/BedGraphAdapter/configSchema.js +5 -10
- package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.js +11 -8
- package/esm/BedGraphTabixAdapter/configSchema.d.ts +25 -30
- package/esm/BedGraphTabixAdapter/configSchema.js +11 -3
- package/esm/BedTabixAdapter/BedTabixAdapter.js +11 -12
- package/esm/BedTabixAdapter/configSchema.d.ts +33 -46
- package/esm/BedTabixAdapter/configSchema.js +9 -2
- package/esm/BedpeAdapter/BedpeAdapter.d.ts +3 -4
- package/esm/BedpeAdapter/BedpeAdapter.js +3 -10
- package/esm/BedpeAdapter/configSchema.d.ts +12 -16
- package/esm/BedpeAdapter/configSchema.js +2 -11
- package/esm/BedpeAdapter/util.js +3 -2
- package/esm/BigBedAdapter/BigBedAdapter.js +4 -5
- package/esm/BigBedAdapter/configSchema.d.ts +21 -29
- package/esm/BigBedAdapter/configSchema.js +4 -10
- package/esm/BigBedTextSearchAdapter/BigBedTextSearchAdapter.d.ts +27 -0
- package/esm/BigBedTextSearchAdapter/BigBedTextSearchAdapter.js +128 -0
- package/esm/BigBedTextSearchAdapter/configSchema.d.ts +51 -0
- package/esm/BigBedTextSearchAdapter/configSchema.js +57 -0
- package/esm/BigBedTextSearchAdapter/index.d.ts +2 -0
- package/esm/BigBedTextSearchAdapter/index.js +11 -0
- package/esm/StarFusionAdapter/StarFusionAdapter.d.ts +2 -3
- package/esm/StarFusionAdapter/StarFusionAdapter.js +16 -71
- package/esm/StarFusionAdapter/configSchema.d.ts +9 -10
- package/esm/StarFusionAdapter/configSchema.js +2 -11
- package/esm/adapterUtil.d.ts +1 -1
- package/esm/adapterUtil.js +20 -5
- package/esm/bedGraphUtil.d.ts +2 -1
- package/esm/bedGraphUtil.js +5 -3
- package/esm/copyExcept.d.ts +1 -0
- package/esm/copyExcept.js +11 -0
- package/esm/generateBedMethylFeature.js +12 -7
- package/esm/generateRepeatMaskerFeature.d.ts +17 -4
- package/esm/generateRepeatMaskerFeature.js +29 -26
- package/esm/generateUcscTranscript.js +51 -4
- package/esm/index.js +2 -0
- package/esm/util.js +25 -14
- package/package.json +12 -7
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import TextSearchAdapterType from '@jbrowse/core/pluggableElementTypes/TextSearchAdapterType';
|
|
2
|
+
import configSchema from './configSchema.js';
|
|
3
|
+
export default function BigBedTextSearchAdapterF(pluginManager) {
|
|
4
|
+
pluginManager.addTextSearchAdapterType(() => new TextSearchAdapterType({
|
|
5
|
+
name: 'BigBedTextSearchAdapter',
|
|
6
|
+
displayName: 'BigBed text search adapter',
|
|
7
|
+
description: 'Searches a BigBed by its extra-indexed names',
|
|
8
|
+
configSchema,
|
|
9
|
+
getAdapterClass: () => import('./BigBedTextSearchAdapter.js').then(r => r.default),
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
2
2
|
import type { StarFusionAdapterConfig } from './configSchema.ts';
|
|
3
3
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
4
|
-
import type { Feature,
|
|
4
|
+
import type { Feature, Region } from '@jbrowse/core/util';
|
|
5
5
|
export default class StarFusionAdapter extends BaseFeatureDataAdapter<StarFusionAdapterConfig> {
|
|
6
6
|
private loadData;
|
|
7
|
-
protected intervalTrees: Record<string, Promise<IntervalTree<Feature> | undefined> | undefined>;
|
|
8
7
|
static capabilities: string[];
|
|
9
8
|
private loadDataP;
|
|
10
9
|
getRefNames(opts?: BaseOptions): Promise<string[]>;
|
|
11
10
|
private featureFromLine;
|
|
12
11
|
private loadFeatureTreeP;
|
|
13
|
-
private
|
|
12
|
+
private treeFeatures;
|
|
14
13
|
getFeatures(query: Region, opts?: BaseOptions): import("rxjs").Observable<Feature>;
|
|
15
14
|
}
|
|
@@ -2,44 +2,10 @@ import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapter
|
|
|
2
2
|
import { SimpleFeature, fetchAndMaybeUnzip } from '@jbrowse/core/util';
|
|
3
3
|
import { openLocation } from '@jbrowse/core/util/io';
|
|
4
4
|
import { parseLineByLine } from '@jbrowse/core/util/parseLineByLine';
|
|
5
|
+
import { parseStarFusionBreakpoint, starFusionColumns, } from '@jbrowse/core/util/starFusion';
|
|
5
6
|
import { buildPairedIntervalTree, intervalTreeFeatures, } from '../adapterUtil.js';
|
|
6
|
-
/**
|
|
7
|
-
* A STAR-Fusion `refName:pos:strand` breakpoint, read from the RIGHT: a refName
|
|
8
|
-
* may contain colons. GRCh38's full analysis set names its HLA contigs
|
|
9
|
-
* `HLA-A*01:01:01:01`, so a breakpoint on one arrives as
|
|
10
|
-
* `HLA-A*01:01:01:01:5000:+` and splitting at the first colon read the contig as
|
|
11
|
-
* `HLA-A*01`, the position as 1 and the strand as absent. Same rule
|
|
12
|
-
* `parseSvAlt` applies to a BND mate locstring, one field further along.
|
|
13
|
-
*
|
|
14
|
-
* A string with fewer than three fields keeps what it always parsed to, so a
|
|
15
|
-
* truncated or strandless row is no worse off than before.
|
|
16
|
-
*/
|
|
17
|
-
function parseBreakpoint(str) {
|
|
18
|
-
const parts = str.split(':');
|
|
19
|
-
const strandStr = parts.length >= 3 ? parts.pop() : undefined;
|
|
20
|
-
const pos = parts.length >= 2 ? +parts.pop() : Number.NaN;
|
|
21
|
-
return {
|
|
22
|
-
refName: parts.join(':'),
|
|
23
|
-
start: pos,
|
|
24
|
-
end: pos + 1,
|
|
25
|
-
strand: strandStr === '+' ? 1 : strandStr === '-' ? -1 : undefined,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Which way the sequence a breakpoint keeps runs from it, as the paired-arc
|
|
30
|
-
* display's mate-direction tick (1 = right, -1 = left, 0 = unknown). A
|
|
31
|
-
* breakpoint's strand in STAR-Fusion is its gene's transcription strand, and the
|
|
32
|
-
* fusion transcript keeps the donor's sequence 5' of the junction and the
|
|
33
|
-
* acceptor's 3' of it: so on a + strand donor the retained side is at lower
|
|
34
|
-
* coordinates and on a + strand acceptor it is at higher ones.
|
|
35
|
-
*/
|
|
36
|
-
function tickDirection(strand, isDonor) {
|
|
37
|
-
return strand === undefined ? 0 : isDonor ? -strand : strand;
|
|
38
|
-
}
|
|
39
7
|
export default class StarFusionAdapter extends BaseFeatureDataAdapter {
|
|
40
|
-
// No `label`: `fetchAndMaybeUnzip` narrates the download from inside.
|
|
41
8
|
loadData = cachedSetup({ setup: opts => this.loadDataP(opts) });
|
|
42
|
-
intervalTrees = {};
|
|
43
9
|
static capabilities = ['getFeatures', 'getRefNames'];
|
|
44
10
|
async loadDataP(opts) {
|
|
45
11
|
const buffer = await fetchAndMaybeUnzip(openLocation(this.getConf('starFusionLocation'), this.pluginManager), opts);
|
|
@@ -48,23 +14,19 @@ export default class StarFusionAdapter extends BaseFeatureDataAdapter {
|
|
|
48
14
|
let rightIdx = -1;
|
|
49
15
|
const feats1 = {};
|
|
50
16
|
const feats2 = {};
|
|
51
|
-
parseLineByLine(buffer, line => {
|
|
52
|
-
if (
|
|
53
|
-
columnNames = line
|
|
17
|
+
parseLineByLine(buffer, (line, lineIndex) => {
|
|
18
|
+
if (lineIndex === 0) {
|
|
19
|
+
columnNames = starFusionColumns(line);
|
|
54
20
|
leftIdx = columnNames.indexOf('LeftBreakpoint');
|
|
55
21
|
rightIdx = columnNames.indexOf('RightBreakpoint');
|
|
56
22
|
}
|
|
57
|
-
else if (
|
|
23
|
+
else if (!line.startsWith('#')) {
|
|
58
24
|
const cols = line.split('\t');
|
|
59
25
|
const left = cols[leftIdx];
|
|
60
26
|
const right = cols[rightIdx];
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const leftRef = left ? parseBreakpoint(left).refName : undefined;
|
|
65
|
-
const rightRef = right ? parseBreakpoint(right).refName : undefined;
|
|
66
|
-
if (leftRef && rightRef) {
|
|
67
|
-
;
|
|
27
|
+
if (left && right) {
|
|
28
|
+
const leftRef = parseStarFusionBreakpoint(left, true).refName;
|
|
29
|
+
const rightRef = parseStarFusionBreakpoint(right, false).refName;
|
|
68
30
|
(feats1[leftRef] ??= []).push(line);
|
|
69
31
|
(feats2[rightRef] ??= []).push(line);
|
|
70
32
|
}
|
|
@@ -80,27 +42,16 @@ export default class StarFusionAdapter extends BaseFeatureDataAdapter {
|
|
|
80
42
|
featureFromLine(line, columnNames, uniqueId, flip) {
|
|
81
43
|
const cols = line.split('\t');
|
|
82
44
|
const row = Object.fromEntries(columnNames.map((name, i) => [name, cols[i]]));
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
// junction whenever it is not flipped, and the mate is the other end
|
|
45
|
+
const donor = parseStarFusionBreakpoint(row.LeftBreakpoint, true);
|
|
46
|
+
const acceptor = parseStarFusionBreakpoint(row.RightBreakpoint, false);
|
|
47
|
+
const [primary, mate] = flip ? [acceptor, donor] : [donor, acceptor];
|
|
87
48
|
return new SimpleFeature({
|
|
88
49
|
uniqueId,
|
|
89
|
-
|
|
90
|
-
start: primaryBp.start,
|
|
91
|
-
end: primaryBp.end,
|
|
92
|
-
strand: primaryBp.strand,
|
|
93
|
-
mateDirection: tickDirection(primaryBp.strand, !flip),
|
|
50
|
+
...primary,
|
|
94
51
|
name: row.FusionName,
|
|
95
52
|
score: row.JunctionReadCount ? +row.JunctionReadCount : undefined,
|
|
96
53
|
type: 'fusion',
|
|
97
|
-
mate
|
|
98
|
-
refName: mateBp.refName,
|
|
99
|
-
start: mateBp.start,
|
|
100
|
-
end: mateBp.end,
|
|
101
|
-
strand: mateBp.strand,
|
|
102
|
-
mateDirection: tickDirection(mateBp.strand, flip),
|
|
103
|
-
},
|
|
54
|
+
mate,
|
|
104
55
|
...row,
|
|
105
56
|
});
|
|
106
57
|
}
|
|
@@ -108,14 +59,8 @@ export default class StarFusionAdapter extends BaseFeatureDataAdapter {
|
|
|
108
59
|
const { columnNames, feats1, feats2 } = await this.loadData();
|
|
109
60
|
return buildPairedIntervalTree(feats1, feats2, refName, this.id, (line, uniqueId, flip) => this.featureFromLine(line, columnNames, uniqueId, flip));
|
|
110
61
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
throw e;
|
|
115
|
-
});
|
|
116
|
-
return this.intervalTrees[refName];
|
|
117
|
-
}
|
|
118
|
-
getFeatures(query, opts = {}) {
|
|
119
|
-
return intervalTreeFeatures(query, opts, refName => this.loadFeatureTree(refName));
|
|
62
|
+
treeFeatures = intervalTreeFeatures(opts => this.loadData(opts), refName => this.loadFeatureTreeP(refName));
|
|
63
|
+
getFeatures(query, opts) {
|
|
64
|
+
return this.treeFeatures(query, opts);
|
|
120
65
|
}
|
|
121
66
|
}
|
|
@@ -15,17 +15,16 @@ export declare function normalizeSnapshot(snap: Record<string, unknown>): Record
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
declare const StarFusionAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
type: {
|
|
19
|
+
type: "string";
|
|
20
|
+
} & {
|
|
21
|
+
defaultValue: string;
|
|
22
|
+
};
|
|
21
23
|
readonly starFusionLocation: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
readonly uri: "/path/to/star-fusion.fusion_predictions.tsv";
|
|
26
|
-
readonly locationType: "UriLocation";
|
|
27
|
-
};
|
|
24
|
+
type: "fileLocation";
|
|
25
|
+
} & {
|
|
26
|
+
defaultValue: unknown;
|
|
28
27
|
};
|
|
29
|
-
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
28
|
+
}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
30
29
|
export type StarFusionAdapterConfig = Instance<typeof StarFusionAdapter>;
|
|
31
30
|
export default StarFusionAdapter;
|
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
1
|
+
import { ConfigurationSchema, expandUriShorthand, } from '@jbrowse/core/configuration';
|
|
2
2
|
export function normalizeSnapshot(snap) {
|
|
3
|
-
return snap
|
|
4
|
-
? {
|
|
5
|
-
...snap,
|
|
6
|
-
starFusionLocation: {
|
|
7
|
-
uri: snap.uri,
|
|
8
|
-
baseUri: snap.baseUri,
|
|
9
|
-
locationType: 'UriLocation',
|
|
10
|
-
},
|
|
11
|
-
}
|
|
12
|
-
: snap;
|
|
3
|
+
return expandUriShorthand(snap, 'starFusionLocation');
|
|
13
4
|
}
|
|
14
5
|
/**
|
|
15
6
|
* #config StarFusionAdapter
|
package/esm/adapterUtil.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IntervalTree } from '@jbrowse/core/util';
|
|
2
2
|
import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
|
|
3
3
|
import type { Feature, Region } from '@jbrowse/core/util';
|
|
4
|
-
export declare function intervalTreeFeatures(
|
|
4
|
+
export declare function intervalTreeFeatures(loadData: (opts: BaseOptions) => Promise<unknown>, buildTree: (refName: string) => Promise<IntervalTree<Feature> | undefined>): (query: Region, opts?: BaseOptions) => import("rxjs").Observable<Feature>;
|
|
5
5
|
export declare function buildPairedIntervalTree(feats1: Record<string, string[]>, feats2: Record<string, string[]>, refName: string, idPrefix: string, makeFeature: (line: string, uniqueId: string, flip: boolean) => Feature): IntervalTree<Feature>;
|
package/esm/adapterUtil.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { IntervalTree } from '@jbrowse/core/util';
|
|
2
2
|
import { ObservableCreate } from '@jbrowse/core/util/rxjs';
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
// `getFeatures` for an in-memory interval-tree adapter: one tree per refName,
|
|
4
|
+
// built once and rebuilt after a failure. `loadData` is awaited with each
|
|
5
|
+
// caller's opts ahead of the tree, so every fetch joins the whole-file load's
|
|
6
|
+
// progress rather than only the one that started it.
|
|
7
|
+
export function intervalTreeFeatures(loadData, buildTree) {
|
|
8
|
+
const trees = new Map();
|
|
9
|
+
const loadTree = (refName) => {
|
|
10
|
+
let tree = trees.get(refName);
|
|
11
|
+
if (tree === undefined) {
|
|
12
|
+
tree = buildTree(refName).catch((e) => {
|
|
13
|
+
trees.delete(refName);
|
|
14
|
+
throw e;
|
|
15
|
+
});
|
|
16
|
+
trees.set(refName, tree);
|
|
17
|
+
}
|
|
18
|
+
return tree;
|
|
19
|
+
};
|
|
20
|
+
return (query, opts = {}) => ObservableCreate(async (observer) => {
|
|
21
|
+
await loadData(opts);
|
|
7
22
|
const tree = await loadTree(query.refName);
|
|
8
23
|
for (const f of tree?.search([query.start, query.end]) ?? []) {
|
|
9
24
|
observer.next(f);
|
|
10
25
|
}
|
|
11
26
|
observer.complete();
|
|
12
|
-
}, opts.
|
|
27
|
+
}, opts.signal);
|
|
13
28
|
}
|
|
14
29
|
// Build one interval tree for a refName from paired-feature buckets, inserting
|
|
15
30
|
// the records that start on this ref (feats1, flip=false) and those whose mate
|
package/esm/bedGraphUtil.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Feature } from '@jbrowse/core/util';
|
|
2
|
-
export declare function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, value, }: {
|
|
2
|
+
export declare function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, value, rowSource, }: {
|
|
3
3
|
uniqueId: string;
|
|
4
4
|
refName: string;
|
|
5
5
|
start: number;
|
|
@@ -7,4 +7,5 @@ export declare function makeBedGraphFeature({ uniqueId, refName, start, end, nam
|
|
|
7
7
|
names: string[];
|
|
8
8
|
j: number;
|
|
9
9
|
value: string;
|
|
10
|
+
rowSource?: string;
|
|
10
11
|
}): Feature | undefined;
|
package/esm/bedGraphUtil.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { SimpleFeature } from '@jbrowse/core/util';
|
|
2
2
|
// Build a single-score bedGraph feature for value column j, or undefined when
|
|
3
|
-
// the value is not numeric.
|
|
4
|
-
|
|
3
|
+
// the value is not numeric. A wide file names each value column's source in
|
|
4
|
+
// its header; a tidy one has a `source` column naming each row's instead.
|
|
5
|
+
// Shared by BedGraphAdapter and BedGraphTabixAdapter.
|
|
6
|
+
export function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, value, rowSource, }) {
|
|
5
7
|
const score = +value;
|
|
6
8
|
return Number.isNaN(score)
|
|
7
9
|
? undefined
|
|
@@ -12,7 +14,7 @@ export function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, v
|
|
|
12
14
|
start,
|
|
13
15
|
end,
|
|
14
16
|
score,
|
|
15
|
-
source: names[j] || `col${j}
|
|
17
|
+
source: rowSource ?? (names[j] || `col${j}`),
|
|
16
18
|
},
|
|
17
19
|
});
|
|
18
20
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function copyExcept(data: Record<string, unknown>, excluded: ReadonlySet<string>): Record<string, unknown>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `const { a, b, ...rest } = data` in the same key order, which measured
|
|
2
|
+
// slower on every BED line shape (benches/featureData.bench.ts)
|
|
3
|
+
export function copyExcept(data, excluded) {
|
|
4
|
+
const out = {};
|
|
5
|
+
for (const key in data) {
|
|
6
|
+
if (!excluded.has(key)) {
|
|
7
|
+
out[key] = data[key];
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { parseStrand } from './util.js';
|
|
2
2
|
// this uses modkit bedMethyl. unclear how to reliably detect minimal 9+2 bedMethyl
|
|
3
3
|
export function isBedMethylFeature({ splitLine, start, end, }) {
|
|
4
|
+
if (splitLine.length < 18 ||
|
|
5
|
+
+splitLine[6] !== start ||
|
|
6
|
+
+splitLine[7] !== end) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
4
9
|
// columns 9-17 are the nine numeric methylation stats
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
for (let i = 9; i < 18; i++) {
|
|
11
|
+
const x = splitLine[i];
|
|
12
|
+
if (!x || Number.isNaN(+x)) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
12
17
|
}
|
|
13
18
|
export function generateBedMethylFeature({ splitLine, uniqueId, refName, start, end, }) {
|
|
14
19
|
// see
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
declare
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export declare function parseRepeatMaskerDescription(desc: unknown): {
|
|
2
|
+
bitsw_score: string;
|
|
3
|
+
percent_div: string;
|
|
4
|
+
percent_del: string;
|
|
5
|
+
percent_ins: string;
|
|
6
|
+
query_chr: string;
|
|
7
|
+
query_begin: string;
|
|
8
|
+
query_end: string;
|
|
9
|
+
query_remaining: string;
|
|
10
|
+
orientation: string;
|
|
11
|
+
matching_repeat_name: string;
|
|
12
|
+
matching_repeat_class: string;
|
|
13
|
+
matching_repeat_begin: string;
|
|
14
|
+
matching_repeat_end: string;
|
|
15
|
+
matching_repeat_remaining: string;
|
|
16
|
+
repeat_id: string;
|
|
17
|
+
} | undefined;
|
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
'percent_ins',
|
|
6
|
-
'query_chr',
|
|
7
|
-
'query_begin',
|
|
8
|
-
'query_end',
|
|
9
|
-
'query_remaining',
|
|
10
|
-
'orientation',
|
|
11
|
-
'matching_repeat_name',
|
|
12
|
-
'matching_repeat_class',
|
|
13
|
-
'matching_repeat_begin',
|
|
14
|
-
'matching_repeat_end',
|
|
15
|
-
'matching_repeat_remaining',
|
|
16
|
-
'repeat_id',
|
|
17
|
-
];
|
|
18
|
-
// RepeatMasker .out-derived description: 15 space-separated fields with numeric
|
|
19
|
-
// values at positions 0,1,2,3,5,6. Returns parsed fields or undefined if the
|
|
20
|
-
// description doesn't match the format.
|
|
1
|
+
const NUMERIC_PARTS = [0, 1, 2, 3, 5, 6];
|
|
2
|
+
// RepeatMasker .out-derived description: 15 space-separated fields, numeric at
|
|
3
|
+
// positions 0,1,2,3,5,6. A literal rather than an object built from the field
|
|
4
|
+
// list, since this runs on every line of a RepeatMasker track.
|
|
21
5
|
export function parseRepeatMaskerDescription(desc) {
|
|
22
|
-
if (typeof desc
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
6
|
+
if (typeof desc !== 'string') {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const p = desc.trim().split(' ');
|
|
10
|
+
for (const i of NUMERIC_PARTS) {
|
|
11
|
+
const v = p[i];
|
|
12
|
+
if (v === undefined || Number.isNaN(+v)) {
|
|
13
|
+
return undefined;
|
|
27
14
|
}
|
|
28
15
|
}
|
|
29
|
-
return
|
|
16
|
+
return {
|
|
17
|
+
bitsw_score: p[0] ?? '',
|
|
18
|
+
percent_div: p[1] ?? '',
|
|
19
|
+
percent_del: p[2] ?? '',
|
|
20
|
+
percent_ins: p[3] ?? '',
|
|
21
|
+
query_chr: p[4] ?? '',
|
|
22
|
+
query_begin: p[5] ?? '',
|
|
23
|
+
query_end: p[6] ?? '',
|
|
24
|
+
query_remaining: p[7] ?? '',
|
|
25
|
+
orientation: p[8] ?? '',
|
|
26
|
+
matching_repeat_name: p[9] ?? '',
|
|
27
|
+
matching_repeat_class: p[10] ?? '',
|
|
28
|
+
matching_repeat_begin: p[11] ?? '',
|
|
29
|
+
matching_repeat_end: p[12] ?? '',
|
|
30
|
+
matching_repeat_remaining: p[13] ?? '',
|
|
31
|
+
repeat_id: p[14] ?? '',
|
|
32
|
+
};
|
|
30
33
|
}
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
import { copyExcept } from './copyExcept.js';
|
|
2
|
+
// type guard: narrows input fields to required when true
|
|
2
3
|
// strand===0 means unstranded — likely not a gene
|
|
4
|
+
//
|
|
5
|
+
// Blocks are what make a transcript, and UCSC's own format definition calls
|
|
6
|
+
// them exons. Thickness says whether the transcript CODES, which is a separate
|
|
7
|
+
// axis: an lncRNA, a spliced EST and any other 12-column feature with
|
|
8
|
+
// thickStart===thickEnd is a transcript by every structural measure, so it is
|
|
9
|
+
// not tested here. `generateUcscTranscript` reads the same equality to emit
|
|
10
|
+
// exons and no CDS.
|
|
11
|
+
//
|
|
12
|
+
// This does conflate a spliced alignment with a transcript, deliberately: a
|
|
13
|
+
// bigPsl track (`est`, `intronEst`, `mrna`, `xenoMrna`, IsoSeq) has an empty
|
|
14
|
+
// thick range too, so it promotes where the same alignment as GFF3
|
|
15
|
+
// `cDNA_match` does not. BED12 carries no column that separates one from an
|
|
16
|
+
// lncRNA, and the promotion stays honest without one — no CDS children, so the
|
|
17
|
+
// sequence panel withholds CDS and protein and HGVS stays `n.`, and the type
|
|
18
|
+
// is `transcript` rather than `mRNA`.
|
|
3
19
|
export function isUcscTranscript(input) {
|
|
4
20
|
return (input.thickStart !== undefined &&
|
|
5
21
|
input.thickEnd !== undefined &&
|
|
6
|
-
input.thickStart !== input.thickEnd &&
|
|
7
22
|
input.blockCount !== undefined &&
|
|
8
23
|
input.blockCount > 0 &&
|
|
9
24
|
input.strand !== undefined &&
|
|
@@ -32,8 +47,26 @@ function parseFrames(frames) {
|
|
|
32
47
|
? frames.replace(/,$/, '').split(',').map(Number)
|
|
33
48
|
: frames;
|
|
34
49
|
}
|
|
50
|
+
const TRANSCRIPT_OWN = new Set([
|
|
51
|
+
'strand',
|
|
52
|
+
'uniqueId',
|
|
53
|
+
'start',
|
|
54
|
+
'end',
|
|
55
|
+
'thickStart',
|
|
56
|
+
'thickEnd',
|
|
57
|
+
'refName',
|
|
58
|
+
'subfeatures',
|
|
59
|
+
'chrom',
|
|
60
|
+
'chromStart',
|
|
61
|
+
'chromEnd',
|
|
62
|
+
'chromStarts',
|
|
63
|
+
'blockStarts',
|
|
64
|
+
'blockSizes',
|
|
65
|
+
'blockCount',
|
|
66
|
+
]);
|
|
35
67
|
export function generateUcscTranscript(data) {
|
|
36
|
-
const { strand, uniqueId, start, end, thickStart, thickEnd, refName, subfeatures: oldSubfeatures,
|
|
68
|
+
const { strand, uniqueId, start, end, thickStart, thickEnd, refName, subfeatures: oldSubfeatures, } = data;
|
|
69
|
+
const rest = copyExcept(data, TRANSCRIPT_OWN);
|
|
37
70
|
// exonFrames from bigGenePred - the @gmod/bed parser returns it in genomic order.
|
|
38
71
|
// _exonFrames fallback supports BED files that use the underscore-prefixed column
|
|
39
72
|
// name, which the parser leaves as raw text because it is not a standard column.
|
|
@@ -43,8 +76,22 @@ export function generateUcscTranscript(data) {
|
|
|
43
76
|
.sort((a, b) => a.start - b.start);
|
|
44
77
|
const fiveUTR = strand > 0 ? 'five_prime_UTR' : 'three_prime_UTR';
|
|
45
78
|
const threeUTR = strand > 0 ? 'three_prime_UTR' : 'five_prime_UTR';
|
|
79
|
+
// thickStart===thickEnd is UCSC for no coding region, and the block walk
|
|
80
|
+
// below cannot express that: a thick point inside a block would emit a
|
|
81
|
+
// zero-length CDS carrying a phase, and one outside every block would make
|
|
82
|
+
// the whole transcript UTR.
|
|
83
|
+
//
|
|
84
|
+
// cdsStartStat and cdsEndStat `none` alone do not mean non-coding: UCSC's
|
|
85
|
+
// GENCODE bigGenePred (gencodeV44.bb through V50) writes `none` on every
|
|
86
|
+
// transcript, and reading it as non-coding stripped the CDS from all 370,886
|
|
87
|
+
// of V50's coding ones, TP53 included. exonFrames tells them apart there:
|
|
88
|
+
// every coding transcript has a block with a frame, every other one has -1
|
|
89
|
+
// throughout.
|
|
46
90
|
const { cdsEndStat, cdsStartStat } = rest;
|
|
47
|
-
if (
|
|
91
|
+
if (thickStart === thickEnd ||
|
|
92
|
+
(cdsStartStat === 'none' &&
|
|
93
|
+
cdsEndStat === 'none' &&
|
|
94
|
+
!exonFrames?.some(frame => frame >= 0))) {
|
|
48
95
|
return {
|
|
49
96
|
...rest,
|
|
50
97
|
uniqueId,
|
package/esm/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import BedGraphTabixAdapterF from './BedGraphTabixAdapter/index.js';
|
|
|
5
5
|
import BedTabixAdapterF from './BedTabixAdapter/index.js';
|
|
6
6
|
import BedpeAdapterF from './BedpeAdapter/index.js';
|
|
7
7
|
import BigBedAdapterF from './BigBedAdapter/index.js';
|
|
8
|
+
import BigBedTextSearchAdapterF from './BigBedTextSearchAdapter/index.js';
|
|
8
9
|
import StarFusionAdapterF from './StarFusionAdapter/index.js';
|
|
9
10
|
export { default as BedTabixAdapter } from './BedTabixAdapter/BedTabixAdapter.js';
|
|
10
11
|
export { default as bedTabixConfigSchema } from './BedTabixAdapter/configSchema.js';
|
|
@@ -12,6 +13,7 @@ export default class BedPlugin extends Plugin {
|
|
|
12
13
|
name = 'BedPlugin';
|
|
13
14
|
install(pluginManager) {
|
|
14
15
|
BigBedAdapterF(pluginManager);
|
|
16
|
+
BigBedTextSearchAdapterF(pluginManager);
|
|
15
17
|
BedAdapterF(pluginManager);
|
|
16
18
|
BedpeAdapterF(pluginManager);
|
|
17
19
|
StarFusionAdapterF(pluginManager);
|
package/esm/util.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import BED from '@gmod/bed';
|
|
2
2
|
import { parseLineByLine } from '@jbrowse/core/util/parseLineByLine';
|
|
3
|
+
import { copyExcept } from './copyExcept.js';
|
|
3
4
|
import { generateBedMethylFeature, isBedMethylFeature, } from './generateBedMethylFeature.js';
|
|
4
5
|
import { generateRastairMethylFeature, isRastairMethylFeature, } from './generateRastairMethylFeature.js';
|
|
5
6
|
import { parseRepeatMaskerDescription } from './generateRepeatMaskerFeature.js';
|
|
@@ -99,6 +100,17 @@ export function parseStrand(strand) {
|
|
|
99
100
|
}
|
|
100
101
|
return 0;
|
|
101
102
|
}
|
|
103
|
+
const BED_OWN = new Set(['strand', 'score', 'chrom', 'chromStart', 'chromEnd']);
|
|
104
|
+
const REPEAT_OWN = new Set([
|
|
105
|
+
...BED_OWN,
|
|
106
|
+
'description',
|
|
107
|
+
'chromStarts',
|
|
108
|
+
'blockSizes',
|
|
109
|
+
'blockStarts',
|
|
110
|
+
'blockCount',
|
|
111
|
+
'thickStart',
|
|
112
|
+
'thickEnd',
|
|
113
|
+
]);
|
|
102
114
|
export function featureData({ splitLine, refName, start, end, parser, uniqueId, scoreColumn, names, disableGeneHeuristic, }) {
|
|
103
115
|
// bedMethyl detection runs on raw splitLine before parsing
|
|
104
116
|
if (isBedMethylFeature({ splitLine, start, end })) {
|
|
@@ -122,15 +134,13 @@ export function featureData({ splitLine, refName, start, end, parser, uniqueId,
|
|
|
122
134
|
});
|
|
123
135
|
}
|
|
124
136
|
const data = parser.parseLine(splitLine, { uniqueId });
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const rawScore = scoreColumn ? data[scoreColumn] : scoreRaw;
|
|
137
|
+
const strand = parseStrand(data.strand);
|
|
138
|
+
const rawScore = scoreColumn ? data[scoreColumn] : data.score;
|
|
128
139
|
const score = rawScore === undefined ? undefined : Number(rawScore);
|
|
129
|
-
const repeat = parseRepeatMaskerDescription(
|
|
140
|
+
const repeat = parseRepeatMaskerDescription(data.description);
|
|
130
141
|
if (repeat) {
|
|
131
|
-
const { description, chromStarts, blockSizes, blockStarts, blockCount, thickStart, thickEnd, ...rest2 } = rest;
|
|
132
142
|
return {
|
|
133
|
-
...
|
|
143
|
+
...copyExcept(data, REPEAT_OWN),
|
|
134
144
|
...repeat,
|
|
135
145
|
uniqueId,
|
|
136
146
|
score,
|
|
@@ -140,22 +150,23 @@ export function featureData({ splitLine, refName, start, end, parser, uniqueId,
|
|
|
140
150
|
refName,
|
|
141
151
|
};
|
|
142
152
|
}
|
|
143
|
-
const
|
|
153
|
+
const rest = copyExcept(data, BED_OWN);
|
|
154
|
+
const subfeatures = data.blockCount
|
|
144
155
|
? makeBlocks({
|
|
145
156
|
start,
|
|
146
157
|
uniqueId,
|
|
147
158
|
refName,
|
|
148
|
-
chromStarts:
|
|
149
|
-
blockCount:
|
|
150
|
-
blockSizes:
|
|
151
|
-
blockStarts:
|
|
159
|
+
chromStarts: data.chromStarts,
|
|
160
|
+
blockCount: data.blockCount,
|
|
161
|
+
blockSizes: data.blockSizes,
|
|
162
|
+
blockStarts: data.blockStarts,
|
|
152
163
|
})
|
|
153
164
|
: undefined;
|
|
154
165
|
const transcriptCheck = {
|
|
155
166
|
strand,
|
|
156
|
-
blockCount:
|
|
157
|
-
thickStart:
|
|
158
|
-
thickEnd:
|
|
167
|
+
blockCount: data.blockCount,
|
|
168
|
+
thickStart: data.thickStart,
|
|
169
|
+
thickEnd: data.thickEnd,
|
|
159
170
|
};
|
|
160
171
|
if (!disableGeneHeuristic &&
|
|
161
172
|
subfeatures &&
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-bed",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 bed adapters, tracks, etc.",
|
|
6
6
|
"keywords": [
|
|
@@ -21,13 +21,18 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@gmod/bbi": "^11.
|
|
25
|
-
"@gmod/bed": "^2.3.
|
|
26
|
-
"@gmod/tabix": "^3.
|
|
27
|
-
"@
|
|
28
|
-
"mobx": "^7.0
|
|
24
|
+
"@gmod/bbi": "^11.3.0",
|
|
25
|
+
"@gmod/bed": "^2.3.1",
|
|
26
|
+
"@gmod/tabix": "^3.9.1",
|
|
27
|
+
"@gmod/trix": "^3.0.22",
|
|
28
|
+
"@jbrowse/mobx-state-tree": "^6.7.0",
|
|
29
|
+
"mobx": "^7.0.5",
|
|
29
30
|
"rxjs": "^7.8.2",
|
|
30
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
31
|
+
"@jbrowse/core": "^5.0.0-beta.11"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=19.0.0",
|
|
35
|
+
"react-dom": ">=19.0.0"
|
|
31
36
|
},
|
|
32
37
|
"publishConfig": {
|
|
33
38
|
"access": "public"
|