@jbrowse/img 2.2.0 → 2.2.2

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/src/index.ts ADDED
@@ -0,0 +1,210 @@
1
+ import fs from 'fs'
2
+ import yargs from 'yargs'
3
+ import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
4
+ import fetch, { Headers, Response, Request } from 'node-fetch'
5
+ import { JSDOM } from 'jsdom'
6
+ import { Image, createCanvas } from 'canvas'
7
+
8
+ // locals
9
+ import { standardizeArgv, parseArgv } from './parseArgv'
10
+ import { renderRegion, Opts } from './renderRegion'
11
+ import { convert } from './util'
12
+
13
+ // @ts-ignore
14
+ global.nodeImage = Image
15
+ // @ts-ignore
16
+ global.nodeCreateCanvas = createCanvas
17
+
18
+ const { document } = new JSDOM(`...`).window
19
+ global.document = document
20
+
21
+ // force use of node-fetch polyfill, even if node 18+ fetch is available.
22
+ // native node 18+ fetch currently gives errors related to unidici and
23
+ // Uint8Array:
24
+ //
25
+ //
26
+ // % node --version
27
+ // v18.12.1
28
+ //
29
+ // % jb2export --fasta https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.fa --bam https://jbrowse.org/code/jb2/main/test_data/volvox/volvox-sorted.bam --loc ctgA:1-1000 --out out4.svg
30
+ // [
31
+ // '(node:1387934) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time\n' +
32
+ // '(Use `node --trace-warnings ...` to show where the warning was created)'
33
+ // ]
34
+ // [
35
+ // RangeError: offset is out of bounds
36
+ // at Uint8Array.set (<anonymous>)
37
+ // at Response.arrayBuffer (node:internal/deps/undici/undici:6117:23)
38
+ // at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
39
+ // ]
40
+
41
+ // @ts-ignore
42
+ global.fetch = fetch
43
+ // @ts-ignore
44
+ global.Headers = Headers
45
+ // @ts-ignore
46
+ global.Response = Response
47
+ // @ts-ignore
48
+ global.Request = Request
49
+
50
+ const err = console.error
51
+ console.error = (...p: unknown[]) => {
52
+ if (!`${p[0]}`.match('useLayoutEffect')) {
53
+ err(p)
54
+ }
55
+ }
56
+
57
+ const warn = console.warn
58
+ console.warn = (...p: unknown[]) => {
59
+ if (!`${p[0]}`.match('estimation reached timeout')) {
60
+ warn(p)
61
+ }
62
+ }
63
+
64
+ // note: yargs is actually unused except for printing help
65
+ // we do custom command line parsing, see parseArgv.ts
66
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
67
+ yargs
68
+ .command('jb2export', 'Creates a jbrowse 2 image snapshot')
69
+ .option('config', {
70
+ description: 'Path to config file',
71
+ type: 'string',
72
+ })
73
+ .option('session', {
74
+ description: 'Path to session file',
75
+ type: 'string',
76
+ })
77
+ .option('assembly', {
78
+ description:
79
+ 'Path to an assembly configuration, or a name of an assembly in the configFile',
80
+ type: 'string',
81
+ })
82
+ .option('tracks', {
83
+ description: 'Path to tracks portion of a session',
84
+ type: 'string',
85
+ })
86
+ .option('loc', {
87
+ description:
88
+ 'A locstring to navigate to, or --loc all to view the whole genome',
89
+ type: 'string',
90
+ })
91
+ .option('fasta', {
92
+ description: 'Supply a fasta for the assembly',
93
+ type: 'string',
94
+ })
95
+ .option('aliases', {
96
+ description:
97
+ 'Supply aliases for the assembly, e.g. mapping of 1 to chr1. Tab separated file where column 1 matches the names from the FASTA',
98
+ type: 'string',
99
+ })
100
+ .option('width', {
101
+ description:
102
+ 'Set the width of the window that jbrowse renders to, default: 1500px',
103
+ type: 'number',
104
+ })
105
+ .option('pngwidth', {
106
+ description:
107
+ 'Set the width of the png canvas if using png output, default 2048px',
108
+ type: 'number',
109
+ default: 2048,
110
+ })
111
+ // track types
112
+ .option('configtracks', {
113
+ description: 'A list of track labels from a config file',
114
+ type: 'array',
115
+ })
116
+ .option('bam', {
117
+ description:
118
+ 'A bam file, flag --bam can be used multiple times to specify multiple bam files',
119
+ type: 'array',
120
+ })
121
+ .option('bigwig', {
122
+ description:
123
+ 'A bigwig file, the --bigwig flag can be used multiple times to specify multiple bigwig files',
124
+ type: 'array',
125
+ })
126
+ .option('cram', {
127
+ description:
128
+ 'A cram file, the --cram flag can be used multiple times to specify multiple cram files',
129
+ type: 'array',
130
+ })
131
+ .option('vcfgz', {
132
+ description:
133
+ 'A tabixed VCF, the --vcfgz flag can be used multiple times to specify multiple vcfgz files',
134
+ type: 'array',
135
+ })
136
+ .option('gffgz', {
137
+ description:
138
+ 'A tabixed GFF, the --gffgz can be used multiple times to specify multiple gffgz files',
139
+ type: 'array',
140
+ })
141
+ .option('hic', {
142
+ description:
143
+ 'A .hic file, the --hic can be used multiple times to specify multiple hic files',
144
+ type: 'array',
145
+ })
146
+ .option('bigbed', {
147
+ description:
148
+ 'A .bigBed file, the --bigbed can be used multiple times to specify multiple bigbed files',
149
+ type: 'array',
150
+ })
151
+ .option('bedgz', {
152
+ description:
153
+ 'A bed tabix file, the --bedgz can be used multiple times to specify multiple bedtabix files',
154
+ type: 'array',
155
+ })
156
+
157
+ // other
158
+ .option('out', {
159
+ description:
160
+ 'File to output to. Default: out.svg. If a filename with extension .png is supplied the program will try to automatically execute rsvg-convert to convert it to png',
161
+ type: 'string',
162
+ default: 'out.svg',
163
+ })
164
+ .option('noRasterize', {
165
+ description:
166
+ 'Use full SVG rendering with no rasterized layers, this can substantially increase filesize',
167
+ type: 'boolean',
168
+ })
169
+ .option('defaultSession', {
170
+ description: 'Use the defaultSession from config.json',
171
+ type: 'boolean',
172
+ })
173
+ .help()
174
+ .alias('help', 'h')
175
+ .alias('width', 'w').argv
176
+
177
+ const args = standardizeArgv(parseArgv(process.argv.slice(2)), [
178
+ 'bam',
179
+ 'cram',
180
+ 'vcfgz',
181
+ 'hic',
182
+ 'bigwig',
183
+ 'bigbed',
184
+ 'bedgz',
185
+ 'gffgz',
186
+ 'configtracks',
187
+ ])
188
+
189
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
190
+ ;(async () => {
191
+ try {
192
+ const result = await renderRegion(args as Opts)
193
+ const { out = 'out.svg', pngwidth = '2048' } = args
194
+ if (out.endsWith('.png')) {
195
+ convert(result, { out, pngwidth })
196
+ } else if (out.endsWith('.pdf')) {
197
+ convert(result, { out, pngwidth }, ['-f', 'pdf'])
198
+ } else {
199
+ fs.writeFileSync(out, result)
200
+ }
201
+
202
+ // manually exit the process after done rendering because autoruns or
203
+ // something similar otherwise keeps the nodejs process alive xref
204
+ // https://github.com/GMOD/jb2export/issues/6
205
+ process.exit(0)
206
+ } catch (e) {
207
+ console.error(e)
208
+ process.exit(1)
209
+ }
210
+ })()
@@ -0,0 +1,29 @@
1
+ import { parseArgv } from './parseArgv'
2
+
3
+ test('parse', () => {
4
+ const args =
5
+ '--bam dad.bam color:red --vcf variants.vcf --bam mom.bam --defaultSession --out out.svg --noRasterize'
6
+
7
+ expect(parseArgv(args.split(' '))).toEqual([
8
+ ['bam', ['dad.bam', 'color:red']],
9
+ ['vcf', ['variants.vcf']],
10
+ ['bam', ['mom.bam']],
11
+ ['defaultSession', []],
12
+ ['out', ['out.svg']],
13
+ ['noRasterize', []],
14
+ ])
15
+ })
16
+
17
+ test('parse', () => {
18
+ const args =
19
+ '--bam dad.bam color:red --vcf variants.vcf --bam mom.bam force:true --defaultSession --out out.svg --noRasterize'
20
+
21
+ expect(parseArgv(args.split(' '))).toEqual([
22
+ ['bam', ['dad.bam', 'color:red']],
23
+ ['vcf', ['variants.vcf']],
24
+ ['bam', ['mom.bam', 'force:true']],
25
+ ['defaultSession', []],
26
+ ['out', ['out.svg']],
27
+ ['noRasterize', []],
28
+ ])
29
+ })
@@ -0,0 +1,48 @@
1
+ export type Entry = [string, string[]]
2
+
3
+ // example (see parseArgv.test.js):
4
+ // const args =
5
+ // '--bam dad.bam color:red --vcf variants.vcf --bam mom.bam --defaultSession --out out.svg --noRasterize'
6
+ //
7
+ // expect(parseArgv(args.split(' '))).toEqual([
8
+ // ['bam', ['dad.bam', 'color:red']],
9
+ // ['vcf', ['variants.vcf']],
10
+ // ['bam', ['mom.bam']],
11
+ // ['defaultSession', []],
12
+ // ['out', ['out.svg']],
13
+ // ['noRasterize', []],
14
+ // ])
15
+ export function parseArgv(argv: string[]) {
16
+ const map = [] as Entry[]
17
+ while (argv.length) {
18
+ const val = argv[0].slice(2)
19
+ argv = argv.slice(1)
20
+ const next = argv.findIndex(arg => arg.startsWith('-'))
21
+
22
+ if (next !== -1) {
23
+ map.push([val, argv.slice(0, next)])
24
+ argv = argv.slice(next)
25
+ } else {
26
+ map.push([val, argv])
27
+ break
28
+ }
29
+ }
30
+ return map
31
+ }
32
+
33
+ export function standardizeArgv(args: Entry[], trackTypes: string[]) {
34
+ const result = { trackList: [] } as {
35
+ trackList: Entry[]
36
+ out?: string
37
+ pngwidth?: string
38
+ [key: string]: unknown
39
+ }
40
+ args.forEach(arg => {
41
+ if (trackTypes.includes(arg[0])) {
42
+ result.trackList.push(arg)
43
+ } else {
44
+ result[arg[0]] = arg[1][0] || true
45
+ }
46
+ })
47
+ return result
48
+ }