comparison abjbrowse2.py @ 14:7c2e28e144f3 draft

planemo upload for repository https://github.com/galaxyproject/tools-iuc/tree/master/tools/jbrowse2 commit 5d84967148d45684d585d4e12b210dc5e12e4776
author fubar
date Mon, 22 Jan 2024 12:05:09 +0000
parents 1d86925dbb4c
children
comparison
equal deleted inserted replaced
13:1d86925dbb4c 14:7c2e28e144f3
13 import tempfile 13 import tempfile
14 import xml.etree.ElementTree as ET 14 import xml.etree.ElementTree as ET
15 from collections import defaultdict 15 from collections import defaultdict
16 16
17 logging.basicConfig(level=logging.INFO) 17 logging.basicConfig(level=logging.INFO)
18 log = logging.getLogger('jbrowse') 18 log = logging.getLogger("jbrowse")
19 TODAY = datetime.datetime.now().strftime("%Y-%m-%d") 19 TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
20 GALAXY_INFRASTRUCTURE_URL = None 20 GALAXY_INFRASTRUCTURE_URL = None
21 21
22 22
23 class ColorScaling(object): 23 class ColorScaling(object):
73 return 'rgba(' + red + ',' + green + ',' + blue + ',' + opacity + ')'; 73 return 'rgba(' + red + ',' + green + ',' + blue + ',' + opacity + ')';
74 }} 74 }}
75 """ 75 """
76 76
77 OPACITY_MATH = { 77 OPACITY_MATH = {
78 'linear': """ 78 "linear": """
79 var opacity = (score - ({min})) / (({max}) - ({min})); 79 var opacity = (score - ({min})) / (({max}) - ({min}));
80 """, 80 """,
81 'logarithmic': """ 81 "logarithmic": """
82 var opacity = Math.log10(score - ({min})) / Math.log10(({max}) - ({min})); 82 var opacity = Math.log10(score - ({min})) / Math.log10(({max}) - ({min}));
83 """, 83 """,
84 'blast': """ 84 "blast": """
85 var opacity = 0; 85 var opacity = 0;
86 if(score == 0.0) {{ 86 if(score == 0.0) {{
87 opacity = 1; 87 opacity = 1;
88 }} else {{ 88 }} else {{
89 opacity = (20 - Math.log10(score)) / 180; 89 opacity = (20 - Math.log10(score)) / 180;
90 }} 90 }}
91 """ 91 """,
92 } 92 }
93 93
94 BREWER_COLOUR_IDX = 0 94 BREWER_COLOUR_IDX = 0
95 BREWER_COLOUR_SCHEMES = [ 95 BREWER_COLOUR_SCHEMES = [
96 (166, 206, 227), 96 (166, 206, 227),
111 (152, 78, 163), 111 (152, 78, 163),
112 (255, 127, 0), 112 (255, 127, 0),
113 ] 113 ]
114 114
115 BREWER_DIVERGING_PALLETES = { 115 BREWER_DIVERGING_PALLETES = {
116 'BrBg': ("#543005", "#003c30"), 116 "BrBg": ("#543005", "#003c30"),
117 'PiYg': ("#8e0152", "#276419"), 117 "PiYg": ("#8e0152", "#276419"),
118 'PRGn': ("#40004b", "#00441b"), 118 "PRGn": ("#40004b", "#00441b"),
119 'PuOr': ("#7f3b08", "#2d004b"), 119 "PuOr": ("#7f3b08", "#2d004b"),
120 'RdBu': ("#67001f", "#053061"), 120 "RdBu": ("#67001f", "#053061"),
121 'RdGy': ("#67001f", "#1a1a1a"), 121 "RdGy": ("#67001f", "#1a1a1a"),
122 'RdYlBu': ("#a50026", "#313695"), 122 "RdYlBu": ("#a50026", "#313695"),
123 'RdYlGn': ("#a50026", "#006837"), 123 "RdYlGn": ("#a50026", "#006837"),
124 'Spectral': ("#9e0142", "#5e4fa2"), 124 "Spectral": ("#9e0142", "#5e4fa2"),
125 } 125 }
126 126
127 def __init__(self): 127 def __init__(self):
128 self.brewer_colour_idx = 0 128 self.brewer_colour_idx = 0
129 129
130 def rgb_from_hex(self, hexstr): 130 def rgb_from_hex(self, hexstr):
131 # http://stackoverflow.com/questions/4296249/how-do-i-convert-a-hex-triplet-to-an-rgb-tuple-and-back 131 # http://stackoverflow.com/questions/4296249/how-do-i-convert-a-hex-triplet-to-an-rgb-tuple-and-back
132 return struct.unpack('BBB', binascii.unhexlify(hexstr)) 132 return struct.unpack("BBB", binascii.unhexlify(hexstr))
133 133
134 def min_max_gff(self, gff_file): 134 def min_max_gff(self, gff_file):
135 min_val = None 135 min_val = None
136 max_val = None 136 max_val = None
137 with open(gff_file, 'r') as handle: 137 with open(gff_file, "r") as handle:
138 for line in handle: 138 for line in handle:
139 try: 139 try:
140 value = float(line.split('\t')[5]) 140 value = float(line.split("\t")[5])
141 min_val = min(value, (min_val or value)) 141 min_val = min(value, (min_val or value))
142 max_val = max(value, (max_val or value)) 142 max_val = max(value, (max_val or value))
143 143
144 if value < min_val: 144 if value < min_val:
145 min_val = value 145 min_val = value
149 except Exception: 149 except Exception:
150 pass 150 pass
151 return min_val, max_val 151 return min_val, max_val
152 152
153 def hex_from_rgb(self, r, g, b): 153 def hex_from_rgb(self, r, g, b):
154 return '#%02x%02x%02x' % (r, g, b) 154 return "#%02x%02x%02x" % (r, g, b)
155 155
156 def _get_colours(self): 156 def _get_colours(self):
157 r, g, b = self.BREWER_COLOUR_SCHEMES[self.brewer_colour_idx % len(self.BREWER_COLOUR_SCHEMES)] 157 r, g, b = self.BREWER_COLOUR_SCHEMES[
158 self.brewer_colour_idx % len(self.BREWER_COLOUR_SCHEMES)
159 ]
158 self.brewer_colour_idx += 1 160 self.brewer_colour_idx += 1
159 return r, g, b 161 return r, g, b
160 162
161 def parse_menus(self, track): 163 def parse_menus(self, track):
162 trackConfig = {'menuTemplate': [{}, {}, {}, {}]} 164 trackConfig = {"menuTemplate": [{}, {}, {}, {}]}
163 165
164 if 'menu' in track['menus']: 166 if "menu" in track["menus"]:
165 menu_list = [track['menus']['menu']] 167 menu_list = [track["menus"]["menu"]]
166 if isinstance(track['menus']['menu'], list): 168 if isinstance(track["menus"]["menu"], list):
167 menu_list = track['menus']['menu'] 169 menu_list = track["menus"]["menu"]
168 170
169 for m in menu_list: 171 for m in menu_list:
170 tpl = { 172 tpl = {
171 'action': m['action'], 173 "action": m["action"],
172 'label': m.get('label', '{name}'), 174 "label": m.get("label", "{name}"),
173 'iconClass': m.get('iconClass', 'dijitIconBookmark'), 175 "iconClass": m.get("iconClass", "dijitIconBookmark"),
174 } 176 }
175 if 'url' in m: 177 if "url" in m:
176 tpl['url'] = m['url'] 178 tpl["url"] = m["url"]
177 if 'content' in m: 179 if "content" in m:
178 tpl['content'] = m['content'] 180 tpl["content"] = m["content"]
179 if 'title' in m: 181 if "title" in m:
180 tpl['title'] = m['title'] 182 tpl["title"] = m["title"]
181 183
182 trackConfig['menuTemplate'].append(tpl) 184 trackConfig["menuTemplate"].append(tpl)
183 185
184 return trackConfig 186 return trackConfig
185 187
186 def parse_colours(self, track, trackFormat, gff3=None): 188 def parse_colours(self, track, trackFormat, gff3=None):
187 # Wiggle tracks have a bicolor pallete 189 # Wiggle tracks have a bicolor pallete
188 trackConfig = {'style': {}} 190 trackConfig = {"style": {}}
189 if trackFormat == 'wiggle': 191 if trackFormat == "wiggle":
190 192
191 trackConfig['style']['pos_color'] = track['wiggle']['color_pos'] 193 trackConfig["style"]["pos_color"] = track["wiggle"]["color_pos"]
192 trackConfig['style']['neg_color'] = track['wiggle']['color_neg'] 194 trackConfig["style"]["neg_color"] = track["wiggle"]["color_neg"]
193 195
194 if trackConfig['style']['pos_color'] == '__auto__': 196 if trackConfig["style"]["pos_color"] == "__auto__":
195 trackConfig['style']['neg_color'] = self.hex_from_rgb(*self._get_colours()) 197 trackConfig["style"]["neg_color"] = self.hex_from_rgb(
196 trackConfig['style']['pos_color'] = self.hex_from_rgb(*self._get_colours()) 198 *self._get_colours()
199 )
200 trackConfig["style"]["pos_color"] = self.hex_from_rgb(
201 *self._get_colours()
202 )
197 203
198 # Wiggle tracks can change colour at a specified place 204 # Wiggle tracks can change colour at a specified place
199 bc_pivot = track['wiggle']['bicolor_pivot'] 205 bc_pivot = track["wiggle"]["bicolor_pivot"]
200 if bc_pivot not in ('mean', 'zero'): 206 if bc_pivot not in ("mean", "zero"):
201 # The values are either one of those two strings 207 # The values are either one of those two strings
202 # or a number 208 # or a number
203 bc_pivot = float(bc_pivot) 209 bc_pivot = float(bc_pivot)
204 trackConfig['bicolor_pivot'] = bc_pivot 210 trackConfig["bicolor_pivot"] = bc_pivot
205 elif 'scaling' in track: 211 elif "scaling" in track:
206 if track['scaling']['method'] == 'ignore': 212 if track["scaling"]["method"] == "ignore":
207 if track['scaling']['scheme']['color'] != '__auto__': 213 if track["scaling"]["scheme"]["color"] != "__auto__":
208 trackConfig['style']['color'] = track['scaling']['scheme']['color'] 214 trackConfig["style"]["color"] = track["scaling"]["scheme"]["color"]
209 else: 215 else:
210 trackConfig['style']['color'] = self.hex_from_rgb(*self._get_colours()) 216 trackConfig["style"]["color"] = self.hex_from_rgb(
217 *self._get_colours()
218 )
211 else: 219 else:
212 # Scored method 220 # Scored method
213 algo = track['scaling']['algo'] 221 algo = track["scaling"]["algo"]
214 # linear, logarithmic, blast 222 # linear, logarithmic, blast
215 scales = track['scaling']['scales'] 223 scales = track["scaling"]["scales"]
216 # type __auto__, manual (min, max) 224 # type __auto__, manual (min, max)
217 scheme = track['scaling']['scheme'] 225 scheme = track["scaling"]["scheme"]
218 # scheme -> (type (opacity), color) 226 # scheme -> (type (opacity), color)
219 # ================================== 227 # ==================================
220 # GENE CALLS OR BLAST 228 # GENE CALLS OR BLAST
221 # ================================== 229 # ==================================
222 if trackFormat == 'blast': 230 if trackFormat == "blast":
223 red, green, blue = self._get_colours() 231 red, green, blue = self._get_colours()
224 color_function = self.COLOR_FUNCTION_TEMPLATE.format(**{ 232 color_function = self.COLOR_FUNCTION_TEMPLATE.format(
225 'score': "feature._parent.get('score')", 233 **{
226 'opacity': self.OPACITY_MATH['blast'], 234 "score": "feature._parent.get('score')",
227 'red': red, 235 "opacity": self.OPACITY_MATH["blast"],
228 'green': green, 236 "red": red,
229 'blue': blue, 237 "green": green,
230 }) 238 "blue": blue,
231 trackConfig['style']['color'] = color_function.replace('\n', '') 239 }
232 elif trackFormat == 'gene_calls': 240 )
241 trackConfig["style"]["color"] = color_function.replace("\n", "")
242 elif trackFormat == "gene_calls":
233 # Default values, based on GFF3 spec 243 # Default values, based on GFF3 spec
234 min_val = 0 244 min_val = 0
235 max_val = 1000 245 max_val = 1000
236 # Get min/max and build a scoring function since JBrowse doesn't 246 # Get min/max and build a scoring function since JBrowse doesn't
237 if scales['type'] == 'automatic' or scales['type'] == '__auto__': 247 if scales["type"] == "automatic" or scales["type"] == "__auto__":
238 min_val, max_val = self.min_max_gff(gff3) 248 min_val, max_val = self.min_max_gff(gff3)
239 else: 249 else:
240 min_val = scales.get('min', 0) 250 min_val = scales.get("min", 0)
241 max_val = scales.get('max', 1000) 251 max_val = scales.get("max", 1000)
242 252
243 if scheme['color'] == '__auto__': 253 if scheme["color"] == "__auto__":
244 user_color = 'undefined' 254 user_color = "undefined"
245 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours()) 255 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours())
246 elif scheme['color'].startswith('#'): 256 elif scheme["color"].startswith("#"):
247 user_color = "'%s'" % self.hex_from_rgb(*self.rgb_from_hex(scheme['color'][1:])) 257 user_color = "'%s'" % self.hex_from_rgb(
248 auto_color = 'undefined' 258 *self.rgb_from_hex(scheme["color"][1:])
259 )
260 auto_color = "undefined"
249 else: 261 else:
250 user_color = 'undefined' 262 user_color = "undefined"
251 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours()) 263 auto_color = "'%s'" % self.hex_from_rgb(*self._get_colours())
252 264
253 color_function = self.COLOR_FUNCTION_TEMPLATE_QUAL.format(**{ 265 color_function = self.COLOR_FUNCTION_TEMPLATE_QUAL.format(
254 'opacity': self.OPACITY_MATH[algo].format(**{'max': max_val, 'min': min_val}), 266 **{
255 'user_spec_color': user_color, 267 "opacity": self.OPACITY_MATH[algo].format(
256 'auto_gen_color': auto_color, 268 **{"max": max_val, "min": min_val}
257 }) 269 ),
258 270 "user_spec_color": user_color,
259 trackConfig['style']['color'] = color_function.replace('\n', '') 271 "auto_gen_color": auto_color,
272 }
273 )
274
275 trackConfig["style"]["color"] = color_function.replace("\n", "")
260 return trackConfig 276 return trackConfig
261 277
262 278
263 def etree_to_dict(t): 279 def etree_to_dict(t):
264 if t is None: 280 if t is None:
271 for dc in map(etree_to_dict, children): 287 for dc in map(etree_to_dict, children):
272 for k, v in dc.items(): 288 for k, v in dc.items():
273 dd[k].append(v) 289 dd[k].append(v)
274 d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}} 290 d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}
275 if t.attrib: 291 if t.attrib:
276 d[t.tag].update(('@' + k, v) for k, v in t.attrib.items()) 292 d[t.tag].update(("@" + k, v) for k, v in t.attrib.items())
277 if t.text: 293 if t.text:
278 text = t.text.strip() 294 text = t.text.strip()
279 if children or t.attrib: 295 if children or t.attrib:
280 if text: 296 if text:
281 d[t.tag]['#text'] = text 297 d[t.tag]["#text"] = text
282 else: 298 else:
283 d[t.tag] = text 299 d[t.tag] = text
284 return d 300 return d
285 301
286 302
290 306
291 307
292 def metadata_from_node(node): 308 def metadata_from_node(node):
293 metadata = {} 309 metadata = {}
294 try: 310 try:
295 if len(node.findall('dataset')) != 1: 311 if len(node.findall("dataset")) != 1:
296 # exit early 312 # exit early
297 return metadata 313 return metadata
298 except Exception: 314 except Exception:
299 return {} 315 return {}
300 316
301 for (key, value) in node.findall('dataset')[0].attrib.items(): 317 for (key, value) in node.findall("dataset")[0].attrib.items():
302 metadata['dataset_%s' % key] = value 318 metadata["dataset_%s" % key] = value
303 319
304 for (key, value) in node.findall('history')[0].attrib.items(): 320 for (key, value) in node.findall("history")[0].attrib.items():
305 metadata['history_%s' % key] = value 321 metadata["history_%s" % key] = value
306 322
307 for (key, value) in node.findall('metadata')[0].attrib.items(): 323 for (key, value) in node.findall("metadata")[0].attrib.items():
308 metadata['metadata_%s' % key] = value 324 metadata["metadata_%s" % key] = value
309 325
310 for (key, value) in node.findall('tool')[0].attrib.items(): 326 for (key, value) in node.findall("tool")[0].attrib.items():
311 metadata['tool_%s' % key] = value 327 metadata["tool_%s" % key] = value
312 328
313 # Additional Mappings applied: 329 # Additional Mappings applied:
314 metadata['dataset_edam_format'] = '<a target="_blank" href="http://edamontology.org/{0}">{1}</a>'.format(metadata['dataset_edam_format'], metadata['dataset_file_ext']) 330 metadata[
315 metadata['history_user_email'] = '<a href="mailto:{0}">{0}</a>'.format(metadata['history_user_email']) 331 "dataset_edam_format"
316 metadata['history_display_name'] = '<a target="_blank" href="{galaxy}/history/view/{encoded_hist_id}">{hist_name}</a>'.format( 332 ] = '<a target="_blank" href="http://edamontology.org/{0}">{1}</a>'.format(
333 metadata["dataset_edam_format"], metadata["dataset_file_ext"]
334 )
335 metadata["history_user_email"] = '<a href="mailto:{0}">{0}</a>'.format(
336 metadata["history_user_email"]
337 )
338 metadata[
339 "history_display_name"
340 ] = '<a target="_blank" href="{galaxy}/history/view/{encoded_hist_id}">{hist_name}</a>'.format(
317 galaxy=GALAXY_INFRASTRUCTURE_URL, 341 galaxy=GALAXY_INFRASTRUCTURE_URL,
318 encoded_hist_id=metadata['history_id'], 342 encoded_hist_id=metadata["history_id"],
319 hist_name=metadata['history_display_name'] 343 hist_name=metadata["history_display_name"],
320 ) 344 )
321 metadata['tool_tool'] = '<a target="_blank" href="{galaxy}/datasets/{encoded_id}/show_params">{tool_id}</a>'.format( 345 metadata[
346 "tool_tool"
347 ] = '<a target="_blank" href="{galaxy}/datasets/{encoded_id}/show_params">{tool_id}</a>'.format(
322 galaxy=GALAXY_INFRASTRUCTURE_URL, 348 galaxy=GALAXY_INFRASTRUCTURE_URL,
323 encoded_id=metadata['dataset_id'], 349 encoded_id=metadata["dataset_id"],
324 tool_id=metadata['tool_tool_id'], 350 tool_id=metadata["tool_tool_id"],
325 # tool_version=metadata['tool_tool_version'], 351 # tool_version=metadata['tool_tool_version'],
326 ) 352 )
327 return metadata 353 return metadata
328 354
329 355
330 class JbrowseConnector(object): 356 class JbrowseConnector(object):
331
332 def __init__(self, jbrowse, outdir, genomes): 357 def __init__(self, jbrowse, outdir, genomes):
333 self.cs = ColorScaling() 358 self.cs = ColorScaling()
334 self.jbrowse = jbrowse 359 self.jbrowse = jbrowse
335 self.outdir = outdir 360 self.outdir = outdir
336 self.genome_paths = genomes 361 self.genome_paths = genomes
347 372
348 self.process_genomes() 373 self.process_genomes()
349 374
350 def subprocess_check_call(self, command, output=None): 375 def subprocess_check_call(self, command, output=None):
351 if output: 376 if output:
352 log.debug('cd %s && %s > %s', self.outdir, ' '.join(command), output) 377 log.debug("cd %s && %s > %s", self.outdir, " ".join(command), output)
353 subprocess.check_call(command, cwd=self.outdir, stdout=output) 378 subprocess.check_call(command, cwd=self.outdir, stdout=output)
354 else: 379 else:
355 log.debug('cd %s && %s', self.outdir, ' '.join(command)) 380 log.debug("cd %s && %s", self.outdir, " ".join(command))
356 subprocess.check_call(command, cwd=self.outdir) 381 subprocess.check_call(command, cwd=self.outdir)
357 382
358 def subprocess_popen(self, command): 383 def subprocess_popen(self, command):
359 log.debug('cd %s && %s', self.outdir, command) 384 log.debug("cd %s && %s", self.outdir, command)
360 p = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 385 p = subprocess.Popen(
386 command,
387 shell=True,
388 stdin=subprocess.PIPE,
389 stdout=subprocess.PIPE,
390 stderr=subprocess.PIPE,
391 )
361 output, err = p.communicate() 392 output, err = p.communicate()
362 retcode = p.returncode 393 retcode = p.returncode
363 if retcode != 0: 394 if retcode != 0:
364 log.error('cd %s && %s', self.outdir, command) 395 log.error("cd %s && %s", self.outdir, command)
365 log.error(output) 396 log.error(output)
366 log.error(err) 397 log.error(err)
367 raise RuntimeError("Command failed with exit code %s" % (retcode)) 398 raise RuntimeError("Command failed with exit code %s" % (retcode))
368 399
369 def subprocess_check_output(self, command): 400 def subprocess_check_output(self, command):
370 log.debug('cd %s && %s', self.outdir, ' '.join(command)) 401 log.debug("cd %s && %s", self.outdir, " ".join(command))
371 return subprocess.check_output(command, cwd=self.outdir) 402 return subprocess.check_output(command, cwd=self.outdir)
372 403
373 def symlink_or_copy(self, src, dest): 404 def symlink_or_copy(self, src, dest):
374 if 'GALAXY_JBROWSE_SYMLINKS' in os.environ and bool(os.environ['GALAXY_JBROWSE_SYMLINKS']): 405 if "GALAXY_JBROWSE_SYMLINKS" in os.environ and bool(
375 cmd = ['ln', '-s', src, dest] 406 os.environ["GALAXY_JBROWSE_SYMLINKS"]
407 ):
408 cmd = ["ln", "-s", src, dest]
376 else: 409 else:
377 cmd = ['cp', src, dest] 410 cmd = ["cp", src, dest]
378 411
379 return self.subprocess_check_call(cmd) 412 return self.subprocess_check_call(cmd)
380 413
381 def symlink_or_copy_load_action(self): 414 def symlink_or_copy_load_action(self):
382 if 'GALAXY_JBROWSE_SYMLINKS' in os.environ and bool(os.environ['GALAXY_JBROWSE_SYMLINKS']): 415 if "GALAXY_JBROWSE_SYMLINKS" in os.environ and bool(
383 return 'symlink' 416 os.environ["GALAXY_JBROWSE_SYMLINKS"]
417 ):
418 return "symlink"
384 else: 419 else:
385 return 'copy' 420 return "copy"
386 421
387 def check_existing(self, destination): 422 def check_existing(self, destination):
388 existing = os.path.join(destination, 'data', "config.json") 423 existing = os.path.join(destination, "data", "config.json")
389 if os.path.exists(existing): 424 if os.path.exists(existing):
390 with open(existing, 'r') as existing_conf: 425 with open(existing, "r") as existing_conf:
391 conf = json.load(existing_conf) 426 conf = json.load(existing_conf)
392 if 'assemblies' in conf: 427 if "assemblies" in conf:
393 for assembly in conf['assemblies']: 428 for assembly in conf["assemblies"]:
394 if 'name' in assembly: 429 if "name" in assembly:
395 self.assembly_ids[assembly['name']] = None 430 self.assembly_ids[assembly["name"]] = None
396 431
397 def process_genomes(self): 432 def process_genomes(self):
398 for genome_node in self.genome_paths: 433 for genome_node in self.genome_paths:
399 # We only expect one input genome per run. This for loop is just 434 # We only expect one input genome per run. This for loop is just
400 # easier to write than the alternative / catches any possible 435 # easier to write than the alternative / catches any possible
401 # issues. 436 # issues.
402 self.add_assembly(genome_node['path'], genome_node['label']) 437 self.add_assembly(genome_node["path"], genome_node["label"])
403 438
404 def add_assembly(self, path, label, default=True): 439 def add_assembly(self, path, label, default=True):
405 # Find a non-existing filename for the new genome 440 # Find a non-existing filename for the new genome
406 # (to avoid colision when upgrading an existing instance) 441 # (to avoid colision when upgrading an existing instance)
407 rel_seq_path = os.path.join('data', 'assembly') 442 rel_seq_path = os.path.join("data", "assembly")
408 seq_path = os.path.join(self.outdir, rel_seq_path) 443 seq_path = os.path.join(self.outdir, rel_seq_path)
409 fn_try = 1 444 fn_try = 1
410 while (os.path.exists(seq_path + '.fasta') or os.path.exists(seq_path + '.fasta.gz') 445 while (
411 or os.path.exists(seq_path + '.fasta.gz.fai') or os.path.exists(seq_path + '.fasta.gz.gzi')): 446 os.path.exists(seq_path + ".fasta")
412 rel_seq_path = os.path.join('data', 'assembly%s' % fn_try) 447 or os.path.exists(seq_path + ".fasta.gz")
448 or os.path.exists(seq_path + ".fasta.gz.fai")
449 or os.path.exists(seq_path + ".fasta.gz.gzi")
450 ):
451 rel_seq_path = os.path.join("data", "assembly%s" % fn_try)
413 seq_path = os.path.join(self.outdir, rel_seq_path) 452 seq_path = os.path.join(self.outdir, rel_seq_path)
414 fn_try += 1 453 fn_try += 1
415 454
416 # Find a non-existing label for the new genome 455 # Find a non-existing label for the new genome
417 # (to avoid colision when upgrading an existing instance) 456 # (to avoid colision when upgrading an existing instance)
421 uniq_label = label + str(lab_try) 460 uniq_label = label + str(lab_try)
422 lab_try += 1 461 lab_try += 1
423 462
424 # Find a default scaffold to display 463 # Find a default scaffold to display
425 # TODO this may not be necessary in the future, see https://github.com/GMOD/jbrowse-components/issues/2708 464 # TODO this may not be necessary in the future, see https://github.com/GMOD/jbrowse-components/issues/2708
426 with open(path, 'r') as fa_handle: 465 with open(path, "r") as fa_handle:
427 fa_header = fa_handle.readline()[1:].strip().split(' ')[0] 466 fa_header = fa_handle.readline()[1:].strip().split(" ")[0]
428 467
429 self.assembly_ids[uniq_label] = fa_header 468 self.assembly_ids[uniq_label] = fa_header
430 if default: 469 if default:
431 self.current_assembly_id = uniq_label 470 self.current_assembly_id = uniq_label
432 471
433 copied_genome = seq_path + '.fasta' 472 copied_genome = seq_path + ".fasta"
434 shutil.copy(path, copied_genome) 473 shutil.copy(path, copied_genome)
435 474
436 # Compress with bgzip 475 # Compress with bgzip
437 cmd = ['bgzip', copied_genome] 476 cmd = ["bgzip", copied_genome]
438 self.subprocess_check_call(cmd) 477 self.subprocess_check_call(cmd)
439 478
440 # FAI Index 479 # FAI Index
441 cmd = ['samtools', 'faidx', copied_genome + '.gz'] 480 cmd = ["samtools", "faidx", copied_genome + ".gz"]
442 self.subprocess_check_call(cmd) 481 self.subprocess_check_call(cmd)
443 482
444 self.subprocess_check_call([ 483 self.subprocess_check_call(
445 'jbrowse', 'add-assembly', 484 [
446 '--load', 'inPlace', 485 "jbrowse",
447 '--name', uniq_label, 486 "add-assembly",
448 '--type', 'bgzipFasta', 487 "--load",
449 '--target', os.path.join(self.outdir, 'data'), 488 "inPlace",
450 '--skipCheck', 489 "--name",
451 rel_seq_path + '.fasta.gz']) 490 uniq_label,
491 "--type",
492 "bgzipFasta",
493 "--target",
494 os.path.join(self.outdir, "data"),
495 "--skipCheck",
496 rel_seq_path + ".fasta.gz",
497 ]
498 )
452 499
453 return uniq_label 500 return uniq_label
454 501
455 def text_index(self): 502 def text_index(self):
456 # Index tracks 503 # Index tracks
457 args = [ 504 args = [
458 'jbrowse', 'text-index', 505 "jbrowse",
459 '--target', os.path.join(self.outdir, 'data'), 506 "text-index",
460 '--assemblies', self.current_assembly_id, 507 "--target",
508 os.path.join(self.outdir, "data"),
509 "--assemblies",
510 self.current_assembly_id,
461 ] 511 ]
462 512
463 tracks = ','.join(self.tracksToIndex) 513 tracks = ",".join(self.tracksToIndex)
464 if tracks: 514 if tracks:
465 args += ['--tracks', tracks] 515 args += ["--tracks", tracks]
466 516
467 self.subprocess_check_call(args) 517 self.subprocess_check_call(args)
468 518
469 def _blastxml_to_gff3(self, xml, min_gap=10): 519 def _blastxml_to_gff3(self, xml, min_gap=10):
470 gff3_unrebased = tempfile.NamedTemporaryFile(delete=False) 520 gff3_unrebased = tempfile.NamedTemporaryFile(delete=False)
471 cmd = ['python', os.path.join(INSTALLED_TO, 'blastxml_to_gapped_gff3.py'), 521 cmd = [
472 '--trim', '--trim_end', '--include_seq', '--min_gap', str(min_gap), xml] 522 "python",
473 log.debug('cd %s && %s > %s', self.outdir, ' '.join(cmd), gff3_unrebased.name) 523 os.path.join(INSTALLED_TO, "blastxml_to_gapped_gff3.py"),
524 "--trim",
525 "--trim_end",
526 "--include_seq",
527 "--min_gap",
528 str(min_gap),
529 xml,
530 ]
531 log.debug("cd %s && %s > %s", self.outdir, " ".join(cmd), gff3_unrebased.name)
474 subprocess.check_call(cmd, cwd=self.outdir, stdout=gff3_unrebased) 532 subprocess.check_call(cmd, cwd=self.outdir, stdout=gff3_unrebased)
475 gff3_unrebased.close() 533 gff3_unrebased.close()
476 return gff3_unrebased.name 534 return gff3_unrebased.name
477 535
478 def _prepare_track_style(self, xml_conf): 536 def _prepare_track_style(self, xml_conf):
479 537
480 style_data = { 538 style_data = {"type": "LinearBasicDisplay"}
481 "type": "LinearBasicDisplay" 539
482 } 540 if "display" in xml_conf["style"]:
483 541 style_data["type"] = xml_conf["style"]["display"]
484 if 'display' in xml_conf['style']: 542 del xml_conf["style"]["display"]
485 style_data['type'] = xml_conf['style']['display'] 543
486 del xml_conf['style']['display'] 544 style_data["displayId"] = "%s_%s" % (xml_conf["label"], style_data["type"])
487 545
488 style_data['displayId'] = "%s_%s" % (xml_conf['label'], style_data['type']) 546 style_data.update(xml_conf["style"])
489 547
490 style_data.update(xml_conf['style']) 548 return {"displays": [style_data]}
491
492 return {'displays': [style_data]}
493 549
494 def add_blastxml(self, data, trackData, blastOpts, **kwargs): 550 def add_blastxml(self, data, trackData, blastOpts, **kwargs):
495 gff3 = self._blastxml_to_gff3(data, min_gap=blastOpts['min_gap']) 551 gff3 = self._blastxml_to_gff3(data, min_gap=blastOpts["min_gap"])
496 552
497 if 'parent' in blastOpts and blastOpts['parent'] != 'None': 553 if "parent" in blastOpts and blastOpts["parent"] != "None":
498 gff3_rebased = tempfile.NamedTemporaryFile(delete=False) 554 gff3_rebased = tempfile.NamedTemporaryFile(delete=False)
499 cmd = ['python', os.path.join(INSTALLED_TO, 'gff3_rebase.py')] 555 cmd = ["python", os.path.join(INSTALLED_TO, "gff3_rebase.py")]
500 if blastOpts.get('protein', 'false') == 'true': 556 if blastOpts.get("protein", "false") == "true":
501 cmd.append('--protein2dna') 557 cmd.append("--protein2dna")
502 cmd.extend([os.path.realpath(blastOpts['parent']), gff3]) 558 cmd.extend([os.path.realpath(blastOpts["parent"]), gff3])
503 log.debug('cd %s && %s > %s', self.outdir, ' '.join(cmd), gff3_rebased.name) 559 log.debug("cd %s && %s > %s", self.outdir, " ".join(cmd), gff3_rebased.name)
504 subprocess.check_call(cmd, cwd=self.outdir, stdout=gff3_rebased) 560 subprocess.check_call(cmd, cwd=self.outdir, stdout=gff3_rebased)
505 gff3_rebased.close() 561 gff3_rebased.close()
506 562
507 # Replace original gff3 file 563 # Replace original gff3 file
508 shutil.copy(gff3_rebased.name, gff3) 564 shutil.copy(gff3_rebased.name, gff3)
509 os.unlink(gff3_rebased.name) 565 os.unlink(gff3_rebased.name)
510 566
511 rel_dest = os.path.join('data', trackData['label'] + '.gff') 567 rel_dest = os.path.join("data", trackData["label"] + ".gff")
512 dest = os.path.join(self.outdir, rel_dest) 568 dest = os.path.join(self.outdir, rel_dest)
513 569
514 self._sort_gff(gff3, dest) 570 self._sort_gff(gff3, dest)
515 os.unlink(gff3) 571 os.unlink(gff3)
516 572
517 style_json = self._prepare_track_style(trackData) 573 style_json = self._prepare_track_style(trackData)
518 574
519 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest + '.gz', config=style_json) 575 self._add_track(
576 trackData["label"],
577 trackData["key"],
578 trackData["category"],
579 rel_dest + ".gz",
580 config=style_json,
581 )
520 582
521 def add_bigwig(self, data, trackData, wiggleOpts, **kwargs): 583 def add_bigwig(self, data, trackData, wiggleOpts, **kwargs):
522 584
523 rel_dest = os.path.join('data', trackData['label'] + '.bw') 585 rel_dest = os.path.join("data", trackData["label"] + ".bw")
524 dest = os.path.join(self.outdir, rel_dest) 586 dest = os.path.join(self.outdir, rel_dest)
525 self.symlink_or_copy(os.path.realpath(data), dest) 587 self.symlink_or_copy(os.path.realpath(data), dest)
526 588
527 style_json = self._prepare_track_style(trackData) 589 style_json = self._prepare_track_style(trackData)
528 590
529 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest, config=style_json) 591 self._add_track(
592 trackData["label"],
593 trackData["key"],
594 trackData["category"],
595 rel_dest,
596 config=style_json,
597 )
530 598
531 # Anything ending in "am" (Bam or Cram) 599 # Anything ending in "am" (Bam or Cram)
532 def add_xam(self, data, trackData, xamOpts, index=None, ext="bam", **kwargs): 600 def add_xam(self, data, trackData, xamOpts, index=None, ext="bam", **kwargs):
533 601
534 index_ext = "bai" 602 index_ext = "bai"
535 if ext == "cram": 603 if ext == "cram":
536 index_ext = "crai" 604 index_ext = "crai"
537 605
538 rel_dest = os.path.join('data', trackData['label'] + '.%s' % ext) 606 rel_dest = os.path.join("data", trackData["label"] + ".%s" % ext)
539 dest = os.path.join(self.outdir, rel_dest) 607 dest = os.path.join(self.outdir, rel_dest)
540 608
541 self.symlink_or_copy(os.path.realpath(data), dest) 609 self.symlink_or_copy(os.path.realpath(data), dest)
542 610
543 if index is not None and os.path.exists(os.path.realpath(index)): 611 if index is not None and os.path.exists(os.path.realpath(index)):
544 # xai most probably made by galaxy and stored in galaxy dirs, need to copy it to dest 612 # xai most probably made by galaxy and stored in galaxy dirs, need to copy it to dest
545 self.subprocess_check_call(['cp', os.path.realpath(index), dest + '.%s' % index_ext]) 613 self.subprocess_check_call(
614 ["cp", os.path.realpath(index), dest + ".%s" % index_ext]
615 )
546 else: 616 else:
547 # Can happen in exotic condition 617 # Can happen in exotic condition
548 # e.g. if bam imported as symlink with datatype=unsorted.bam, then datatype changed to bam 618 # e.g. if bam imported as symlink with datatype=unsorted.bam, then datatype changed to bam
549 # => no index generated by galaxy, but there might be one next to the symlink target 619 # => no index generated by galaxy, but there might be one next to the symlink target
550 # this trick allows to skip the bam sorting made by galaxy if already done outside 620 # this trick allows to skip the bam sorting made by galaxy if already done outside
551 if os.path.exists(os.path.realpath(data) + '.%s' % index_ext): 621 if os.path.exists(os.path.realpath(data) + ".%s" % index_ext):
552 self.symlink_or_copy(os.path.realpath(data) + '.%s' % index_ext, dest + '.%s' % index_ext) 622 self.symlink_or_copy(
623 os.path.realpath(data) + ".%s" % index_ext, dest + ".%s" % index_ext
624 )
553 else: 625 else:
554 log.warn('Could not find a bam index (.%s file) for %s', (index_ext, data)) 626 log.warn(
627 "Could not find a bam index (.%s file) for %s", (index_ext, data)
628 )
555 629
556 style_json = self._prepare_track_style(trackData) 630 style_json = self._prepare_track_style(trackData)
557 631
558 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest, config=style_json) 632 self._add_track(
633 trackData["label"],
634 trackData["key"],
635 trackData["category"],
636 rel_dest,
637 config=style_json,
638 )
559 639
560 def add_vcf(self, data, trackData, vcfOpts={}, zipped=False, **kwargs): 640 def add_vcf(self, data, trackData, vcfOpts={}, zipped=False, **kwargs):
561 641
562 if zipped: 642 if zipped:
563 rel_dest = os.path.join('data', trackData['label'] + '.vcf.gz') 643 rel_dest = os.path.join("data", trackData["label"] + ".vcf.gz")
564 dest = os.path.join(self.outdir, rel_dest) 644 dest = os.path.join(self.outdir, rel_dest)
565 shutil.copy(os.path.realpath(data), dest) 645 shutil.copy(os.path.realpath(data), dest)
566 else: 646 else:
567 rel_dest = os.path.join('data', trackData['label'] + '.vcf') 647 rel_dest = os.path.join("data", trackData["label"] + ".vcf")
568 dest = os.path.join(self.outdir, rel_dest) 648 dest = os.path.join(self.outdir, rel_dest)
569 shutil.copy(os.path.realpath(data), dest) 649 shutil.copy(os.path.realpath(data), dest)
570 650
571 cmd = ['bgzip', dest] 651 cmd = ["bgzip", dest]
572 self.subprocess_check_call(cmd) 652 self.subprocess_check_call(cmd)
573 cmd = ['tabix', dest + '.gz'] 653 cmd = ["tabix", dest + ".gz"]
574 self.subprocess_check_call(cmd) 654 self.subprocess_check_call(cmd)
575 655
576 rel_dest = os.path.join('data', trackData['label'] + '.vcf.gz') 656 rel_dest = os.path.join("data", trackData["label"] + ".vcf.gz")
577 657
578 style_json = self._prepare_track_style(trackData) 658 style_json = self._prepare_track_style(trackData)
579 659
580 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest, config=style_json) 660 self._add_track(
661 trackData["label"],
662 trackData["key"],
663 trackData["category"],
664 rel_dest,
665 config=style_json,
666 )
581 667
582 def add_gff(self, data, format, trackData, gffOpts, **kwargs): 668 def add_gff(self, data, format, trackData, gffOpts, **kwargs):
583 rel_dest = os.path.join('data', trackData['label'] + '.gff') 669 rel_dest = os.path.join("data", trackData["label"] + ".gff")
584 dest = os.path.join(self.outdir, rel_dest) 670 dest = os.path.join(self.outdir, rel_dest)
585 671
586 self._sort_gff(data, dest) 672 self._sort_gff(data, dest)
587 673
588 style_json = self._prepare_track_style(trackData) 674 style_json = self._prepare_track_style(trackData)
589 675
590 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest + '.gz', config=style_json) 676 self._add_track(
677 trackData["label"],
678 trackData["key"],
679 trackData["category"],
680 rel_dest + ".gz",
681 config=style_json,
682 )
591 683
592 def add_bed(self, data, format, trackData, gffOpts, **kwargs): 684 def add_bed(self, data, format, trackData, gffOpts, **kwargs):
593 rel_dest = os.path.join('data', trackData['label'] + '.bed') 685 rel_dest = os.path.join("data", trackData["label"] + ".bed")
594 dest = os.path.join(self.outdir, rel_dest) 686 dest = os.path.join(self.outdir, rel_dest)
595 687
596 self._sort_bed(data, dest) 688 self._sort_bed(data, dest)
597 689
598 style_json = self._prepare_track_style(trackData) 690 style_json = self._prepare_track_style(trackData)
599 691
600 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest + '.gz', config=style_json) 692 self._add_track(
693 trackData["label"],
694 trackData["key"],
695 trackData["category"],
696 rel_dest + ".gz",
697 config=style_json,
698 )
601 699
602 def add_paf(self, data, trackData, pafOpts, **kwargs): 700 def add_paf(self, data, trackData, pafOpts, **kwargs):
603 rel_dest = os.path.join('data', trackData['label'] + '.paf') 701 rel_dest = os.path.join("data", trackData["label"] + ".paf")
604 dest = os.path.join(self.outdir, rel_dest) 702 dest = os.path.join(self.outdir, rel_dest)
605 703
606 self.symlink_or_copy(os.path.realpath(data), dest) 704 self.symlink_or_copy(os.path.realpath(data), dest)
607 705
608 added_assembly = self.add_assembly(pafOpts['genome'], pafOpts['genome_label'], default=False) 706 added_assembly = self.add_assembly(
707 pafOpts["genome"], pafOpts["genome_label"], default=False
708 )
609 709
610 style_json = self._prepare_track_style(trackData) 710 style_json = self._prepare_track_style(trackData)
611 711
612 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest, assemblies=[self.current_assembly_id, added_assembly], config=style_json) 712 self._add_track(
713 trackData["label"],
714 trackData["key"],
715 trackData["category"],
716 rel_dest,
717 assemblies=[self.current_assembly_id, added_assembly],
718 config=style_json,
719 )
613 720
614 def add_hic(self, data, trackData, hicOpts, **kwargs): 721 def add_hic(self, data, trackData, hicOpts, **kwargs):
615 rel_dest = os.path.join('data', trackData['label'] + '.hic') 722 rel_dest = os.path.join("data", trackData["label"] + ".hic")
616 dest = os.path.join(self.outdir, rel_dest) 723 dest = os.path.join(self.outdir, rel_dest)
617 724
618 self.symlink_or_copy(os.path.realpath(data), dest) 725 self.symlink_or_copy(os.path.realpath(data), dest)
619 726
620 style_json = self._prepare_track_style(trackData) 727 style_json = self._prepare_track_style(trackData)
621 728
622 self._add_track(trackData['label'], trackData['key'], trackData['category'], rel_dest, config=style_json) 729 self._add_track(
730 trackData["label"],
731 trackData["key"],
732 trackData["category"],
733 rel_dest,
734 config=style_json,
735 )
623 736
624 def add_sparql(self, url, query, query_refnames, trackData): 737 def add_sparql(self, url, query, query_refnames, trackData):
625 738
626 json_track_data = { 739 json_track_data = {
627 "type": "FeatureTrack", 740 "type": "FeatureTrack",
628 "trackId": id, 741 "trackId": id,
629 "name": trackData['label'], 742 "name": trackData["label"],
630 "adapter": { 743 "adapter": {
631 "type": "SPARQLAdapter", 744 "type": "SPARQLAdapter",
632 "endpoint": { 745 "endpoint": {"uri": url, "locationType": "UriLocation"},
633 "uri": url, 746 "queryTemplate": query,
634 "locationType": "UriLocation"
635 },
636 "queryTemplate": query
637 }, 747 },
638 "category": [ 748 "category": [trackData["category"]],
639 trackData['category'] 749 "assemblyNames": [self.current_assembly_id],
640 ], 750 }
641 "assemblyNames": [ 751
642 self.current_assembly_id 752 if query_refnames:
753 json_track_data["adapter"]["refNamesQueryTemplate"]: query_refnames
754
755 self.subprocess_check_call(
756 [
757 "jbrowse",
758 "add-track-json",
759 "--target",
760 os.path.join(self.outdir, "data"),
761 json_track_data,
643 ] 762 ]
644 } 763 )
645
646 if query_refnames:
647 json_track_data['adapter']['refNamesQueryTemplate']: query_refnames
648
649 self.subprocess_check_call([
650 'jbrowse', 'add-track-json',
651 '--target', os.path.join(self.outdir, 'data'),
652 json_track_data])
653 764
654 # Doesn't work as of 1.6.4, might work in the future 765 # Doesn't work as of 1.6.4, might work in the future
655 # self.subprocess_check_call([ 766 # self.subprocess_check_call([
656 # 'jbrowse', 'add-track', 767 # 'jbrowse', 'add-track',
657 # '--trackType', 'sparql', 768 # '--trackType', 'sparql',
664 775
665 def _add_track(self, id, label, category, path, assemblies=[], config=None): 776 def _add_track(self, id, label, category, path, assemblies=[], config=None):
666 777
667 assemblies_opt = self.current_assembly_id 778 assemblies_opt = self.current_assembly_id
668 if assemblies: 779 if assemblies:
669 assemblies_opt = ','.join(assemblies) 780 assemblies_opt = ",".join(assemblies)
670 781
671 cmd = [ 782 cmd = [
672 'jbrowse', 'add-track', 783 "jbrowse",
673 '--load', 'inPlace', 784 "add-track",
674 '--name', label, 785 "--load",
675 '--category', category, 786 "inPlace",
676 '--target', os.path.join(self.outdir, 'data'), 787 "--name",
677 '--trackId', id, 788 label,
678 '--assemblyNames', assemblies_opt 789 "--category",
790 category,
791 "--target",
792 os.path.join(self.outdir, "data"),
793 "--trackId",
794 id,
795 "--assemblyNames",
796 assemblies_opt,
679 ] 797 ]
680 798
681 if config: 799 if config:
682 cmd.append('--config') 800 cmd.append("--config")
683 cmd.append(json.dumps(config)) 801 cmd.append(json.dumps(config))
684 802
685 cmd.append(path) 803 cmd.append(path)
686 804
687 self.subprocess_check_call(cmd) 805 self.subprocess_check_call(cmd)
690 # Only index if not already done 808 # Only index if not already done
691 if not os.path.exists(dest): 809 if not os.path.exists(dest):
692 cmd = "gff3sort.pl --precise '%s' | grep -v \"^$\" > '%s'" % (data, dest) 810 cmd = "gff3sort.pl --precise '%s' | grep -v \"^$\" > '%s'" % (data, dest)
693 self.subprocess_popen(cmd) 811 self.subprocess_popen(cmd)
694 812
695 self.subprocess_check_call(['bgzip', '-f', dest]) 813 self.subprocess_check_call(["bgzip", "-f", dest])
696 self.subprocess_check_call(['tabix', '-f', '-p', 'gff', dest + '.gz']) 814 self.subprocess_check_call(["tabix", "-f", "-p", "gff", dest + ".gz"])
697 815
698 def _sort_bed(self, data, dest): 816 def _sort_bed(self, data, dest):
699 # Only index if not already done 817 # Only index if not already done
700 if not os.path.exists(dest): 818 if not os.path.exists(dest):
701 cmd = ['sort', '-k1,1', '-k2,2n', data] 819 cmd = ["sort", "-k1,1", "-k2,2n", data]
702 with open(dest, 'w') as handle: 820 with open(dest, "w") as handle:
703 self.subprocess_check_call(cmd, output=handle) 821 self.subprocess_check_call(cmd, output=handle)
704 822
705 self.subprocess_check_call(['bgzip', '-f', dest]) 823 self.subprocess_check_call(["bgzip", "-f", dest])
706 self.subprocess_check_call(['tabix', '-f', '-p', 'bed', dest + '.gz']) 824 self.subprocess_check_call(["tabix", "-f", "-p", "bed", dest + ".gz"])
707 825
708 def process_annotations(self, track): 826 def process_annotations(self, track):
709 827
710 category = track['category'].replace('__pd__date__pd__', TODAY) 828 category = track["category"].replace("__pd__date__pd__", TODAY)
711 outputTrackConfig = { 829 outputTrackConfig = {
712 'category': category, 830 "category": category,
713 } 831 }
714 832
715 mapped_chars = { 833 mapped_chars = {
716 '>': '__gt__', 834 ">": "__gt__",
717 '<': '__lt__', 835 "<": "__lt__",
718 "'": '__sq__', 836 "'": "__sq__",
719 '"': '__dq__', 837 '"': "__dq__",
720 '[': '__ob__', 838 "[": "__ob__",
721 ']': '__cb__', 839 "]": "__cb__",
722 '{': '__oc__', 840 "{": "__oc__",
723 '}': '__cc__', 841 "}": "__cc__",
724 '@': '__at__', 842 "@": "__at__",
725 '#': '__pd__', 843 "#": "__pd__",
726 "": '__cn__' 844 "": "__cn__",
727 } 845 }
728 846
729 for i, (dataset_path, dataset_ext, track_human_label, extra_metadata) in enumerate(track['trackfiles']): 847 for i, (
848 dataset_path,
849 dataset_ext,
850 track_human_label,
851 extra_metadata,
852 ) in enumerate(track["trackfiles"]):
730 # Unsanitize labels (element_identifiers are always sanitized by Galaxy) 853 # Unsanitize labels (element_identifiers are always sanitized by Galaxy)
731 for key, value in mapped_chars.items(): 854 for key, value in mapped_chars.items():
732 track_human_label = track_human_label.replace(value, key) 855 track_human_label = track_human_label.replace(value, key)
733 856
734 log.info('Processing track %s / %s (%s)', category, track_human_label, dataset_ext) 857 log.info(
735 outputTrackConfig['key'] = track_human_label 858 "Processing track %s / %s (%s)",
859 category,
860 track_human_label,
861 dataset_ext,
862 )
863 outputTrackConfig["key"] = track_human_label
736 # We add extra data to hash for the case of REST + SPARQL. 864 # We add extra data to hash for the case of REST + SPARQL.
737 if 'conf' in track and 'options' in track['conf'] and 'url' in track['conf']['options']: 865 if (
738 rest_url = track['conf']['options']['url'] 866 "conf" in track
867 and "options" in track["conf"]
868 and "url" in track["conf"]["options"]
869 ):
870 rest_url = track["conf"]["options"]["url"]
739 else: 871 else:
740 rest_url = '' 872 rest_url = ""
741 873
742 # I chose to use track['category'] instead of 'category' here. This 874 # I chose to use track['category'] instead of 'category' here. This
743 # is intentional. This way re-running the tool on a different date 875 # is intentional. This way re-running the tool on a different date
744 # will not generate different hashes and make comparison of outputs 876 # will not generate different hashes and make comparison of outputs
745 # much simpler. 877 # much simpler.
746 hashData = [str(dataset_path), track_human_label, track['category'], rest_url, self.current_assembly_id] 878 hashData = [
747 hashData = '|'.join(hashData).encode('utf-8') 879 str(dataset_path),
748 outputTrackConfig['label'] = hashlib.md5(hashData).hexdigest() + '_%s' % i 880 track_human_label,
749 outputTrackConfig['metadata'] = extra_metadata 881 track["category"],
750 882 rest_url,
751 outputTrackConfig['style'] = track['style'] 883 self.current_assembly_id,
752 884 ]
753 if 'menus' in track['conf']['options']: 885 hashData = "|".join(hashData).encode("utf-8")
754 menus = self.cs.parse_menus(track['conf']['options']) 886 outputTrackConfig["label"] = hashlib.md5(hashData).hexdigest() + "_%s" % i
887 outputTrackConfig["metadata"] = extra_metadata
888
889 outputTrackConfig["style"] = track["style"]
890
891 if "menus" in track["conf"]["options"]:
892 menus = self.cs.parse_menus(track["conf"]["options"])
755 outputTrackConfig.update(menus) 893 outputTrackConfig.update(menus)
756 894
757 if dataset_ext in ('gff', 'gff3'): 895 if dataset_ext in ("gff", "gff3"):
758 self.add_gff(dataset_path, dataset_ext, outputTrackConfig, 896 self.add_gff(
759 track['conf']['options']['gff']) 897 dataset_path,
760 elif dataset_ext == 'bed': 898 dataset_ext,
761 self.add_bed(dataset_path, dataset_ext, outputTrackConfig, 899 outputTrackConfig,
762 track['conf']['options']['gff']) 900 track["conf"]["options"]["gff"],
763 elif dataset_ext == 'bigwig': 901 )
764 self.add_bigwig(dataset_path, outputTrackConfig, 902 elif dataset_ext == "bed":
765 track['conf']['options']['wiggle']) 903 self.add_bed(
766 elif dataset_ext == 'bam': 904 dataset_path,
767 real_indexes = track['conf']['options']['pileup']['bam_indices']['bam_index'] 905 dataset_ext,
906 outputTrackConfig,
907 track["conf"]["options"]["gff"],
908 )
909 elif dataset_ext == "bigwig":
910 self.add_bigwig(
911 dataset_path, outputTrackConfig, track["conf"]["options"]["wiggle"]
912 )
913 elif dataset_ext == "bam":
914 real_indexes = track["conf"]["options"]["pileup"]["bam_indices"][
915 "bam_index"
916 ]
768 if not isinstance(real_indexes, list): 917 if not isinstance(real_indexes, list):
769 # <bam_indices> 918 # <bam_indices>
770 # <bam_index>/path/to/a.bam.bai</bam_index> 919 # <bam_index>/path/to/a.bam.bai</bam_index>
771 # </bam_indices> 920 # </bam_indices>
772 # 921 #
773 # The above will result in the 'bam_index' key containing a 922 # The above will result in the 'bam_index' key containing a
774 # string. If there are two or more indices, the container 923 # string. If there are two or more indices, the container
775 # becomes a list. Fun! 924 # becomes a list. Fun!
776 real_indexes = [real_indexes] 925 real_indexes = [real_indexes]
777 926
778 self.add_xam(dataset_path, outputTrackConfig, 927 self.add_xam(
779 track['conf']['options']['pileup'], 928 dataset_path,
780 index=real_indexes[i], ext="bam") 929 outputTrackConfig,
781 elif dataset_ext == 'cram': 930 track["conf"]["options"]["pileup"],
782 real_indexes = track['conf']['options']['cram']['cram_indices']['cram_index'] 931 index=real_indexes[i],
932 ext="bam",
933 )
934 elif dataset_ext == "cram":
935 real_indexes = track["conf"]["options"]["cram"]["cram_indices"][
936 "cram_index"
937 ]
783 if not isinstance(real_indexes, list): 938 if not isinstance(real_indexes, list):
784 # <bam_indices> 939 # <bam_indices>
785 # <bam_index>/path/to/a.bam.bai</bam_index> 940 # <bam_index>/path/to/a.bam.bai</bam_index>
786 # </bam_indices> 941 # </bam_indices>
787 # 942 #
788 # The above will result in the 'bam_index' key containing a 943 # The above will result in the 'bam_index' key containing a
789 # string. If there are two or more indices, the container 944 # string. If there are two or more indices, the container
790 # becomes a list. Fun! 945 # becomes a list. Fun!
791 real_indexes = [real_indexes] 946 real_indexes = [real_indexes]
792 947
793 self.add_xam(dataset_path, outputTrackConfig, 948 self.add_xam(
794 track['conf']['options']['cram'], 949 dataset_path,
795 index=real_indexes[i], ext="cram") 950 outputTrackConfig,
796 elif dataset_ext == 'blastxml': 951 track["conf"]["options"]["cram"],
797 self.add_blastxml(dataset_path, outputTrackConfig, track['conf']['options']['blast']) 952 index=real_indexes[i],
798 elif dataset_ext == 'vcf': 953 ext="cram",
954 )
955 elif dataset_ext == "blastxml":
956 self.add_blastxml(
957 dataset_path, outputTrackConfig, track["conf"]["options"]["blast"]
958 )
959 elif dataset_ext == "vcf":
799 self.add_vcf(dataset_path, outputTrackConfig) 960 self.add_vcf(dataset_path, outputTrackConfig)
800 elif dataset_ext == 'vcf_bgzip': 961 elif dataset_ext == "vcf_bgzip":
801 self.add_vcf(dataset_path, outputTrackConfig, zipped=True) 962 self.add_vcf(dataset_path, outputTrackConfig, zipped=True)
802 elif dataset_ext == 'rest': 963 elif dataset_ext == "rest":
803 self.add_rest(track['conf']['options']['rest']['url'], outputTrackConfig) 964 self.add_rest(
804 elif dataset_ext == 'synteny': 965 track["conf"]["options"]["rest"]["url"], outputTrackConfig
805 self.add_paf(dataset_path, outputTrackConfig, 966 )
806 track['conf']['options']['synteny']) 967 elif dataset_ext == "synteny":
807 elif dataset_ext == 'hic': 968 self.add_paf(
808 self.add_hic(dataset_path, outputTrackConfig, 969 dataset_path, outputTrackConfig, track["conf"]["options"]["synteny"]
809 track['conf']['options']['hic']) 970 )
810 elif dataset_ext == 'sparql': 971 elif dataset_ext == "hic":
811 sparql_query = track['conf']['options']['sparql']['query'] 972 self.add_hic(
973 dataset_path, outputTrackConfig, track["conf"]["options"]["hic"]
974 )
975 elif dataset_ext == "sparql":
976 sparql_query = track["conf"]["options"]["sparql"]["query"]
812 for key, value in mapped_chars.items(): 977 for key, value in mapped_chars.items():
813 sparql_query = sparql_query.replace(value, key) 978 sparql_query = sparql_query.replace(value, key)
814 sparql_query_refnames = track['conf']['options']['sparql']['query_refnames'] 979 sparql_query_refnames = track["conf"]["options"]["sparql"][
980 "query_refnames"
981 ]
815 for key, value in mapped_chars.items(): 982 for key, value in mapped_chars.items():
816 sparql_query_refnames = sparql_query_refnames.replace(value, key) 983 sparql_query_refnames = sparql_query_refnames.replace(value, key)
817 self.add_sparql(track['conf']['options']['sparql']['url'], sparql_query, sparql_query_refnames, outputTrackConfig) 984 self.add_sparql(
985 track["conf"]["options"]["sparql"]["url"],
986 sparql_query,
987 sparql_query_refnames,
988 outputTrackConfig,
989 )
818 else: 990 else:
819 log.warn('Do not know how to handle %s', dataset_ext) 991 log.warn("Do not know how to handle %s", dataset_ext)
820 992
821 # Return non-human label for use in other fields 993 # Return non-human label for use in other fields
822 yield outputTrackConfig['label'] 994 yield outputTrackConfig["label"]
823 995
824 def add_default_session(self, data): 996 def add_default_session(self, data):
825 """ 997 """
826 Add some default session settings: set some assemblies/tracks on/off 998 Add some default session settings: set some assemblies/tracks on/off
827 """ 999 """
828 tracks_data = [] 1000 tracks_data = []
829 1001
830 # TODO using the default session for now, but check out session specs in the future https://github.com/GMOD/jbrowse-components/issues/2708 1002 # TODO using the default session for now, but check out session specs in the future https://github.com/GMOD/jbrowse-components/issues/2708
831 1003
832 # We need to know the track type from the config.json generated just before 1004 # We need to know the track type from the config.json generated just before
833 config_path = os.path.join(self.outdir, 'data', 'config.json') 1005 config_path = os.path.join(self.outdir, "data", "config.json")
834 track_types = {} 1006 track_types = {}
835 with open(config_path, 'r') as config_file: 1007 with open(config_path, "r") as config_file:
836 config_json = json.load(config_file) 1008 config_json = json.load(config_file)
837 1009
838 for track_conf in config_json['tracks']: 1010 for track_conf in config_json["tracks"]:
839 track_types[track_conf['trackId']] = track_conf['type'] 1011 track_types[track_conf["trackId"]] = track_conf["type"]
840 1012
841 for on_track in data['visibility']['default_on']: 1013 for on_track in data["visibility"]["default_on"]:
842 # TODO several problems with this currently 1014 # TODO several problems with this currently
843 # - we are forced to copy the same kind of style config as the per track config from _prepare_track_style (not exactly the same though) 1015 # - we are forced to copy the same kind of style config as the per track config from _prepare_track_style (not exactly the same though)
844 # - we get an error when refreshing the page 1016 # - we get an error when refreshing the page
845 # - this could be solved by session specs, see https://github.com/GMOD/jbrowse-components/issues/2708 1017 # - this could be solved by session specs, see https://github.com/GMOD/jbrowse-components/issues/2708
846 style_data = { 1018 style_data = {"type": "LinearBasicDisplay", "height": 100}
847 "type": "LinearBasicDisplay", 1019
848 "height": 100 1020 if on_track in data["style"]:
849 } 1021 if "display" in data["style"][on_track]:
850 1022 style_data["type"] = data["style"][on_track]["display"]
851 if on_track in data['style']: 1023 del data["style"][on_track]["display"]
852 if 'display' in data['style'][on_track]: 1024
853 style_data['type'] = data['style'][on_track]['display'] 1025 style_data.update(data["style"][on_track])
854 del data['style'][on_track]['display'] 1026
855 1027 if on_track in data["style_labels"]:
856 style_data.update(data['style'][on_track])
857
858 if on_track in data['style_labels']:
859 # TODO fix this: it should probably go in a renderer block (SvgFeatureRenderer) but still does not work 1028 # TODO fix this: it should probably go in a renderer block (SvgFeatureRenderer) but still does not work
860 # TODO move this to per track displays? 1029 # TODO move this to per track displays?
861 style_data['labels'] = data['style_labels'][on_track] 1030 style_data["labels"] = data["style_labels"][on_track]
862 1031
863 tracks_data.append({ 1032 tracks_data.append(
864 "type": track_types[on_track], 1033 {
865 "configuration": on_track, 1034 "type": track_types[on_track],
866 "displays": [ 1035 "configuration": on_track,
867 style_data 1036 "displays": [style_data],
868 ] 1037 }
869 }) 1038 )
870 1039
871 # The view for the assembly we're adding 1040 # The view for the assembly we're adding
872 view_json = { 1041 view_json = {"type": "LinearGenomeView", "tracks": tracks_data}
873 "type": "LinearGenomeView",
874 "tracks": tracks_data
875 }
876 1042
877 refName = None 1043 refName = None
878 if data.get('defaultLocation', ''): 1044 if data.get("defaultLocation", ""):
879 loc_match = re.search(r'^(\w+):(\d+)\.+(\d+)$', data['defaultLocation']) 1045 loc_match = re.search(r"^(\w+):(\d+)\.+(\d+)$", data["defaultLocation"])
880 if loc_match: 1046 if loc_match:
881 refName = loc_match.group(1) 1047 refName = loc_match.group(1)
882 start = int(loc_match.group(2)) 1048 start = int(loc_match.group(2))
883 end = int(loc_match.group(3)) 1049 end = int(loc_match.group(3))
884 elif self.assembly_ids[self.current_assembly_id] is not None: 1050 elif self.assembly_ids[self.current_assembly_id] is not None:
886 start = 0 1052 start = 0
887 end = 1000000 # Booh, hard coded! waiting for https://github.com/GMOD/jbrowse-components/issues/2708 1053 end = 1000000 # Booh, hard coded! waiting for https://github.com/GMOD/jbrowse-components/issues/2708
888 1054
889 if refName is not None: 1055 if refName is not None:
890 # TODO displayedRegions is not just zooming to the region, it hides the rest of the chromosome 1056 # TODO displayedRegions is not just zooming to the region, it hides the rest of the chromosome
891 view_json['displayedRegions'] = [{ 1057 view_json["displayedRegions"] = [
892 "refName": refName, 1058 {
893 "start": start, 1059 "refName": refName,
894 "end": end, 1060 "start": start,
895 "reversed": False, 1061 "end": end,
896 "assemblyName": self.current_assembly_id 1062 "reversed": False,
897 }] 1063 "assemblyName": self.current_assembly_id,
898 1064 }
899 session_name = data.get('session_name', "New session") 1065 ]
1066
1067 session_name = data.get("session_name", "New session")
900 if not session_name: 1068 if not session_name:
901 session_name = "New session" 1069 session_name = "New session"
902 1070
903 # Merge with possibly existing defaultSession (if upgrading a jbrowse instance) 1071 # Merge with possibly existing defaultSession (if upgrading a jbrowse instance)
904 session_json = {} 1072 session_json = {}
905 if 'defaultSession' in config_json: 1073 if "defaultSession" in config_json:
906 session_json = config_json['defaultSession'] 1074 session_json = config_json["defaultSession"]
907 1075
908 session_json["name"] = session_name 1076 session_json["name"] = session_name
909 1077
910 if 'views' not in session_json: 1078 if "views" not in session_json:
911 session_json['views'] = [] 1079 session_json["views"] = []
912 1080
913 session_json['views'].append(view_json) 1081 session_json["views"].append(view_json)
914 1082
915 config_json['defaultSession'] = session_json 1083 config_json["defaultSession"] = session_json
916 1084
917 with open(config_path, 'w') as config_file: 1085 with open(config_path, "w") as config_file:
918 json.dump(config_json, config_file, indent=2) 1086 json.dump(config_json, config_file, indent=2)
919 1087
920 def add_general_configuration(self, data): 1088 def add_general_configuration(self, data):
921 """ 1089 """
922 Add some general configuration to the config.json file 1090 Add some general configuration to the config.json file
923 """ 1091 """
924 1092
925 config_path = os.path.join(self.outdir, 'data', 'config.json') 1093 config_path = os.path.join(self.outdir, "data", "config.json")
926 with open(config_path, 'r') as config_file: 1094 with open(config_path, "r") as config_file:
927 config_json = json.load(config_file) 1095 config_json = json.load(config_file)
928 1096
929 config_data = {} 1097 config_data = {}
930 1098
931 config_data['disableAnalytics'] = data.get('analytics', 'false') == 'true' 1099 config_data["disableAnalytics"] = data.get("analytics", "false") == "true"
932 1100
933 config_data['theme'] = { 1101 config_data["theme"] = {
934 "palette": { 1102 "palette": {
935 "primary": { 1103 "primary": {"main": data.get("primary_color", "#0D233F")},
936 "main": data.get('primary_color', '#0D233F') 1104 "secondary": {"main": data.get("secondary_color", "#721E63")},
937 }, 1105 "tertiary": {"main": data.get("tertiary_color", "#135560")},
938 "secondary": { 1106 "quaternary": {"main": data.get("quaternary_color", "#FFB11D")},
939 "main": data.get('secondary_color', '#721E63')
940 },
941 "tertiary": {
942 "main": data.get('tertiary_color', '#135560')
943 },
944 "quaternary": {
945 "main": data.get('quaternary_color', '#FFB11D')
946 },
947 }, 1107 },
948 "typography": { 1108 "typography": {"fontSize": int(data.get("font_size", 10))},
949 "fontSize": int(data.get('font_size', 10))
950 },
951 } 1109 }
952 1110
953 config_json['configuration'].update(config_data) 1111 config_json["configuration"].update(config_data)
954 1112
955 with open(config_path, 'w') as config_file: 1113 with open(config_path, "w") as config_file:
956 json.dump(config_json, config_file, indent=2) 1114 json.dump(config_json, config_file, indent=2)
957 1115
958 def clone_jbrowse(self, jbrowse_dir, destination): 1116 def clone_jbrowse(self, jbrowse_dir, destination):
959 """Clone a JBrowse directory into a destination directory. 1117 """Clone a JBrowse directory into a destination directory."""
960 """
961 1118
962 copytree(jbrowse_dir, destination) 1119 copytree(jbrowse_dir, destination)
963 1120
964 try: 1121 try:
965 shutil.rmtree(os.path.join(destination, 'test_data')) 1122 shutil.rmtree(os.path.join(destination, "test_data"))
966 except OSError as e: 1123 except OSError as e:
967 log.error("Error: %s - %s." % (e.filename, e.strerror)) 1124 log.error("Error: %s - %s." % (e.filename, e.strerror))
968 1125
969 if not os.path.exists(os.path.join(destination, 'data')): 1126 if not os.path.exists(os.path.join(destination, "data")):
970 # It can already exist if upgrading an instance 1127 # It can already exist if upgrading an instance
971 os.makedirs(os.path.join(destination, 'data')) 1128 os.makedirs(os.path.join(destination, "data"))
972 log.info("makedir %s" % (os.path.join(destination, 'data'))) 1129 log.info("makedir %s" % (os.path.join(destination, "data")))
973 1130
974 os.symlink('./data/config.json', os.path.join(destination, 'config.json')) 1131 os.symlink("./data/config.json", os.path.join(destination, "config.json"))
975 1132
976 1133
977 def copytree(src, dst, symlinks=False, ignore=None): 1134 def copytree(src, dst, symlinks=False, ignore=None):
978 for item in os.listdir(src): 1135 for item in os.listdir(src):
979 s = os.path.join(src, item) 1136 s = os.path.join(src, item)
983 else: 1140 else:
984 shutil.copy2(s, d) 1141 shutil.copy2(s, d)
985 1142
986 1143
987 def parse_style_conf(item): 1144 def parse_style_conf(item):
988 if 'type' in item.attrib and item.attrib['type'] in ['boolean', 'integer']: 1145 if "type" in item.attrib and item.attrib["type"] in ["boolean", "integer"]:
989 if item.attrib['type'] == 'boolean': 1146 if item.attrib["type"] == "boolean":
990 return item.text in ("yes", "true", "True") 1147 return item.text in ("yes", "true", "True")
991 elif item.attrib['type'] == 'integer': 1148 elif item.attrib["type"] == "integer":
992 return int(item.text) 1149 return int(item.text)
993 else: 1150 else:
994 return item.text 1151 return item.text
995 1152
996 1153
997 if __name__ == '__main__': 1154 if __name__ == "__main__":
998 parser = argparse.ArgumentParser(description="", epilog="") 1155 parser = argparse.ArgumentParser(description="", epilog="")
999 parser.add_argument('xml', type=argparse.FileType('r'), help='Track Configuration') 1156 parser.add_argument("xml", type=argparse.FileType("r"), help="Track Configuration")
1000 1157
1001 parser.add_argument('--jbrowse', help='Folder containing a jbrowse release') 1158 parser.add_argument("--jbrowse", help="Folder containing a jbrowse release")
1002 parser.add_argument('--outdir', help='Output directory', default='out') 1159 parser.add_argument("--outdir", help="Output directory", default="out")
1003 parser.add_argument('--version', '-V', action='version', version="%(prog)s 0.8.0") 1160 parser.add_argument("--version", "-V", action="version", version="%(prog)s 0.8.0")
1004 args = parser.parse_args() 1161 args = parser.parse_args()
1005 1162
1006 tree = ET.parse(args.xml.name) 1163 tree = ET.parse(args.xml.name)
1007 root = tree.getroot() 1164 root = tree.getroot()
1008 1165
1009 # This should be done ASAP 1166 # This should be done ASAP
1010 GALAXY_INFRASTRUCTURE_URL = root.find('metadata/galaxyUrl').text 1167 GALAXY_INFRASTRUCTURE_URL = root.find("metadata/galaxyUrl").text
1011 # Sometimes this comes as `localhost` without a protocol 1168 # Sometimes this comes as `localhost` without a protocol
1012 if not GALAXY_INFRASTRUCTURE_URL.startswith('http'): 1169 if not GALAXY_INFRASTRUCTURE_URL.startswith("http"):
1013 # so we'll prepend `http://` and hope for the best. Requests *should* 1170 # so we'll prepend `http://` and hope for the best. Requests *should*
1014 # be GET and not POST so it should redirect OK 1171 # be GET and not POST so it should redirect OK
1015 GALAXY_INFRASTRUCTURE_URL = 'http://' + GALAXY_INFRASTRUCTURE_URL 1172 GALAXY_INFRASTRUCTURE_URL = "http://" + GALAXY_INFRASTRUCTURE_URL
1016 1173
1017 jc = JbrowseConnector( 1174 jc = JbrowseConnector(
1018 jbrowse=args.jbrowse, 1175 jbrowse=args.jbrowse,
1019 outdir=args.outdir, 1176 outdir=args.outdir,
1020 genomes=[ 1177 genomes=[
1021 { 1178 {
1022 'path': os.path.realpath(x.attrib['path']), 1179 "path": os.path.realpath(x.attrib["path"]),
1023 'meta': metadata_from_node(x.find('metadata')), 1180 "meta": metadata_from_node(x.find("metadata")),
1024 'label': x.attrib['label'] 1181 "label": x.attrib["label"],
1025 } 1182 }
1026 for x in root.findall('metadata/genomes/genome') 1183 for x in root.findall("metadata/genomes/genome")
1027 ] 1184 ],
1028 ) 1185 )
1029 1186
1030 default_session_data = { 1187 default_session_data = {
1031 'visibility': { 1188 "visibility": {
1032 'default_on': [], 1189 "default_on": [],
1033 'default_off': [], 1190 "default_off": [],
1034 }, 1191 },
1035 'style': {}, 1192 "style": {},
1036 'style_labels': {} 1193 "style_labels": {},
1037 } 1194 }
1038 1195
1039 # TODO add metadata to tracks 1196 # TODO add metadata to tracks
1040 for track in root.findall('tracks/track'): 1197 for track in root.findall("tracks/track"):
1041 track_conf = {} 1198 track_conf = {}
1042 track_conf['trackfiles'] = [] 1199 track_conf["trackfiles"] = []
1043 1200
1044 trackfiles = track.findall('files/trackFile') 1201 trackfiles = track.findall("files/trackFile")
1045 if trackfiles: 1202 if trackfiles:
1046 for x in track.findall('files/trackFile'): 1203 for x in track.findall("files/trackFile"):
1047 if trackfiles: 1204 if trackfiles:
1048 metadata = metadata_from_node(x.find('metadata')) 1205 metadata = metadata_from_node(x.find("metadata"))
1049 1206
1050 track_conf['trackfiles'].append(( 1207 track_conf["trackfiles"].append(
1051 os.path.realpath(x.attrib['path']), 1208 (
1052 x.attrib['ext'], 1209 os.path.realpath(x.attrib["path"]),
1053 x.attrib['label'], 1210 x.attrib["ext"],
1054 metadata 1211 x.attrib["label"],
1055 )) 1212 metadata,
1213 )
1214 )
1056 else: 1215 else:
1057 # For tracks without files (rest, sparql) 1216 # For tracks without files (rest, sparql)
1058 track_conf['trackfiles'].append(( 1217 track_conf["trackfiles"].append(
1059 '', # N/A, no path for rest or sparql 1218 (
1060 track.attrib['format'], 1219 "", # N/A, no path for rest or sparql
1061 track.find('options/label').text, 1220 track.attrib["format"],
1062 {} 1221 track.find("options/label").text,
1063 )) 1222 {},
1064 1223 )
1065 track_conf['category'] = track.attrib['cat'] 1224 )
1066 track_conf['format'] = track.attrib['format'] 1225
1067 track_conf['style'] = {item.tag: parse_style_conf(item) for item in track.find('options/style')} 1226 track_conf["category"] = track.attrib["cat"]
1068 1227 track_conf["format"] = track.attrib["format"]
1069 track_conf['style'] = {item.tag: parse_style_conf(item) for item in track.find('options/style')} 1228 track_conf["style"] = {
1070 1229 item.tag: parse_style_conf(item) for item in track.find("options/style")
1071 track_conf['style_labels'] = {item.tag: parse_style_conf(item) for item in track.find('options/style_labels')} 1230 }
1072 1231
1073 track_conf['conf'] = etree_to_dict(track.find('options')) 1232 track_conf["style"] = {
1233 item.tag: parse_style_conf(item) for item in track.find("options/style")
1234 }
1235
1236 track_conf["style_labels"] = {
1237 item.tag: parse_style_conf(item)
1238 for item in track.find("options/style_labels")
1239 }
1240
1241 track_conf["conf"] = etree_to_dict(track.find("options"))
1074 keys = jc.process_annotations(track_conf) 1242 keys = jc.process_annotations(track_conf)
1075 1243
1076 for key in keys: 1244 for key in keys:
1077 default_session_data['visibility'][track.attrib.get('visibility', 'default_off')].append(key) 1245 default_session_data["visibility"][
1078 1246 track.attrib.get("visibility", "default_off")
1079 default_session_data['style'][key] = track_conf['style'] # TODO do we need this anymore? 1247 ].append(key)
1080 default_session_data['style_labels'][key] = track_conf['style_labels'] 1248
1081 1249 default_session_data["style"][key] = track_conf[
1082 default_session_data['defaultLocation'] = root.find('metadata/general/defaultLocation').text 1250 "style"
1083 default_session_data['session_name'] = root.find('metadata/general/session_name').text 1251 ] # TODO do we need this anymore?
1252 default_session_data["style_labels"][key] = track_conf["style_labels"]
1253
1254 default_session_data["defaultLocation"] = root.find(
1255 "metadata/general/defaultLocation"
1256 ).text
1257 default_session_data["session_name"] = root.find(
1258 "metadata/general/session_name"
1259 ).text
1084 1260
1085 general_data = { 1261 general_data = {
1086 'analytics': root.find('metadata/general/analytics').text, 1262 "analytics": root.find("metadata/general/analytics").text,
1087 'primary_color': root.find('metadata/general/primary_color').text, 1263 "primary_color": root.find("metadata/general/primary_color").text,
1088 'secondary_color': root.find('metadata/general/secondary_color').text, 1264 "secondary_color": root.find("metadata/general/secondary_color").text,
1089 'tertiary_color': root.find('metadata/general/tertiary_color').text, 1265 "tertiary_color": root.find("metadata/general/tertiary_color").text,
1090 'quaternary_color': root.find('metadata/general/quaternary_color').text, 1266 "quaternary_color": root.find("metadata/general/quaternary_color").text,
1091 'font_size': root.find('metadata/general/font_size').text, 1267 "font_size": root.find("metadata/general/font_size").text,
1092 } 1268 }
1093 1269
1094 jc.add_default_session(default_session_data) 1270 jc.add_default_session(default_session_data)
1095 jc.add_general_configuration(general_data) 1271 jc.add_general_configuration(general_data)
1096 jc.text_index() 1272 jc.text_index()
1097