@jbrowse/graph-walks 1.0.0 → 1.1.0
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/README.md +14 -6
- package/esm/index.d.ts +4 -3
- package/esm/index.js +4 -3
- package/esm/refNames.d.ts +12 -0
- package/esm/refNames.js +35 -0
- package/esm/util.d.ts +2 -0
- package/esm/util.js +13 -0
- package/esm/walkReader.d.ts +14 -0
- package/esm/walkReader.js +22 -1
- package/esm/walkRows.d.ts +6 -0
- package/esm/walkRows.js +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,11 +27,7 @@ npm install @jbrowse/graph-walks @gmod/tabix
|
|
|
27
27
|
|
|
28
28
|
```ts
|
|
29
29
|
import { TabixIndexedFile } from '@gmod/tabix'
|
|
30
|
-
import {
|
|
31
|
-
WalkReader,
|
|
32
|
-
panSNHaplotype,
|
|
33
|
-
walkNameFilter,
|
|
34
|
-
} from '@jbrowse/graph-walks'
|
|
30
|
+
import { WalkReader, walksKept } from '@jbrowse/graph-walks'
|
|
35
31
|
import { RemoteFile } from 'generic-filehandle2'
|
|
36
32
|
|
|
37
33
|
const url = 'https://example.org/hprc-v2.1-mc-grch38.GRCh38.walks.bed.gz'
|
|
@@ -46,7 +42,7 @@ const refName = 'GRCh38#0#chr6'
|
|
|
46
42
|
const { graph, kept, fragments } = await reader.cut(
|
|
47
43
|
refName,
|
|
48
44
|
{ start: 160_614_798, end: 160_647_758 },
|
|
49
|
-
{ keep:
|
|
45
|
+
{ keep: walksKept(['HG00097', 'HG00133'], refName) },
|
|
50
46
|
)
|
|
51
47
|
const tables = graph.tables(kept, fragments)
|
|
52
48
|
```
|
|
@@ -73,6 +69,18 @@ const tables = graph.tables(kept, fragments)
|
|
|
73
69
|
|
|
74
70
|
A cut over a budget throws a `RegionTooLargeError`. Its `fitsBp` holds about how
|
|
75
71
|
wide a window would fit, and JBrowse shows it as a zoom-in notice.
|
|
72
|
+
`WALK_BUDGETS` holds the step and byte budgets the JBrowse plugin and BandageJS
|
|
73
|
+
cut by.
|
|
74
|
+
|
|
75
|
+
## Naming the window
|
|
76
|
+
|
|
77
|
+
- `reader.sequenceFor(sample, refName)` gives the walk file's sequence for a
|
|
78
|
+
contig on a sample: `chr6` on `GRCh38` is `GRCh38#0#chr6`, and a pggb file's
|
|
79
|
+
`chm13#chr1` is `chr1` on `chm13`
|
|
80
|
+
- `walksKept(haplotypes, refName)` is the `keep` filter for a cut on `refName`:
|
|
81
|
+
its own walk and those of the haplotypes named
|
|
82
|
+
- `refNameLookup` and `resolveRefName` resolve a contig the same way against any
|
|
83
|
+
tabix file's sequence names
|
|
76
84
|
|
|
77
85
|
## How a cut works
|
|
78
86
|
|
package/esm/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, } from './walkRows.ts';
|
|
1
|
+
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, walksKept, } from './walkRows.ts';
|
|
2
2
|
export type { BudgetNames, WalkChunk, WalkFragment, WalkHeader, WalkNode, WalkRow, WalkRun, WalkWindow, } from './walkRows.ts';
|
|
3
|
-
export { WalkFormatError, WalkReader } from './walkReader.ts';
|
|
3
|
+
export { WALK_BUDGETS, WalkFormatError, WalkReader, checkRange, } from './walkReader.ts';
|
|
4
|
+
export { refNameLookup, resolveRefName } from './refNames.ts';
|
|
4
5
|
export { referenceNodes } from './walkChunk.ts';
|
|
5
6
|
export type { TabixSource, WalkCutOptions, WalkReaderHeader, } from './walkReader.ts';
|
|
6
|
-
export { RegionTooLargeError, bpString, panSNHaplotype, panSNMatchesPrefix, } from './util.ts';
|
|
7
|
+
export { RegionTooLargeError, bpString, panSNContig, panSNHaplotype, panSNMatchesPrefix, panSNSample, } from './util.ts';
|
|
7
8
|
export type { GraphTables } from './util.ts';
|
package/esm/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, } from "./walkRows.js";
|
|
2
|
-
export { WalkFormatError, WalkReader } from "./walkReader.js";
|
|
1
|
+
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, walksKept, } from "./walkRows.js";
|
|
2
|
+
export { WALK_BUDGETS, WalkFormatError, WalkReader, checkRange, } from "./walkReader.js";
|
|
3
|
+
export { refNameLookup, resolveRefName } from "./refNames.js";
|
|
3
4
|
export { referenceNodes } from "./walkChunk.js";
|
|
4
|
-
export { RegionTooLargeError, bpString, panSNHaplotype, panSNMatchesPrefix, } from "./util.js";
|
|
5
|
+
export { RegionTooLargeError, bpString, panSNContig, panSNHaplotype, panSNMatchesPrefix, panSNSample, } from "./util.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A file's sequence names under each spelling a caller may ask by: the file's
|
|
3
|
+
* own (`GRCh38#0#chr6`), and a PanSN name's contig under its sample (`chr6`
|
|
4
|
+
* on `GRCh38`) and under its haplotype (`chr6` on `GRCh38#0`). A pggb name
|
|
5
|
+
* with no haplotype field, `chm13#chr1`, is `chr1` on `chm13`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function refNameLookup(names: string[]): Map<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* The file's name for `refName` on `assemblyName`, a sample or a haplotype,
|
|
10
|
+
* else for `refName` as the file spells it; undefined when it holds neither
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveRefName(lookup: Map<string, string>, assemblyName: string, refName: string): string | undefined;
|
package/esm/refNames.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { panSNContig, panSNHaplotype, panSNSample } from "./util.js";
|
|
2
|
+
// A tab can occur in neither half: both come out of BED columns
|
|
3
|
+
function qualifiedKey(assemblyName, refName) {
|
|
4
|
+
return `${assemblyName}\t${refName}`;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A file's sequence names under each spelling a caller may ask by: the file's
|
|
8
|
+
* own (`GRCh38#0#chr6`), and a PanSN name's contig under its sample (`chr6`
|
|
9
|
+
* on `GRCh38`) and under its haplotype (`chr6` on `GRCh38#0`). A pggb name
|
|
10
|
+
* with no haplotype field, `chm13#chr1`, is `chr1` on `chm13`.
|
|
11
|
+
*/
|
|
12
|
+
export function refNameLookup(names) {
|
|
13
|
+
const lookup = new Map();
|
|
14
|
+
for (const name of names) {
|
|
15
|
+
lookup.set(name, name);
|
|
16
|
+
const sample = panSNSample(name);
|
|
17
|
+
if (sample !== name) {
|
|
18
|
+
const contig = panSNContig(name);
|
|
19
|
+
lookup.set(qualifiedKey(sample, contig), name);
|
|
20
|
+
const haplotype = panSNHaplotype(name);
|
|
21
|
+
if (haplotype !== undefined) {
|
|
22
|
+
lookup.set(qualifiedKey(haplotype, contig), name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return lookup;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The file's name for `refName` on `assemblyName`, a sample or a haplotype,
|
|
30
|
+
* else for `refName` as the file spells it; undefined when it holds neither
|
|
31
|
+
*/
|
|
32
|
+
export function resolveRefName(lookup, assemblyName, refName) {
|
|
33
|
+
var _a;
|
|
34
|
+
return (_a = lookup.get(qualifiedKey(assemblyName, refName))) !== null && _a !== void 0 ? _a : lookup.get(refName);
|
|
35
|
+
}
|
package/esm/util.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export declare class RegionTooLargeError extends Error {
|
|
|
34
34
|
readonly fitsBp: number;
|
|
35
35
|
constructor(message: string, limit: number, windowBp: number, fitsBp: number);
|
|
36
36
|
}
|
|
37
|
+
export declare function panSNSample(refName: string): string;
|
|
38
|
+
export declare function panSNContig(refName: string): string;
|
|
37
39
|
export declare function panSNHaplotype(refName: string): string | undefined;
|
|
38
40
|
export declare function panSNMatchesPrefix(refName: string, prefix: string | undefined): boolean;
|
|
39
41
|
export declare function bpString(bp: number): string;
|
package/esm/util.js
CHANGED
|
@@ -14,6 +14,19 @@ export class RegionTooLargeError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
const SEP = '#';
|
|
17
|
+
// `grape#1#chr1` -> `grape`
|
|
18
|
+
export function panSNSample(refName) {
|
|
19
|
+
return refName.split(SEP)[0];
|
|
20
|
+
}
|
|
21
|
+
// `grape#1#chr1` and `grape#chr1` -> `chr1`
|
|
22
|
+
export function panSNContig(refName) {
|
|
23
|
+
const parts = refName.split(SEP);
|
|
24
|
+
return parts.length >= 3
|
|
25
|
+
? parts.slice(2).join(SEP)
|
|
26
|
+
: parts.length === 2
|
|
27
|
+
? parts[1]
|
|
28
|
+
: refName;
|
|
29
|
+
}
|
|
17
30
|
export function panSNHaplotype(refName) {
|
|
18
31
|
const parts = refName.split(SEP);
|
|
19
32
|
return parts.length >= 3 ? `${parts[0]}${SEP}${parts[1]}` : undefined;
|
package/esm/walkReader.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { WalkGraph } from './walkRows.ts';
|
|
2
2
|
import type { BudgetNames, WalkHeader } from './walkRows.ts';
|
|
3
|
+
export declare const WALK_BUDGETS: {
|
|
4
|
+
stepBudget: number;
|
|
5
|
+
byteBudget: number;
|
|
6
|
+
};
|
|
3
7
|
/**
|
|
4
8
|
* A Tabix-indexed file as the reader uses one: @gmod/tabix's
|
|
5
9
|
* TabixIndexedFile is one. `bytesForRegions` estimates the compressed bytes a
|
|
@@ -27,6 +31,7 @@ export interface TabixSource {
|
|
|
27
31
|
export declare class WalkFormatError extends Error {
|
|
28
32
|
name: string;
|
|
29
33
|
}
|
|
34
|
+
export declare function checkRange(refName: string, start: number, end: number): void;
|
|
30
35
|
export interface WalkCutOptions {
|
|
31
36
|
signal?: AbortSignal;
|
|
32
37
|
keep?: (name: string) => boolean;
|
|
@@ -52,6 +57,7 @@ export type WalkReaderHeader = WalkHeader & {
|
|
|
52
57
|
*/
|
|
53
58
|
export declare class WalkReader {
|
|
54
59
|
private headerRead;
|
|
60
|
+
private lookupRead;
|
|
55
61
|
private readonly walks;
|
|
56
62
|
constructor(walks: TabixSource);
|
|
57
63
|
header(opts?: {
|
|
@@ -60,6 +66,14 @@ export declare class WalkReader {
|
|
|
60
66
|
referenceSequences(opts?: {
|
|
61
67
|
signal?: AbortSignal;
|
|
62
68
|
}): Promise<string[]>;
|
|
69
|
+
/**
|
|
70
|
+
* The walk file's sequence for `refName` on `sample`, or for `refName` as
|
|
71
|
+
* the file spells it: `chr6` on `GRCh38` is `GRCh38#0#chr6`. Undefined when
|
|
72
|
+
* the file holds neither.
|
|
73
|
+
*/
|
|
74
|
+
sequenceFor(sample: string, refName: string, opts?: {
|
|
75
|
+
signal?: AbortSignal;
|
|
76
|
+
}): Promise<string | undefined>;
|
|
63
77
|
queryStart(start: number, opts?: {
|
|
64
78
|
signal?: AbortSignal;
|
|
65
79
|
}): Promise<number>;
|
package/esm/walkReader.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { refNameLookup, resolveRefName } from "./refNames.js";
|
|
1
2
|
import { decodeChunk, pieceLinks } from "./walkChunk.js";
|
|
2
3
|
import { WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, } from "./walkRows.js";
|
|
3
4
|
// A window reads whole chunks and keeps the steps on it, so a narrow window
|
|
@@ -12,6 +13,8 @@ const FOLLOW_ROUNDS = 3;
|
|
|
12
13
|
const FOLLOW_CHUNKS = 4;
|
|
13
14
|
// bp of reference a cut keeps past each end of the window
|
|
14
15
|
const WALK_CONTEXT = 1000;
|
|
16
|
+
// The budgets the JBrowse graph plugin and BandageJS cut a browser's window by
|
|
17
|
+
export const WALK_BUDGETS = { stepBudget: 4000000, byteBudget: 8000000 };
|
|
15
18
|
// A walk file gfa-to-tabix 0.12 or earlier wrote, which the reader refuses
|
|
16
19
|
export class WalkFormatError extends Error {
|
|
17
20
|
constructor() {
|
|
@@ -21,7 +24,7 @@ export class WalkFormatError extends Error {
|
|
|
21
24
|
}
|
|
22
25
|
// @gmod/tabix loops forever on a NaN end and reads nothing for a NaN start
|
|
23
26
|
// (GMOD/tabix-js#157), so every query refuses a range that is not finite first
|
|
24
|
-
function checkRange(refName, start, end) {
|
|
27
|
+
export function checkRange(refName, start, end) {
|
|
25
28
|
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
26
29
|
throw new Error(`${refName}:${start}-${end} is not a finite range to query the graph's index for`);
|
|
27
30
|
}
|
|
@@ -87,6 +90,24 @@ export class WalkReader {
|
|
|
87
90
|
referenceSequences(opts = {}) {
|
|
88
91
|
return this.walks.getReferenceSequenceNames(opts);
|
|
89
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* The walk file's sequence for `refName` on `sample`, or for `refName` as
|
|
95
|
+
* the file spells it: `chr6` on `GRCh38` is `GRCh38#0#chr6`. Undefined when
|
|
96
|
+
* the file holds neither.
|
|
97
|
+
*/
|
|
98
|
+
async sequenceFor(sample, refName, opts = {}) {
|
|
99
|
+
if (!this.lookupRead) {
|
|
100
|
+
const read = this.walks.getReferenceSequenceNames().then(refNameLookup);
|
|
101
|
+
read.catch(() => {
|
|
102
|
+
if (this.lookupRead === read) {
|
|
103
|
+
this.lookupRead = undefined;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
this.lookupRead = read;
|
|
107
|
+
}
|
|
108
|
+
const lookup = await raceAbort(this.lookupRead, opts.signal);
|
|
109
|
+
return resolveRefName(lookup, sample, refName);
|
|
110
|
+
}
|
|
90
111
|
async queryStart(start, opts = {}) {
|
|
91
112
|
const { chunk, lookback } = await this.header(opts);
|
|
92
113
|
return chunkQueryStart(start, chunk, lookback);
|
package/esm/walkRows.d.ts
CHANGED
|
@@ -58,6 +58,12 @@ export declare function walkHeader(lines: string[]): WalkHeader;
|
|
|
58
58
|
export declare function lookbackChunks(maxNode: number | undefined, chunk: number): number;
|
|
59
59
|
export declare function chunkQueryStart(start: number, chunk: number, lookback?: number): number;
|
|
60
60
|
export declare function walkNameFilter(wanted: string[] | undefined, referenceHaplotype: string | undefined): ((name: string) => boolean) | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* The walks a cut on `refName` decodes: its own and those `wanted` names. A
|
|
63
|
+
* pggb P-line names a reference with no haplotype field (`chm13#chr1`), so the
|
|
64
|
+
* filter matches the reference by its whole name.
|
|
65
|
+
*/
|
|
66
|
+
export declare function walksKept(wanted: string[] | undefined, refName: string): ((name: string) => boolean) | undefined;
|
|
61
67
|
export interface WalkRun {
|
|
62
68
|
name: string;
|
|
63
69
|
hapOffset: number;
|
package/esm/walkRows.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RegionTooLargeError, bpString, panSNMatchesPrefix } from "./util.js";
|
|
1
|
+
import { RegionTooLargeError, bpString, panSNHaplotype, panSNMatchesPrefix, } from "./util.js";
|
|
2
2
|
import { placeAndLink } from "./walkChunk.js";
|
|
3
3
|
function integerTag(line, tag) {
|
|
4
4
|
const match = new RegExp(`(?:^|\\t)${tag}:i:(\\d+)`).exec(line);
|
|
@@ -53,6 +53,15 @@ export function walkNameFilter(wanted, referenceHaplotype) {
|
|
|
53
53
|
: (name) => panSNMatchesPrefix(name, referenceHaplotype) ||
|
|
54
54
|
wanted.some(prefix => panSNMatchesPrefix(name, prefix));
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The walks a cut on `refName` decodes: its own and those `wanted` names. A
|
|
58
|
+
* pggb P-line names a reference with no haplotype field (`chm13#chr1`), so the
|
|
59
|
+
* filter matches the reference by its whole name.
|
|
60
|
+
*/
|
|
61
|
+
export function walksKept(wanted, refName) {
|
|
62
|
+
var _a;
|
|
63
|
+
return walkNameFilter(wanted, (_a = panSNHaplotype(refName)) !== null && _a !== void 0 ? _a : refName);
|
|
64
|
+
}
|
|
56
65
|
export function joinPieces(rows) {
|
|
57
66
|
const byPath = new Map();
|
|
58
67
|
for (const row of rows) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/graph-walks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Read gfa-to-tabix walk files by range requests and cut a window of a pangenome graph: the reference, every haplotype's walk through it, and the nodes and links they visit",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|