Mercurial > repos > fubar > jbrowse2
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 |