comparison jbrowse2.py @ 0:d78175596286 draft

planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/master/tools/jbrowse2 commit cd77dffaad652cfb75b98bde5231beaa6d63cd5b-dirty
author fubar
date Mon, 08 Jan 2024 09:20:33 +0000
parents
children 2de9f585505b
comparison
equal deleted inserted replaced
-1:000000000000 0:d78175596286
1 #!/usr/bin/env python
2 # change to accumulating all configuration for config.json based on the default from the clone
3 import argparse
4 import binascii
5 import datetime
6 import hashlib
7 import json
8 import logging
9 import os
10 import re
11 import shutil
12 import struct
13 import subprocess
14 import tempfile
15 import xml.etree.ElementTree as ET
16 from collections import defaultdict
17
18 logging.basicConfig(level=logging.INFO)
19 log = logging.getLogger("jbrowse")
20 TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
21 GALAXY_INFRASTRUCTURE_URL = None
22 mapped_chars = {
23 ">": "__gt__",
24 "<": "__lt__",
25 "'": "__sq__",
26 '"': "__dq__",
27 "[": "__ob__",
28 "]": "__cb__",
29 "{": "__oc__",
30 "}": "__cc__",
31 "@": "__at__",
32 "#": "__pd__",
33 "": "__cn__",
34 }
35
36
37 class ColorScaling(object):
38
39 COLOR_FUNCTION_TEMPLATE = """
40 function(feature, variableName, glyphObject, track) {{
41 var score = {score};
42 {opacity}
43 return 'rgba({red}, {green}, {blue}, ' + opacity + ')';
44 }}
45 """
46
47 COLOR_FUNCTION_TEMPLATE_QUAL = r"""
48 function(feature, variableName, glyphObject, track) {{
49 var search_up = function self(sf, attr){{
50 if(sf.get(attr) !== undefined){{
51 return sf.get(attr);
52 }}
53 if(sf.parent() === undefined) {{
54 return;
55 }}else{{
56 return self(sf.parent(), attr);
57 }}
58 }};
59
60 var search_down = function self(sf, attr){{
61 if(sf.get(attr) !== undefined){{
62 return sf.get(attr);
63 }}
64 if(sf.children() === undefined) {{
65 return;
66 }}else{{
67 var kids = sf.children();
68 for(var child_idx in kids){{
69 var x = self(kids[child_idx], attr);
70 if(x !== undefined){{
71 return x;
72 }}
73 }}
74 return;
75 }}
76 }};
77
78 var color = ({user_spec_color} || search_up(feature, 'color') || search_down(feature, 'color') || {auto_gen_color});
79 var score = (search_up(feature, 'score') || search_down(feature, 'score'));
80 {opacity}
81 if(score === undefined){{ opacity = 1; }}
82 var result = /^#?([a-f\d]{{2}})([a-f\d]{{2}})([a-f\d]{{2}})$/i.exec(color);
83 var red = parseInt(result[1], 16);
84 var green = parseInt(result[2], 16);
85 var blue = parseInt(result[3], 16);
86 if(isNaN(opacity) || opacity < 0){{ opacity = 0; }}
87 return 'rgba(' + red + ',' + green + ',' + blue + ',' + opacity + ')';
88 }}
89 """
90
91 OPACITY_MATH = {
92 "linear": """
93 var opacity = (score - ({min})) / (({max}) - ({min}));
94 """,
95 "logarithmic": """
96 var opacity = Math.log10(score - ({min})) / Math.log10(({max}) - ({min}));
97 """,
98 "blast": """
99 var opacity = 0;
100 if(score == 0.0) {{
101 opacity = 1;
102 }} else {{
103 opacity = (20 - Math.log10(score)) / 180;
104 }}
105 """,
106 }
107
108 BREWER_COLOUR_IDX = 0
109 BREWER_COLOUR_SCHEMES = [
110 (166, 206, 227),
111 (31, 120, 180),
112 (178, 223, 138),
113 (51, 160, 44),
114 (251, 154, 153),
115 (227, 26, 28),
116 (253, 191, 111),
117 (255, 127, 0),
118 (202, 178, 214),
119 (106, 61, 154),
120 (255, 255, 153),
121 (177, 89, 40),
122 (228, 26, 28),
123 (55, 126, 184),
124 (77, 175, 74),
125 (152, 78, 163),
126 (255, 127, 0),
127 ]
128
129 BREWER_DIVERGING_PALLETES = {
130 "BrBg": ("#543005", "#003c30"),
131 "PiYg": ("#8e0152", "#276419"),
132 "PRGn": ("#40004b", "#00441b"),
133 "PuOr": ("#7f3b08", "#2d004b"),
134 "RdBu": ("#67001f", "#053061"),
135 "RdGy": ("#67001f", "#1a1a1a"),
136 "RdYlBu": ("#a50026", "#313695"),
137 "RdYlGn": ("#a50026", "#006837"),
138 "Spectral": ("#9e0142", "#5e4fa2"),
139 }
140
141 def __init__(self):
142 self.brewer_colour_idx = 0
143
144 def rgb_from_hex(self, hexstr):
145 # http://stackoverflow.com/questions/4296249/how-do-i-convert-a-hex-triplet-to-an-rgb-tuple-and-back
146 return struct.unpack("BBB", binascii.unhexlify(hexstr))
147
148 def min_max_gff(self, gff_file):
149 min_val = None
150 max_val = None
151 with open(gff_file, "r") as handle:
152 for line in handle:
153 try:
154 value = float(line.split("\t")[5])
155 min_val = min(value, (min_val or value))
156 max_val = max(value, (max_val or value))
157
158 if value < min_val:
159 min_val = value
160
161 if value > max_val:
162 max_val = value
163 except Exception:
164 pass
165 return min_val, max_val
166
167 def hex_from_rgb(self, r, g, b):
168 return "#%02x%02x%02x" % (r, g, b)
169
170 def _get_colours(self):
171 r, g, b = self.BREWER_COLOUR_SCHEMES[
172 self.brewer_colour_idx % len(self.BREWER_COLOUR_SCHEMES)
173 ]
174 self.brewer_colour_idx += 1
175 return r, g, b
176
177 def parse_menus(self, track):
178 trackConfig = {"menuTemplate": [{}, {}, {}, {}]}
179
180 if "menu" in track["menus"]:
181 menu_list = [track["menus"]["menu"]]
182 if isinstance(track["menus"]["menu"], list):
183 menu_list = track["menus"]["menu"]
184
185 for m in menu_list:
186 tpl = {
187 "action": m["action"],
188 "label": m.get("label", "{name}"),
189 "iconClass": m.get("iconClass", "dijitIconBookmark"),
190 }
191 if "url" in m:
192 tpl["url"] = m["url"]
193 if "content" in m:
194 tpl["content"] = m["content"]
195 if "title" in m:
196 tpl["title"] = m["title"]
197
198 trackConfig["menuTemplate"].append(tpl)
199
200 return trackConfig
201
202 def parse_colours(self, track, trackFormat, gff3=None):
203 # Wiggle tracks have a bicolor pallete
204 trackConfig = {"style": {}}
205 if trackFormat == "wiggle":
206
207 trackConfig["style"]["pos_color"] = track["wiggle"]["color_pos"]
208 trackConfig["style"]["neg_color"] = track["wiggle"]["color_neg"]
209
210 if trackConfig["style"]["pos_color"] == "__auto__":
211 trackConfig["style"]["neg_color"] = self.hex_from_rgb(
212 *self._get_colours()
213 )
214 trackConfig["style"]["pos_color"] = self.hex_from_rgb(
215 *self._get_colours()
216 )
217
218 # Wiggle tracks can change colour at a specified place
219 bc_pivot = track["wiggle"]["bicolor_pivot"]
220 if bc_pivot not in ("mean", "zero"):
221 # The values are either one of those two strings
222 # or a number
223 bc_pivot = float(bc_pivot)
224 trackConfig["bicolor_pivot"] = bc_pivot
225 elif "scaling" in track:
226 if track["scaling"]["method"] == "ignore":
227 if track["scaling"]["scheme"]["color"] != "__auto__":
228 trackConfig["style"]["color"] = track["scaling"]["scheme"]["color"]
229 else:
230 trackConfig["style"]["color"] = self.hex_from_rgb(
231 *self._get_colours()
232 )
233 else:
234 # Scored method
235 algo = track["scaling"]["algo"]
236 # linear, logarithmic, blast
237 scales = track["scaling"]["scales"]
238 # type __auto__, manual (min, max)
239 scheme = track["scaling"]["scheme"]
240 # scheme -> (type (opacity), color)
241 # ==================================
242 # GENE CALLS OR BLAST
243 # ==================================
244 if trackFormat == "blast":
245 red, green, blue = self._get_colours()
246 color_function = self.COLOR_FUNCTION_TEMPLATE.format(
247 **{
248 "score": "feature._parent.get('score')",
249 "opacity": self.OPACITY_MATH["blast"],
250 "red": red,
251 "green": green,
252 "blue": blue,
253 }
254 )
255 trackConfig["style"]["color"] = color_function.replace("\n", "")
256 elif trackFormat == "gene_calls":
257 # Default values, based on GFF3 spec
258 min_val = 0
259 max_val = 1000
260 # Get min/max and build a scoring function since JBrowse doesn't
261 if scales["type"] == "automatic" or scales["type"] == "__auto__":
262 min_val, max_val = self.min_max_gff(gff3)
263 else:
264 min_val = scales.get("min", 0)
265 max_val = scales.get("max", 1000)
266
267 if scheme["color"] == "__auto__":
268 user_color = "undefined"
269 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours())
270 elif scheme["color"].startswith("#"):
271 user_color = "'%s'" % self.hex_from_rgb(
272 *self.rgb_from_hex(scheme["color"][1:])
273 )
274 auto_color = "undefined"
275 else:
276 user_color = "undefined"
277 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours())
278
279 color_function = self.COLOR_FUNCTION_TEMPLATE_QUAL.format(
280 **{
281 "opacity": self.OPACITY_MATH[algo].format(
282 **{"max": max_val, "min": min_val}
283 ),
284 "user_spec_color": user_color,
285 "auto_gen_color": auto_color,
286 }
287 )
288
289 trackConfig["style"]["color"] = color_function.replace("\n", "")
290 return trackConfig
291
292
293 def etree_to_dict(t):
294 if t is None:
295 return {}
296
297 d = {t.tag: {} if t.attrib else None}
298 children = list(t)
299 if children:
300 dd = defaultdict(list)
301 for dc in map(etree_to_dict, children):
302 for k, v in dc.items():
303 dd[k].append(v)
304 d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
305 if t.attrib:
306 d[t.tag].update(("@" + k, v) for k, v in t.attrib.items())
307 if t.text:
308 text = t.text.strip()
309 if children or t.attrib:
310 if text:
311 d[t.tag]["#text"] = text
312 else:
313 d[t.tag] = text
314 return d
315
316
317 INSTALLED_TO = os.path.dirname(os.path.realpath(__file__))
318
319
320 def metadata_from_node(node):
321 metadata = {}
322 try:
323 if len(node.findall("dataset")) != 1:
324 # exit early
325 return metadata
326 except Exception:
327 return {}
328
329 for (key, value) in node.findall("dataset")[0].attrib.items():
330 metadata["dataset_%s" % key] = value
331
332 for (key, value) in node.findall("history")[0].attrib.items():
333 metadata["history_%s" % key] = value
334
335 for (key, value) in node.findall("metadata")[0].attrib.items():
336 metadata["metadata_%s" % key] = value
337
338 for (key, value) in node.findall("tool")[0].attrib.items():
339 metadata["tool_%s" % key] = value
340
341 # Additional Mappings applied:
342 metadata[
343 "dataset_edam_format"
344 ] = '<a target="_blank" href="http://edamontology.org/{0}">{1}</a>'.format(
345 metadata["dataset_edam_format"], metadata["dataset_file_ext"]
346 )
347 metadata["history_user_email"] = '<a href="mailto:{0}">{0}</a>'.format(
348 metadata["history_user_email"]
349 )
350 metadata["hist_name"] = metadata["history_display_name"]
351 metadata[
352 "history_display_name"
353 ] = '<a target="_blank" href="{galaxy}/history/view/{encoded_hist_id}">{hist_name}</a>'.format(
354 galaxy=GALAXY_INFRASTRUCTURE_URL,
355 encoded_hist_id=metadata["history_id"],
356 hist_name=metadata["history_display_name"],
357 )
358 metadata[
359 "tool_tool"
360 ] = '<a target="_blank" href="{galaxy}/datasets/{encoded_id}/show_params">{tool_id}</a>'.format(
361 galaxy=GALAXY_INFRASTRUCTURE_URL,
362 encoded_id=metadata["dataset_id"],
363 tool_id=metadata["tool_tool_id"],
364 # tool_version=metadata['tool_tool_version'],
365 )
366 return metadata
367
368
369 class JbrowseConnector(object):
370 def __init__(self, outdir, genomes):
371 self.debug = False
372 self.usejson = True
373 self.giURL = GALAXY_INFRASTRUCTURE_URL
374 self.outdir = outdir
375 os.makedirs(self.outdir, exist_ok=True)
376 self.genome_paths = genomes
377 self.genome_name = None
378 self.genome_names = []
379 self.trackIdlist = []
380 self.tracksToAdd = []
381 self.config_json = {}
382 self.config_json_file = os.path.join(outdir, "config.json")
383 self.clone_jbrowse()
384
385 def subprocess_check_call(self, command, output=None):
386 if output:
387 if self.debug:
388 log.debug("cd %s && %s > %s", self.outdir, " ".join(command), output)
389 subprocess.check_call(command, cwd=self.outdir, stdout=output)
390 else:
391 log.debug("cd %s && %s", self.outdir, " ".join(command))
392 subprocess.check_call(command, cwd=self.outdir)
393
394 def subprocess_popen(self, command):
395 if self.debug:
396 log.debug(command)
397 p = subprocess.Popen(
398 command,
399 cwd=self.outdir,
400 shell=True,
401 stdin=subprocess.PIPE,
402 stdout=subprocess.PIPE,
403 stderr=subprocess.PIPE,
404 )
405 output, err = p.communicate()
406 retcode = p.returncode
407 if retcode != 0:
408 log.error(command)
409 log.error(output)
410 log.error(err)
411 raise RuntimeError("Command failed with exit code %s" % (retcode))
412
413 def subprocess_check_output(self, command):
414 if self.debug:
415 log.debug(" ".join(command))
416 return subprocess.check_output(command, cwd=self.outdir)
417
418 def symlink_or_copy(self, src, dest):
419 if "GALAXY_JBROWSE_SYMLINKS" in os.environ and bool(
420 os.environ["GALAXY_JBROWSE_SYMLINKS"]
421 ):
422 cmd = ["ln", "-s", src, dest]
423 else:
424 cmd = ["cp", src, dest]
425
426 return self.subprocess_check_call(cmd)
427
428 def _prepare_track_style(self, trackDict):
429
430 style_data = {
431 "type": "LinearBasicDisplay",
432 "displayId": "%s-LinearBasicDisplay" % trackDict["trackId"],
433 }
434
435 if trackDict.get("displays", None):
436 style_data["type"] = trackDict["displays"]["type"]
437 style_data["displayId"] = trackDict["displays"]["displayId"]
438 return {"displays": [style_data]}
439
440 def process_genomes(self):
441 assemblies = []
442 for i, genome_node in enumerate(self.genome_paths):
443 if self.debug:
444 log.info("genome_node=%s" % str(genome_node))
445 genome_name = genome_node["meta"]["dataset_dname"].strip()
446 if len(genome_name.split()) > 1:
447 genome_name = genome_name.split()[0]
448 # spaces and cruft break scripts when substituted
449 fapath = genome_node["path"]
450 assem = self.make_assembly(fapath, genome_name)
451 assemblies.append(assem)
452 self.genome_names.append(genome_name)
453 if self.genome_name is None:
454 self.genome_name = genome_name # first one for all tracks - other than paf
455 if self.config_json.get("assemblies", None):
456 self.config_json["assemblies"] += assemblies
457 else:
458 self.config_json["assemblies"] = assemblies
459
460 def make_assembly(self, fapath, gname):
461 faname = gname + ".fa.gz"
462 fadest = os.path.join(self.outdir, faname)
463 # fadest = os.path.realpath(os.path.join(self.outdir, faname))
464 cmd = "bgzip -i -c %s -I %s.gzi > %s && samtools faidx %s" % (
465 fapath,
466 fadest,
467 fadest,
468 fadest,
469 )
470 self.subprocess_popen(cmd)
471 adapter = {
472 "type": "BgzipFastaAdapter",
473 "fastaLocation": {
474 "uri": faname,
475 },
476 "faiLocation": {
477 "uri": faname + ".fai",
478 },
479 "gziLocation": {
480 "uri": faname + ".gzi",
481 },
482 }
483 trackDict = {
484 "name": gname,
485 "sequence": {
486 "type": "ReferenceSequenceTrack",
487 "trackId": gname,
488 "adapter": adapter,
489 },
490 "rendering": {"type": "DivSequenceRenderer"},
491 }
492 return trackDict
493
494 def add_default_view(self):
495 cmd = [
496 "jbrowse",
497 "set-default-session",
498 "-s",
499 self.config_json_file,
500 "-t",
501 ",".join(self.trackIdlist),
502 "-n",
503 "JBrowse2 in Galaxy",
504 "--target",
505 self.config_json_file,
506 "-v",
507 " LinearGenomeView",
508 ]
509 if self.debug:
510 log.info("### calling set-default-session with cmd=%s" % " ".join(cmd))
511 self.subprocess_check_call(cmd)
512
513 def write_config(self):
514 with open(self.config_json_file, "w") as fp:
515 json.dump(self.config_json, fp)
516
517 def text_index(self):
518 # Index tracks
519 args = [
520 "jbrowse",
521 "text-index",
522 "--target",
523 os.path.join(self.outdir, "data"),
524 "--assemblies",
525 self.genome_name,
526 ]
527
528 tracks = ",".join(self.trackIdlist)
529 if tracks:
530 args += ["--tracks", tracks]
531
532 self.subprocess_check_call(args)
533
534 def add_hic(self, data, trackData):
535 """
536 HiC adapter.
537 https://github.com/aidenlab/hic-format/blob/master/HiCFormatV9.md
538 for testing locally, these work:
539 HiC data is from https://s3.amazonaws.com/igv.broadinstitute.org/data/hic/intra_nofrag_30.hic
540 using hg19 reference track as a
541 'BgzipFastaAdapter'
542 fastaLocation:
543 uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/fasta/GRCh38.fa.gz',
544 faiLocation:
545 uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/fasta/GRCh38.fa.gz.fai',
546 gziLocation:
547 uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/fasta/GRCh38.fa.gz.gzi',
548 Cool will not be likely to be a good fit - see discussion at https://github.com/GMOD/jbrowse-components/issues/2438
549 """
550 log.info("#### trackData=%s" % trackData)
551 tId = trackData["label"]
552 # can be served - if public.
553 # dsId = trackData["metadata"]["dataset_id"]
554 # url = "%s/api/datasets/%s/display?to_ext=hic " % (self.giURL, dsId)
555 hname = trackData["name"]
556 dest = os.path.join(self.outdir, hname)
557 cmd = ["cp", data, dest]
558 # these can be very big.
559 self.subprocess_check_call(cmd)
560 floc = {
561 "uri": hname,
562 }
563 trackDict = {
564 "type": "HicTrack",
565 "trackId": tId,
566 "name": hname,
567 "assemblyNames": [self.genome_name],
568 "adapter": {
569 "type": "HicAdapter",
570 "hicLocation": floc,
571 },
572 "displays": [
573 {
574 "type": "LinearHicDisplay",
575 "displayId": "%s-LinearHicDisplay" % tId,
576 },
577 ],
578 }
579 # style_json = self._prepare_track_style(trackDict)
580 # trackDict["style"] = style_json
581 self.tracksToAdd.append(trackDict)
582 self.trackIdlist.append(tId)
583
584 def add_maf(self, data, trackData):
585 """
586 from https://github.com/cmdcolin/maf2bed
587 Note: Both formats start with a MAF as input, and note that your MAF file should contain the species name and chromosome name
588 e.g. hg38.chr1 in the sequence identifiers.
589 need the reference id - eg hg18, for maf2bed.pl as the first parameter
590 """
591 mafPlugin = {
592 "plugins": [
593 {
594 "name": "MafViewer",
595 "url": "https://unpkg.com/jbrowse-plugin-mafviewer/dist/jbrowse-plugin-mafviewer.umd.production.min.js",
596 }
597 ]
598 }
599 tId = trackData["label"]
600 fname = "%s.bed" % tId
601 dest = "%s/%s" % (self.outdir, fname)
602 # self.symlink_or_copy(data, dest)
603 # Process MAF to bed-like. Need build to munge chromosomes
604 gname = self.genome_name
605 cmd = [
606 "bash",
607 os.path.join(INSTALLED_TO, "convertMAF.sh"),
608 data,
609 gname,
610 INSTALLED_TO,
611 dest,
612 ]
613 self.subprocess_check_call(cmd)
614 if True or self.debug:
615 log.info("### convertMAF.sh called as %s" % " ".join(cmd))
616 # Construct samples list
617 # We could get this from galaxy metadata, not sure how easily.
618 ps = subprocess.Popen(["grep", "^s [^ ]*", "-o", data], stdout=subprocess.PIPE)
619 output = subprocess.check_output(("sort", "-u"), stdin=ps.stdout)
620 ps.wait()
621 outp = output.decode("ascii")
622 soutp = outp.split("\n")
623 samp = [x.split("s ")[1] for x in soutp if x.startswith("s ")]
624 samples = [x.split(".")[0] for x in samp]
625 if self.debug:
626 log.info("### got samples = %s " % (samples))
627 trackDict = {
628 "type": "MafTrack",
629 "trackId": tId,
630 "name": trackData["name"],
631 "adapter": {
632 "type": "MafTabixAdapter",
633 "samples": samples,
634 "bedGzLocation": {
635 "uri": fname + ".sorted.bed.gz",
636 },
637 "index": {
638 "location": {
639 "uri": fname + ".sorted.bed.gz.tbi",
640 },
641 },
642 },
643 "assemblyNames": [self.genome_name],
644 }
645 # style_json = self._prepare_track_style(trackDict)
646 # trackDict["style"] = style_json
647 self.tracksToAdd.append(trackDict)
648 self.trackIdlist.append(tId)
649 if self.config_json.get("plugins", None):
650 self.config_json["plugins"].append(mafPlugin[0])
651 else:
652 self.config_json.update(mafPlugin)
653
654 def _blastxml_to_gff3(self, xml, min_gap=10):
655 gff3_unrebased = tempfile.NamedTemporaryFile(delete=False)
656 cmd = [
657 "python",
658 os.path.join(INSTALLED_TO, "blastxml_to_gapped_gff3.py"),
659 "--trim",
660 "--trim_end",
661 "--include_seq",
662 "--min_gap",
663 str(min_gap),
664 xml,
665 ]
666 subprocess.check_call(cmd, cwd=self.outdir, stdout=gff3_unrebased)
667 gff3_unrebased.close()
668 return gff3_unrebased.name
669
670 def add_blastxml(self, data, trackData, blastOpts, **kwargs):
671 gff3 = self._blastxml_to_gff3(data, min_gap=blastOpts["min_gap"])
672
673 if "parent" in blastOpts and blastOpts["parent"] != "None":
674 gff3_rebased = tempfile.NamedTemporaryFile(delete=False)
675 cmd = ["python", os.path.join(INSTALLED_TO, "gff3_rebase.py")]
676 if blastOpts.get("protein", "false") == "true":
677 cmd.append("--protein2dna")
678 cmd.extend([os.path.realpath(blastOpts["parent"]), gff3])
679 subprocess.check_call(cmd, cwd=self.outdir, stdout=gff3_rebased)
680 gff3_rebased.close()
681
682 # Replace original gff3 file
683 shutil.copy(gff3_rebased.name, gff3)
684 os.unlink(gff3_rebased.name)
685 url = "%s.gff3" % trackData["label"]
686 dest = "%s/%s" % (self.outdir, url)
687 self._sort_gff(gff3, dest)
688 url = url + ".gz"
689 tId = trackData["label"]
690 trackDict = {
691 "type": "FeatureTrack",
692 "trackId": tId,
693 "name": trackData["name"],
694 "assemblyNames": [self.genome_name],
695 "adapter": {
696 "type": "Gff3TabixAdapter",
697 "gffGzLocation": {
698 "uri": url,
699 },
700 "index": {
701 "location": {
702 "uri": url + ".tbi",
703 }
704 },
705 },
706 "displays": [
707 {
708 "type": "LinearBasicDisplay",
709 "displayId": "%s-LinearBasicDisplay" % tId,
710 },
711 {"type": "LinearArcDisplay", "displayId": "%s-LinearArcDisplay" % tId},
712 ],
713 }
714 self.tracksToAdd.append(trackDict)
715 self.trackIdlist.append(tId)
716
717 os.unlink(gff3)
718
719 def add_bigwig(self, data, trackData):
720 url = "%s.bw" % trackData["name"]
721 dest = os.path.join(self.outdir, url)
722 cmd = ["cp", data, dest]
723 self.subprocess_check_call(cmd)
724 bwloc = {"uri": url}
725 tId = trackData["label"]
726 trackDict = {
727 "type": "QuantitativeTrack",
728 "trackId": tId,
729 "name": url,
730 "assemblyNames": [
731 self.genome_name,
732 ],
733 "adapter": {
734 "type": "BigWigAdapter",
735 "bigWigLocation": bwloc,
736 },
737 "displays": [
738 {
739 "type": "LinearWiggleDisplay",
740 "displayId": "%s-LinearWiggleDisplay" % tId,
741 }
742 ],
743 }
744 # style_json = self._prepare_track_style(trackDict)
745 # trackDict["style"] = style_json
746 self.tracksToAdd.append(trackDict)
747 self.trackIdlist.append(tId)
748
749 def add_bam(self, data, trackData, bamOpts, bam_index=None, **kwargs):
750 tId = trackData["label"]
751 fname = "%s.bam" % trackData["label"]
752 dest = "%s/%s" % (self.outdir, fname)
753 url = fname
754 self.subprocess_check_call(["cp", data, dest])
755 log.info("### copied %s to %s" % (data, dest))
756 bloc = {"uri": url}
757 if bam_index is not None and os.path.exists(os.path.realpath(bam_index)):
758 # bai most probably made by galaxy and stored in galaxy dirs, need to copy it to dest
759 self.subprocess_check_call(
760 ["cp", os.path.realpath(bam_index), dest + ".bai"]
761 )
762 else:
763 # Can happen in exotic condition
764 # e.g. if bam imported as symlink with datatype=unsorted.bam, then datatype changed to bam
765 # => no index generated by galaxy, but there might be one next to the symlink target
766 # this trick allows to skip the bam sorting made by galaxy if already done outside
767 if os.path.exists(os.path.realpath(data) + ".bai"):
768 self.symlink_or_copy(os.path.realpath(data) + ".bai", dest + ".bai")
769 else:
770 log.warn("Could not find a bam index (.bai file) for %s", data)
771 trackDict = {
772 "type": "AlignmentsTrack",
773 "trackId": tId,
774 "name": trackData["name"],
775 "assemblyNames": [self.genome_name],
776 "adapter": {
777 "type": "BamAdapter",
778 "bamLocation": bloc,
779 "index": {
780 "location": {
781 "uri": fname + ".bai",
782 }
783 },
784 },
785 "displays": [
786 {
787 "type": "LinearAlignmentsDisplay",
788 "displayId": "%s-LinearAlignmentsDisplay" % tId,
789 },
790 ],
791 }
792 # style_json = self._prepare_track_style(trackDict)
793 # trackDict["style"] = style_json
794 self.tracksToAdd.append(trackDict)
795 self.trackIdlist.append(tId)
796
797 def add_vcf(self, data, trackData):
798 tId = trackData["label"]
799 url = "%s/api/datasets/%s/display" % (
800 self.giURL,
801 trackData["metadata"]["dataset_id"],
802 )
803 url = "%s.vcf.gz" % tId
804 dest = "%s/%s" % (self.outdir, url)
805 cmd = "bgzip -c %s > %s" % (data, dest)
806 self.subprocess_popen(cmd)
807 cmd = ["tabix", "-f", "-p", "vcf", dest]
808 self.subprocess_check_call(cmd)
809 trackDict = {
810 "type": "VariantTrack",
811 "trackId": tId,
812 "name": trackData["name"],
813 "assemblyNames": [self.genome_name],
814 "adapter": {
815 "type": "VcfTabixAdapter",
816 "vcfGzLocation": {
817 "uri": url,
818 },
819 "index": {
820 "location": {
821 "uri": url + ".tbi",
822 }
823 },
824 },
825 "displays": [
826 {
827 "type": "LinearVariantDisplay",
828 "displayId": "%s-LinearVariantDisplay" % tId,
829 },
830 {
831 "type": "ChordVariantDisplay",
832 "displayId": "%s-ChordVariantDisplay" % tId,
833 },
834 {
835 "type": "LinearPairedArcDisplay",
836 "displayId": "%s-LinearPairedArcDisplay" % tId,
837 },
838 ],
839 }
840 # style_json = self._prepare_track_style(trackDict)
841 # trackDict["style"] = style_json
842 self.tracksToAdd.append(trackDict)
843 self.trackIdlist.append(tId)
844
845 def _sort_gff(self, data, dest):
846 # Only index if not already done
847 if not os.path.exists(dest + ".gz"):
848 cmd = "jbrowse sort-gff %s | bgzip -c > %s.gz" % (
849 data,
850 dest,
851 ) # "gff3sort.pl --precise '%s' | grep -v \"^$\" > '%s'"
852 self.subprocess_popen(cmd)
853 self.subprocess_check_call(["tabix", "-f", "-p", "gff", dest + ".gz"])
854
855 def _sort_bed(self, data, dest):
856 # Only index if not already done
857 if not os.path.exists(dest):
858 cmd = "sort -k1,1 -k2,2n %s | bgzip -c > %s" % (data, dest)
859 self.subprocess_popen(cmd)
860 cmd = ["tabix", "-f", "-p", "bed", dest]
861 self.subprocess_check_call(cmd)
862
863 def add_gff(self, data, ext, trackData):
864 url = "%s.%s" % (trackData["label"], ext)
865 dest = "%s/%s" % (self.outdir, url)
866 self._sort_gff(data, dest)
867 url = url + ".gz"
868 tId = trackData["label"]
869 trackDict = {
870 "type": "FeatureTrack",
871 "trackId": tId,
872 "name": trackData["name"],
873 "assemblyNames": [self.genome_name],
874 "adapter": {
875 "type": "Gff3TabixAdapter",
876 "gffGzLocation": {
877 "uri": url,
878 },
879 "index": {
880 "location": {
881 "uri": url + ".tbi",
882 }
883 },
884 },
885 "displays": [
886 {
887 "type": "LinearBasicDisplay",
888 "displayId": "%s-LinearBasicDisplay" % tId,
889 },
890 {"type": "LinearArcDisplay", "displayId": "%s-LinearArcDisplay" % tId},
891 ],
892 }
893 # style_json = self._prepare_track_style(trackDict)
894 # trackDict["style"] = style_json
895 self.tracksToAdd.append(trackDict)
896 self.trackIdlist.append(tId)
897
898 def add_bed(self, data, ext, trackData):
899 url = "%s.%s" % (trackData["label"], ext)
900 dest = "%s/%s.gz" % (self.outdir, url)
901 self._sort_bed(data, dest)
902 tId = trackData["label"]
903 url = url + ".gz"
904 trackDict = {
905 "type": "FeatureTrack",
906 "trackId": tId,
907 "name": trackData["name"],
908 "assemblyNames": [self.genome_name],
909 "adapter": {
910 "type": "BedTabixAdapter",
911 "bedGzLocation": {
912 "uri": url,
913 },
914 "index": {
915 "location": {
916 "uri": url + ".tbi",
917 }
918 },
919 },
920 "displays": [
921 {
922 "type": "LinearBasicDisplay",
923 "displayId": "%s-LinearBasicDisplay" % tId,
924 },
925 {"type": "LinearArcDisplay", "displayId": "%s-LinearArcDisplay" % tId},
926 ],
927 }
928 # style_json = self._prepare_track_style(trackDict)
929 # trackDict["style"] = style_json
930 self.tracksToAdd.append(trackDict)
931 self.trackIdlist.append(tId)
932
933 def add_paf(self, data, trackData, pafOpts, **kwargs):
934 tname = trackData["name"]
935 tId = trackData["label"]
936 pgname = pafOpts["genome_label"]
937 if len(pgname.split() > 1):
938 pgname = pgname.split()[0] # trouble from spacey names in command lines avoidance
939 asstrack, gname = self.make_assembly(pafOpts["genome"], pgname)
940 self.genome_names.append(pgname)
941 if self.config_json.get("assemblies", None):
942 self.config_json["assemblies"].append(asstrack)
943 else:
944 self.config_json["assemblies"] = [asstrack,]
945
946 style_json = self._prepare_track_style(trackData)
947 url = "%s.paf" % (trackData["label"])
948 dest = "%s/%s" % (self.outdir, url)
949 self.symlink_or_copy(os.path.realpath(data), dest)
950
951 if self.usejson:
952 trackDict = {
953 "type": "SyntenyTrack",
954 "trackId": tId,
955 "assemblyNames": [self.genome_name, pgname],
956 "name": tname,
957 "adapter": {
958 "type": "PAFAdapter",
959 "pafLocation": {"uri": url},
960 "assemblyNames": [self.genome_name, pgname],
961 },
962 "config": style_json,
963 }
964 self.tracksToAdd.append(trackDict)
965 self.trackIdlist.append(tId)
966 else:
967 self._add_track(
968 trackData["label"],
969 trackData["key"],
970 trackData["category"],
971 dest,
972 assemblies=[self.genome_name, pgname],
973 config=style_json,
974 )
975
976 def add_hicab(self, data, trackData, hicOpts, **kwargs):
977 rel_dest = os.path.join("data", trackData["label"] + ".hic")
978 dest = os.path.join(self.outdir, rel_dest)
979
980 self.symlink_or_copy(os.path.realpath(data), dest)
981
982 style_json = self._prepare_track_style(trackData)
983
984 self._add_track(
985 trackData["label"],
986 trackData["key"],
987 trackData["category"],
988 rel_dest,
989 config=style_json,
990 )
991
992 def add_sparql(self, url, query, query_refnames, trackData):
993
994 json_track_data = {
995 "type": "FeatureTrack",
996 "trackId": id,
997 "name": trackData["label"],
998 "adapter": {
999 "type": "SPARQLAdapter",
1000 "endpoint": {"uri": url, "locationType": "UriLocation"},
1001 "queryTemplate": query,
1002 },
1003 "category": [trackData["category"]],
1004 "assemblyNames": [self.genome_name],
1005 }
1006
1007 if query_refnames:
1008 json_track_data["adapter"]["refNamesQueryTemplate"]: query_refnames
1009
1010 self.subprocess_check_call(
1011 [
1012 "jbrowse",
1013 "add-track-json",
1014 "--target",
1015 os.path.join(self.outdir, "data"),
1016 json_track_data,
1017 ]
1018 )
1019
1020 # Doesn't work as of 1.6.4, might work in the future
1021 # self.subprocess_check_call([
1022 # 'jbrowse', 'add-track',
1023 # '--trackType', 'sparql',
1024 # '--name', trackData['label'],
1025 # '--category', trackData['category'],
1026 # '--target', os.path.join(self.outdir, 'data'),
1027 # '--trackId', id,
1028 # '--config', '{"queryTemplate": "%s"}' % query,
1029 # url])
1030
1031 def process_annotations(self, track):
1032 category = track["category"].replace("__pd__date__pd__", TODAY)
1033 for i, (
1034 dataset_path,
1035 dataset_ext,
1036 track_human_label,
1037 extra_metadata,
1038 ) in enumerate(track["trackfiles"]):
1039 # Unsanitize labels (element_identifiers are always sanitized by Galaxy)
1040 for key, value in mapped_chars.items():
1041 track_human_label = track_human_label.replace(value, key)
1042 outputTrackConfig = {
1043 "category": category,
1044 "style": {},
1045 }
1046
1047 outputTrackConfig["key"] = track_human_label
1048 if self.debug:
1049 log.info(
1050 "Processing category = %s, track_human_label = %s",
1051 category,
1052 track_human_label,
1053 )
1054 # We add extra data to hash for the case of REST + SPARQL.
1055 if (
1056 "conf" in track
1057 and "options" in track["conf"]
1058 and "url" in track["conf"]["options"]
1059 ):
1060 rest_url = track["conf"]["options"]["url"]
1061 else:
1062 rest_url = ""
1063
1064 # I chose to use track['category'] instead of 'category' here. This
1065 # is intentional. This way re-running the tool on a different date
1066 # will not generate different hashes and make comparison of outputs
1067 # much simpler.
1068 hashData = [
1069 str(dataset_path),
1070 track_human_label,
1071 track["category"],
1072 rest_url,
1073 ]
1074 hashData = "|".join(hashData).encode("utf-8")
1075 outputTrackConfig["label"] = hashlib.md5(hashData).hexdigest() + "_%s" % i
1076 outputTrackConfig["metadata"] = extra_metadata
1077 outputTrackConfig["name"] = track_human_label
1078
1079 if dataset_ext in ("gff", "gff3"):
1080 self.add_gff(
1081 dataset_path,
1082 dataset_ext,
1083 outputTrackConfig,
1084 )
1085 elif dataset_ext in ("hic",):
1086 self.add_hic(
1087 dataset_path,
1088 outputTrackConfig,
1089 )
1090 elif dataset_ext in ("bed",):
1091 self.add_bed(
1092 dataset_path,
1093 dataset_ext,
1094 outputTrackConfig,
1095 )
1096 elif dataset_ext in ("maf",):
1097 self.add_maf(
1098 dataset_path,
1099 outputTrackConfig,
1100 )
1101 elif dataset_ext == "bigwig":
1102 self.add_bigwig(
1103 dataset_path,
1104 outputTrackConfig,
1105 )
1106 elif dataset_ext == "bam":
1107 real_indexes = track["conf"]["options"]["pileup"]["bam_indices"][
1108 "bam_index"
1109 ]
1110 if not isinstance(real_indexes, list):
1111 # <bam_indices>
1112 # <bam_index>/path/to/a.bam.bai</bam_index>
1113 # </bam_indices>
1114 #
1115 # The above will result in the 'bam_index' key containing a
1116 # string. If there are two or more indices, the container
1117 # becomes a list. Fun!
1118 real_indexes = [real_indexes]
1119
1120 self.add_bam(
1121 dataset_path,
1122 outputTrackConfig,
1123 track["conf"]["options"]["pileup"],
1124 bam_index=real_indexes[i],
1125 )
1126 elif dataset_ext == "blastxml":
1127 self.add_blastxml(
1128 dataset_path, outputTrackConfig, track["conf"]["options"]["blast"]
1129 )
1130 elif dataset_ext == "vcf":
1131 self.add_vcf(dataset_path, outputTrackConfig)
1132 else:
1133 log.warn("Do not know how to handle %s", dataset_ext)
1134 # Return non-human label for use in other fields
1135 yield outputTrackConfig["label"]
1136
1137 def add_default_session(self, data):
1138 """
1139 Add some default session settings: set some assemblies/tracks on/off
1140 """
1141 tracks_data = []
1142
1143 # TODO using the default session for now, but check out session specs in the future https://github.com/GMOD/jbrowse-components/issues/2708
1144
1145 # We need to know the track type from the config.json generated just before
1146 track_types = {}
1147 logging.info("### add default session has data = %s\n" % str(data))
1148 with open(self.config_json_file, "r") as config_file:
1149 config_json = json.load(config_file)
1150 logging.info("### config.json read \n%s\n" % (config_json))
1151
1152 for track_conf in self.tracksToAdd: # config_json["tracks"]:
1153 track_types[track_conf["trackId"]] = track_conf["type"]
1154 logging.info(
1155 "### self.tracksToAdd = %s; track_types = %s"
1156 % (str(self.tracksToAdd), str(track_types))
1157 )
1158
1159 for on_track in data["visibility"]["default_on"]:
1160 style_data = {"type": "LinearBasicDisplay", "height": 100}
1161 if on_track in data["style"]:
1162 if "display" in data["style"][on_track]:
1163 style_data["type"] = data["style"][on_track]["display"]
1164 del data["style"][on_track]["display"]
1165 style_data.update(data["style"][on_track])
1166 if on_track in data["style_labels"]:
1167 # TODO fix this: it should probably go in a renderer block (SvgFeatureRenderer) but still does not work
1168 # TODO move this to per track displays?
1169 style_data["labels"] = data["style_labels"][on_track]
1170
1171 tracks_data.append(
1172 {
1173 "type": track_types[on_track],
1174 "configuration": on_track,
1175 "displays": [style_data],
1176 }
1177 )
1178
1179 # The view for the assembly we're adding
1180 view_json = {"type": "LinearGenomeView", "tracks": tracks_data}
1181
1182 refName = None
1183 if data.get("defaultLocation", ""):
1184 loc_match = re.search(r"^(\w+):(\d+)\.+(\d+)$", data["defaultLocation"])
1185 if loc_match:
1186 refName = loc_match.group(1)
1187 start = int(loc_match.group(2))
1188 end = int(loc_match.group(3))
1189 elif self.genome_name is not None:
1190 refName = self.genome_name
1191 start = 0
1192 end = 1000 # Booh, hard coded! waiting for https://github.com/GMOD/jbrowse-components/issues/2708
1193
1194 if refName is not None:
1195 # TODO displayedRegions is not just zooming to the region, it hides the rest of the chromosome
1196 view_json["displayedRegions"] = [
1197 {
1198 "refName": refName,
1199 "start": start,
1200 "end": end,
1201 "reversed": False,
1202 "assemblyName": self.genome_name,
1203 }
1204 ]
1205
1206 session_name = data.get("session_name", "New session")
1207
1208 # Merge with possibly existing defaultSession (if upgrading a jbrowse instance)
1209 session_json = {}
1210 if "defaultSession" in config_json:
1211 session_json = config_json["defaultSession"]
1212
1213 session_json["name"] = session_name
1214
1215 if "views" not in session_json:
1216 session_json["views"] = []
1217
1218 session_json["views"].append(view_json)
1219
1220 config_json["defaultSession"] = session_json
1221
1222 with open(self.config_json_file, "w") as config_file:
1223 json.dump(config_json, config_file, indent=2)
1224
1225 def add_general_configuration(self, data):
1226 """
1227 Add some general configuration to the config.json file
1228 """
1229
1230 config_path = self.config_json_file
1231 config_json = {}
1232 if os.path.exists(config_path):
1233 with open(config_path, "r") as config_file:
1234 config_json = json.load(config_file)
1235
1236 config_data = {}
1237
1238 config_data["disableAnalytics"] = data.get("analytics", "false") == "true"
1239
1240 config_data["theme"] = {
1241 "palette": {
1242 "primary": {"main": data.get("primary_color", "#0D233F")},
1243 "secondary": {"main": data.get("secondary_color", "#721E63")},
1244 "tertiary": {"main": data.get("tertiary_color", "#135560")},
1245 "quaternary": {"main": data.get("quaternary_color", "#FFB11D")},
1246 },
1247 "typography": {"fontSize": int(data.get("font_size", 10))},
1248 }
1249 if not config_json.get("configuration", None):
1250 config_json["configuration"] = {}
1251 config_json["configuration"].update(config_data)
1252
1253 with open(config_path, "w") as config_file:
1254 json.dump(config_json, config_file, indent=2)
1255
1256 def clone_jbrowse(self):
1257 """Clone a JBrowse directory into a destination directory."""
1258 dest = self.outdir
1259 cmd = ["jbrowse", "create", "-f", dest]
1260 self.subprocess_check_call(cmd)
1261 for fn in [
1262 "asset-manifest.json",
1263 "favicon.ico",
1264 "robots.txt",
1265 "umd_plugin.js",
1266 "version.txt",
1267 "test_data",
1268 ]:
1269 cmd = ["rm", "-rf", os.path.join(self.outdir, fn)]
1270 self.subprocess_check_call(cmd)
1271 cmd = ["cp", os.path.join(INSTALLED_TO, "servejb2.py"), self.outdir]
1272 self.subprocess_check_call(cmd)
1273
1274
1275 def parse_style_conf(item):
1276 if "type" in item.attrib and item.attrib["type"] in ["boolean", "integer"]:
1277 if item.attrib["type"] == "boolean":
1278 return item.text in ("yes", "true", "True")
1279 elif item.attrib["type"] == "integer":
1280 return int(item.text)
1281 else:
1282 return item.text
1283
1284
1285 if __name__ == "__main__":
1286 parser = argparse.ArgumentParser(description="", epilog="")
1287 parser.add_argument("--xml", help="Track Configuration")
1288 parser.add_argument("--outdir", help="Output directory", default="out")
1289 parser.add_argument("--version", "-V", action="version", version="%(prog)s 2.0.1")
1290 args = parser.parse_args()
1291
1292 tree = ET.parse(args.xml)
1293 root = tree.getroot()
1294
1295 # This should be done ASAP
1296 GALAXY_INFRASTRUCTURE_URL = root.find("metadata/galaxyUrl").text
1297 # Sometimes this comes as `localhost` without a protocol
1298 if not GALAXY_INFRASTRUCTURE_URL.startswith("http"):
1299 # so we'll prepend `http://` and hope for the best. Requests *should*
1300 # be GET and not POST so it should redirect OK
1301 GALAXY_INFRASTRUCTURE_URL = "http://" + GALAXY_INFRASTRUCTURE_URL
1302 jc = JbrowseConnector(
1303 outdir=args.outdir,
1304 genomes=[
1305 {
1306 "path": os.path.realpath(x.attrib["path"]),
1307 "meta": metadata_from_node(x.find("metadata")),
1308 }
1309 for x in root.findall("metadata/genomes/genome")
1310 ],
1311 )
1312 jc.process_genomes()
1313
1314 # .add_default_view() replace from https://github.com/abretaud/tools-iuc/blob/jbrowse2/tools/jbrowse2/jbrowse2.py
1315 default_session_data = {
1316 "visibility": {
1317 "default_on": [],
1318 "default_off": [],
1319 },
1320 "style": {},
1321 "style_labels": {},
1322 }
1323
1324 for track in root.findall("tracks/track"):
1325 track_conf = {}
1326 track_conf["trackfiles"] = []
1327
1328 is_multi_bigwig = False
1329 try:
1330 if track.find("options/wiggle/multibigwig") and (
1331 track.find("options/wiggle/multibigwig").text == "True"
1332 ):
1333 is_multi_bigwig = True
1334 multi_bigwig_paths = []
1335 except KeyError:
1336 pass
1337
1338 trackfiles = track.findall("files/trackFile")
1339 if trackfiles:
1340 for x in track.findall("files/trackFile"):
1341 if is_multi_bigwig:
1342 multi_bigwig_paths.append(
1343 (x.attrib["label"], os.path.realpath(x.attrib["path"]))
1344 )
1345 else:
1346 if trackfiles:
1347 metadata = metadata_from_node(x.find("metadata"))
1348 track_conf["dataset_id"] = metadata["dataset_id"]
1349 track_conf["trackfiles"].append(
1350 (
1351 os.path.realpath(x.attrib["path"]),
1352 x.attrib["ext"],
1353 x.attrib["label"],
1354 metadata,
1355 )
1356 )
1357 else:
1358 # For tracks without files (rest, sparql)
1359 track_conf["trackfiles"].append(
1360 (
1361 "", # N/A, no path for rest or sparql
1362 track.attrib["format"],
1363 track.find("options/label").text,
1364 {},
1365 )
1366 )
1367
1368 if is_multi_bigwig:
1369 metadata = metadata_from_node(x.find("metadata"))
1370
1371 track_conf["trackfiles"].append(
1372 (
1373 multi_bigwig_paths, # Passing an array of paths to represent as one track
1374 "bigwig_multiple",
1375 "MultiBigWig", # Giving an hardcoded name for now
1376 {}, # No metadata for multiple bigwig
1377 )
1378 )
1379
1380 track_conf["category"] = track.attrib["cat"]
1381 track_conf["format"] = track.attrib["format"]
1382 track_conf["style"] = {
1383 item.tag: parse_style_conf(item) for item in track.find("options/style")
1384 }
1385
1386 track_conf["style"] = {
1387 item.tag: parse_style_conf(item) for item in track.find("options/style")
1388 }
1389
1390 track_conf["style_labels"] = {
1391 item.tag: parse_style_conf(item)
1392 for item in track.find("options/style_labels")
1393 }
1394
1395 track_conf["conf"] = etree_to_dict(track.find("options"))
1396 keys = jc.process_annotations(track_conf)
1397
1398 if keys:
1399 for key in keys:
1400 default_session_data["visibility"][
1401 track.attrib.get("visibility", "default_off")
1402 ].append(key)
1403 default_session_data["style"][key] = track_conf[
1404 "style"
1405 ] # TODO do we need this anymore?
1406 default_session_data["style_labels"][key] = track_conf["style_labels"]
1407
1408 default_session_data["defaultLocation"] = root.find(
1409 "metadata/general/defaultLocation"
1410 ).text
1411 default_session_data["session_name"] = root.find(
1412 "metadata/general/session_name"
1413 ).text
1414
1415 general_data = {
1416 "analytics": root.find("metadata/general/analytics").text,
1417 "primary_color": root.find("metadata/general/primary_color").text,
1418 "secondary_color": root.find("metadata/general/secondary_color").text,
1419 "tertiary_color": root.find("metadata/general/tertiary_color").text,
1420 "quaternary_color": root.find("metadata/general/quaternary_color").text,
1421 "font_size": root.find("metadata/general/font_size").text,
1422 }
1423 track_conf["category"] = track.attrib["cat"]
1424 track_conf["format"] = track.attrib["format"]
1425 try:
1426 # Only pertains to gff3 + blastxml. TODO?
1427 track_conf["style"] = {t.tag: t.text for t in track.find("options/style")}
1428 except TypeError:
1429 track_conf["style"] = {}
1430 pass
1431 track_conf["conf"] = etree_to_dict(track.find("options"))
1432 jc.add_general_configuration(general_data)
1433 print("## processed", str(track_conf), "trackIdlist", jc.trackIdlist)
1434 x = open(args.xml, "r").read()
1435 log.info(
1436 "###done processing xml=%s; trackIdlist=%s, config=%s"
1437 % (x, jc.trackIdlist, str(jc.config_json))
1438 )
1439 jc.config_json["tracks"] = jc.tracksToAdd
1440 if jc.usejson:
1441 jc.write_config()
1442 # jc.add_default_view()
1443 jc.add_default_session(default_session_data)
1444
1445 # jc.text_index() not sure what broke here.