Mercurial > repos > jankanis > blast2html
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 |
