comparison blast2html.py @ 53:4217bb9cf1d3

depend on python 3; fix internal links with multiple iterations
author Jan Kanis <jan.code@jankanis.nl>
date Mon, 26 May 2014 13:07:13 +0200
parents b15a20c2372a
children 19c48f2ec775
comparison
equal deleted inserted replaced
52:d6c7b5de2833 53:4217bb9cf1d3
1 #!/usr/bin/env python3 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 #
4 # Actually runs under either python 2 or 3
5 3
6 # Copyright The Hyve B.V. 2014 4 # Copyright The Hyve B.V. 2014
7 # License: GPL version 3 or higher 5 # License: GPL version 3 or higher
8 6
9 from __future__ import unicode_literals 7 from __future__ import unicode_literals
11 import sys 9 import sys
12 import math 10 import math
13 import warnings 11 import warnings
14 from os import path 12 from os import path
15 from itertools import repeat 13 from itertools import repeat
16 import six
17 import argparse 14 import argparse
18 from lxml import objectify 15 from lxml import objectify
19 import jinja2 16 import jinja2
20 17
21 18
22 19
23 _filters = {} 20 _filters = {}
24 def filter(func_or_name): 21 def filter(func_or_name):
25 "Decorator to register a function as filter in the current jinja environment" 22 "Decorator to register a function as filter in the current jinja environment"
26 if isinstance(func_or_name, six.string_types): 23 if isinstance(func_or_name, str):
27 def inner(func): 24 def inner(func):
28 _filters[func_or_name] = func.__name__ 25 _filters[func_or_name] = func.__name__
29 return func 26 return func
30 return inner 27 return inner
31 else: 28 else:
97 if node.tag == 'Hsp': 94 if node.tag == 'Hsp':
98 return int(node['Hsp_align-len']) 95 return int(node['Hsp_align-len'])
99 elif node.tag == 'Iteration': 96 elif node.tag == 'Iteration':
100 return int(node['Iteration_query-len']) 97 return int(node['Iteration_query-len'])
101 raise Exception("Unknown XML node type: "+node.tag) 98 raise Exception("Unknown XML node type: "+node.tag)
102 99
103 100 @filter
101 def nodeid(node):
102 id = []
103 if node.tag == 'Hsp':
104 id.insert(0, node.Hsp_num.text)
105 node = node.getparent().getparent()
106 assert node.tag == 'Hit'
107 if node.tag == 'Hit':
108 id.insert(0, node.Hit_num.text)
109 node = node.getparent().getparent()
110 assert node.tag == 'Iteration'
111 if node.tag == 'Iteration':
112 id.insert(0, node['Iteration_iter-num'].text)
113 return '-'.join(id)
114 raise ValueError("The nodeid filter can only be applied to Hsp, Hit or Iteration nodes in a BlastXML document")
115
116
104 @filter 117 @filter
105 def asframe(frame): 118 def asframe(frame):
106 if frame == 1: 119 if frame == 1:
107 return 'Plus' 120 return 'Plus'
108 elif frame == -1: 121 elif frame == -1:
172 def __init__(self, input, templatedir, templatename): 185 def __init__(self, input, templatedir, templatename):
173 self.input = input 186 self.input = input
174 self.templatename = templatename 187 self.templatename = templatename
175 188
176 self.blast = objectify.parse(self.input).getroot() 189 self.blast = objectify.parse(self.input).getroot()
177 self.loader = jinja2.FileSystemLoader(searchpath=templatedir, encoding='utf-8') 190 self.loader = jinja2.FileSystemLoader(searchpath=templatedir)
178 self.environment = jinja2.Environment(loader=self.loader, 191 self.environment = jinja2.Environment(loader=self.loader,
179 lstrip_blocks=True, trim_blocks=True, autoescape=True) 192 lstrip_blocks=True, trim_blocks=True, autoescape=True)
180 193
181 self._addfilters(self.environment) 194 self._addfilters(self.environment)
182 195
236 matches.append((count * percent_multiplier, self.colors[last] if last != 255 else 'transparent')) 249 matches.append((count * percent_multiplier, self.colors[last] if last != 255 else 'transparent'))
237 last = table[i] 250 last = table[i]
238 count = 1 251 count = 1
239 matches.append((count * percent_multiplier, self.colors[last] if last != 255 else 'transparent')) 252 matches.append((count * percent_multiplier, self.colors[last] if last != 255 else 'transparent'))
240 253
241 yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit)) 254 yield dict(colors=matches, hit=hit, defline=firsttitle(hit))
242 255
243 @filter 256 @filter
244 def queryscale(self, result): 257 def queryscale(self, result):
245 query_length = blastxml_len(result) 258 query_length = blastxml_len(result)
246 skip = math.ceil(query_length / self.max_scale_labels) 259 skip = math.ceil(query_length / self.max_scale_labels)
267 280
268 def hsp_val(path): 281 def hsp_val(path):
269 return (float(hsp[path]) for hsp in hsps) 282 return (float(hsp[path]) for hsp in hsps)
270 283
271 yield dict(hit = hit, 284 yield dict(hit = hit,
272 title = firsttitle(hit), 285 title = firsttitle(hit),
273 link_id = hit.Hit_num,
274 maxscore = "{:.1f}".format(max(hsp_val('Hsp_bit-score'))), 286 maxscore = "{:.1f}".format(max(hsp_val('Hsp_bit-score'))),
275 totalscore = "{:.1f}".format(sum(hsp_val('Hsp_bit-score'))), 287 totalscore = "{:.1f}".format(sum(hsp_val('Hsp_bit-score'))),
276 cover = "{:.0%}".format(cover_count / query_length), 288 cover = "{:.0%}".format(cover_count / query_length),
277 e_value = "{:.4g}".format(min(hsp_val('Hsp_evalue'))), 289 e_value = "{:.4g}".format(min(hsp_val('Hsp_evalue'))),
278 # FIXME: is this the correct formula vv? 290 # FIXME: is this the correct formula vv?
286 parser = argparse.ArgumentParser(description="Convert a BLAST XML result into a nicely readable html page", 298 parser = argparse.ArgumentParser(description="Convert a BLAST XML result into a nicely readable html page",
287 usage="{} [-i] INPUT [-o OUTPUT]".format(sys.argv[0])) 299 usage="{} [-i] INPUT [-o OUTPUT]".format(sys.argv[0]))
288 input_group = parser.add_mutually_exclusive_group(required=True) 300 input_group = parser.add_mutually_exclusive_group(required=True)
289 input_group.add_argument('positional_arg', metavar='INPUT', nargs='?', type=argparse.FileType(mode='r'), 301 input_group.add_argument('positional_arg', metavar='INPUT', nargs='?', type=argparse.FileType(mode='r'),
290 help='The input Blast XML file, same as -i/--input') 302 help='The input Blast XML file, same as -i/--input')
291 input_group.add_argument('-i', '--input', type=argparse.FileType(mode='r', encoding='utf-8'), 303 input_group.add_argument('-i', '--input', type=argparse.FileType(mode='r'),
292 help='The input Blast XML file') 304 help='The input Blast XML file')
293 parser.add_argument('-o', '--output', type=argparse.FileType(mode='w', encoding='utf-8'), default=sys.stdout, 305 parser.add_argument('-o', '--output', type=argparse.FileType(mode='w'), default=sys.stdout,
294 help='The output html file') 306 help='The output html file')
295 # We just want the file name here, so jinja can open the file 307 # We just want the file name here, so jinja can open the file
296 # itself. But it is easier to just use a FileType so argparse can 308 # itself. But it is easier to just use a FileType so argparse can
297 # handle the errors. This introduces a small race condition when 309 # handle the errors. This introduces a small race condition when
298 # jinja later tries to re-open the template file, but we don't 310 # jinja later tries to re-open the template file, but we don't
299 # care too much. 311 # care too much.
300 parser.add_argument('--template', type=argparse.FileType(mode='r', encoding='utf-8'), default=default_template, 312 parser.add_argument('--template', type=argparse.FileType(mode='r'), default=default_template,
301 help='The template file to use. Defaults to blast_html.html.jinja') 313 help='The template file to use. Defaults to blast_html.html.jinja')
302 314
303 args = parser.parse_args() 315 args = parser.parse_args()
304 if args.input == None: 316 if args.input == None:
305 args.input = args.positional_arg 317 args.input = args.positional_arg