Previous changeset 21:9596fea636bb (2014-05-15) Next changeset 23:6995a6f34f3f (2014-05-15) |
Commit message:
rename blast2html |
added:
blast2html.html.jinja blast2html.py |
removed:
blast_html.html.jinja blast_html.py |
b |
diff -r 9596fea636bb -r efce16c98650 blast2html.html.jinja --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/blast2html.html.jinja Thu May 15 10:48:09 2014 +0200 |
[ |
b'@@ -0,0 +1,600 @@\n+<!DOCTYPE html>\n+<html>\n+ <head>\n+ <meta charset="UTF-8">\n+ <meta name=generator content="blast2html; see ...">\n+ \n+ <title>Blast output</title>\n+ \n+ <style>\n+ body {\n+ color: #333333;\n+ font-family: Arial,Sans-Serif;\n+ }\n+\n+ :link {\n+ color: #336699;\n+ }\n+\n+ .right {\n+ float: right;\n+ }\n+\n+ #content {\n+ margin: 0 2em;\n+ padding: 0.5em;\n+ border: 1px solid #888888;\n+ background-color: #d3dff5;\n+ }\n+\n+ h1, h2, h3, h4, h5, h6 {\n+ color: #2A6979;\n+ font-family: arial,verdana,sans-serif;\n+ letter-spacing: -1px;\n+ margin: 1.2em 0 0.3em;\n+ }\n+\n+ h1 {\n+ border-bottom: 1px solid #CCCCCC;\n+ font-size: 150%;\n+ padding-bottom: 0.1em;\n+ }\n+\n+ h2 {\n+ font-size: 120%;\n+ font-weight: bold;\n+ }\n+\n+ h4.darkHeader {\n+ color: #4D4D4D;\n+ letter-spacing: 0;\n+ font-weight: bold;\n+ }\n+\n+ #nodata {\n+ font-weight: bold;\n+ }\n+\n+ .index {\n+ margin-bottom: 3em;\n+ }\n+ .index div.indexentry {\n+ margin: 1.2em 1.6em;\n+ font-weight: bold;\n+ font-size: 100%;\n+ }\n+ \n+ .headerdata {\n+ font-size: 90%;\n+ }\n+ .headerdata .param {\n+ font-weight: bold;\n+ text-align: right;\n+ padding: 0 1em;\n+ }\n+\n+ .grey {\n+ background-color: #eeeeee;\n+ border: 1px solid #cccccc;\n+ padding: 1em;\n+ }\n+\n+ .white {\n+ background-color: white;\n+ border: 1px solid #cccccc;\n+ padding: 1.5em 2%;\n+ }\n+\n+ .graphicrow {\n+ clear: left;\n+ width: 100%;\n+ }\n+\n+ .graphicitem {\n+ float: left;\n+ }\n+\n+\n+ \n+ .graphics .grey {\n+ text-align: center;\n+ }\n+\n+ .graphic {\n+ background-color: white;\n+ border: 2px solid black;\n+ padding: 1.5em;\n+ margin: auto;\n+ }\n+\n+ .centered, .defline, div.legend, div.tablewrapper {\n+ margin-left: auto;\n+ margin-right: auto;\n+ }\n+\n+ .defline {\n+ background-color: white;\n+ border: 1px solid black;\n+ margin: .5em auto;\n+ padding-left: .2em;\n+ padding-right: .2em;\n+ max-width: 50em;\n+ text-align: left;\n+ height: 2.8em;\n+ overflow-y: hidden;\n+ }\n+\n+ div.legend {\n+ max-width: 40em;\n+ }\n+ div.legend div {\n+ width: 100%;\n+ color: white;\n+ font-weight: bold;\n+ border-spacing: 0;\n+ }\n+ div.legend div .graphicitem {\n+ width: 20%;\n+ padding: 0;\n+ margin: 0;\n+ border: none;\n+ }\n+\n+ div.tablewrapper {\n+ width: 50%;\n+ min-width: 60em;\n+ }\n+\n+ /* For small widths we give the graphic 100% */\n+ @media (max-width: 72.5em) {\n+ div.tablewrapper {\n+ width: 100%;\n+ min-width: 0px;\n+ }\n+ }\n+\n+ .scale {\n+ width: 100%;\n+ margin: .5em 0;\n+ font-weight: bold;\n+ }\n+ .scale div {\n+ color: red;\n+ text-align: left;\n+ }\n+ .scale .graphicrow {\n+ margin: .5em 0 .5em 0;\n+ color: white;\n+ }\n+ .scale .graphicitem {\n+ position: relative;\n+ }\n+ .scale .graphicitem div {\n+ margin: 0 1px;\n+ padding: 0 2px;\n+ text-align: right;\n+ background-color: red;\n+ color: white;\n+ }\n+ .scale .graphicitem:first-child div {\n+ margin-left: 0px;\n+ }\n+ .scale .graphicitem:last-child div {\n+ margin-right: 0px;\n+ }\n+ .scale .graphicitem .lastlabel {\n+ position: absolute;\n+ top: 0px;\n+ left: 100%;\n+ background-color: transparent;\n+ color: red;\n+ }\n+\n+ a.matchresult {\n+ display: block;\n+ margin: 0;\n+ padding: 0;\n+ }\n+ div.matchrow {\n+ margin-top: 4px;\n+ }\n+ div.matchrow, div.matchitem {\n+ height: 4px;\n+ }\n+\n+ \n+ table.descriptiontable {\n+ font-'..b'{{hit.link_id}}">\n+ {{hit.title}}\n+ </a></div></td>\n+ <td>{{hit.maxscore}}</td>\n+ <td>{{hit.totalscore}}</td>\n+ <td>{{hit.cover}}</td>\n+ <td>{{hit.e_value}}</td>\n+ <td>{{hit.ident}}</td>\n+ <td><a href="{{genelink(hit.hit|hitid)}}">{{hit.accession}}</a></td>\n+ </tr>\n+ {% endfor %}\n+ </table>\n+\n+ </div></div>\n+ </section>\n+\n+\n+\n+ <section class=alignments>\n+ <h2>Alignments</h2>\n+\n+ <div class=grey><div class=white>\n+ {% for hit in hits %}\n+ <div class=alignment id=hit{{hit.Hit_num}}>\n+\n+ <div class=linkheader>\n+ <div class=right><a href="#description{{hit.Hit_num}}">Descriptions</a></div>\n+ <a class=linkheader href="{{genelink(hit|hitid)}}">GenBank</a>\n+ <a class=linkheader href="{{genelink(hit|hitid, \'graph\')}}">Graphics</a>\n+ </div>\n+\n+ <div class=title>\n+ <p class=hittitle>{{hit|firsttitle}}</p>\n+ <p class=titleinfo>\n+ <span class=b>Sequence ID:</span> <a href="{{genelink(hit|hitid)}}">{{hit|seqid}}</a>\n+ <span class=b>Length:</span> {{hit.Hit_len}}\n+ <span class=b>Number of Matches:</span> {{hit.Hit_hsps.Hsp|length}}\n+ </p>\n+ </div>\n+\n+ {% if hit|othertitles|length %}\n+ <a class=showmoretitles onclick="toggle_visibility(\'moretitles{{hit.Hit_num|js_string_escape}}\'); return false;" href=\'\'>\n+ See {{hit|othertitles|length}} more title(s)\n+ </a>\n+\n+ <div class=moretitles id=moretitles{{hit.Hit_num}} style="display: none">\n+ {% for title in hit|othertitles %}\n+ <div class=title>\n+ <p class=hittitle>{{title.title}}</p>\n+ <p class=titleinfo>\n+ <span class=b>Sequence ID:</span> <a href="{{genelink(title.hitid)}}">{{title.id}}</a>\n+ </p>\n+ </div>\n+ {% endfor %}\n+ </div>\n+ {% endif %}\n+\n+ {% for hsp in hit.Hit_hsps.Hsp %}\n+ <div class=hotspot>\n+ <p class=range>\n+ <span class=range>Range {{hsp.Hsp_num}}: {{hsp[\'Hsp_hit-from\']}} to {{hsp[\'Hsp_hit-to\']}}</span>\n+ <a class=range href="{{genelink(hit|hitid, \'genbank\', hsp)}}">GenBank</a>\n+ <a class=range href="{{genelink(hit|hitid, \'graph\', hsp)}}">Graphics</a>\n+ </p>\n+\n+ <table class=hotspotstable>\n+ <tr>\n+ <th>Score</th><th>Expect</th><th>Identities</th><th>Gaps</th><th>Strand</th>\n+ </tr>\n+ <tr>\n+ <td>{{hsp[\'Hsp_bit-score\']|fmt(\'.1f\')}} bits({{hsp.Hsp_score}})</td>\n+ <td>{{hsp.Hsp_evalue|fmt(\'.1f\')}}</td>\n+ <td>{{ hsp.Hsp_identity }}/{{ hsp|len }}({{\n+ (hsp.Hsp_identity/hsp|len) |fmt(\'.0%\') }})</td>\n+ <td>{{ hsp.Hsp_gaps }}/{{ hsp|len\n+ }}({{ (hsp.Hsp_gaps / hsp|len) | fmt(\'.0%\') }})</td>\n+ <td>{{ hsp[\'Hsp_query-frame\']|asframe }}/{{ hsp[\'Hsp_hit-frame\']|asframe }}</td>\n+ </tr>\n+ </table>\n+\n+ <pre class=alignmentgraphic>{{hsp|alignment_pre}}</pre>\n+ </div>\n+ {% endfor %}\n+\n+ </div>\n+\n+ {% endfor %}\n+ </div></div>\n+ </section>\n+ {% endif %}\n+\n+ {% endfor %}\n+ {% endif %}\n+ </div>\n+ </body>\n+</html>\n+\n+{#\n+Local Variables:\n+tab-width: 2\n+indent-tabs-mode: nil\n+End:\n+#}\n' |
b |
diff -r 9596fea636bb -r efce16c98650 blast2html.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/blast2html.py Thu May 15 10:48:09 2014 +0200 |
[ |
b'@@ -0,0 +1,304 @@\n+#!/usr/bin/env python3\n+\n+# Copyright The Hyve B.V. 2014\n+# License: GPL version 3 or higher\n+\n+import sys\n+import math\n+import warnings\n+from os import path\n+from itertools import repeat\n+import argparse\n+from lxml import objectify\n+import jinja2\n+\n+\n+\n+_filters = {}\n+def filter(func_or_name):\n+ "Decorator to register a function as filter in the current jinja environment"\n+ if isinstance(func_or_name, str):\n+ def inner(func):\n+ _filters[func_or_name] = func.__name__\n+ return func\n+ return inner\n+ else:\n+ _filters[func_or_name.__name__] = func_or_name.__name__\n+ return func_or_name\n+\n+\n+def color_idx(length):\n+ if length < 40:\n+ return 0\n+ elif length < 50:\n+ return 1\n+ elif length < 80:\n+ return 2\n+ elif length < 200:\n+ return 3\n+ return 4\n+\n+@filter\n+def fmt(val, fmt):\n+ return format(float(val), fmt)\n+\n+@filter\n+def firsttitle(hit):\n+ return hit.Hit_def.text.split(\'>\')[0]\n+\n+@filter\n+def othertitles(hit):\n+ """Split a hit.Hit_def that contains multiple titles up, splitting out the hit ids from the titles."""\n+ id_titles = hit.Hit_def.text.split(\'>\')\n+\n+ titles = []\n+ for t in id_titles[1:]:\n+ fullid, title = t.split(\' \', 1)\n+ hitid, id = fullid.split(\'|\', 2)[1:3]\n+ titles.append(dict(id = id,\n+ hitid = hitid,\n+ fullid = fullid,\n+ title = title))\n+ return titles\n+\n+@filter\n+def hitid(hit):\n+ return hit.Hit_id.text.split(\'|\', 2)[1]\n+\n+@filter\n+def seqid(hit):\n+ return hit.Hit_id.text.split(\'|\', 2)[2]\n+\n+@filter\n+def alignment_pre(hsp):\n+ return (\n+ "Query {:>7s} {} {}\\n".format(hsp[\'Hsp_query-from\'].text, hsp.Hsp_qseq, hsp[\'Hsp_query-to\']) +\n+ " {:7s} {}\\n".format(\'\', hsp.Hsp_midline) +\n+ "Subject{:>7s} {} {}".format(hsp[\'Hsp_hit-from\'].text, hsp.Hsp_hseq, hsp[\'Hsp_hit-to\'])\n+ )\n+\n+@filter(\'len\')\n+def blastxml_len(node):\n+ if node.tag == \'Hsp\':\n+ return int(node[\'Hsp_align-len\'])\n+ elif node.tag == \'Iteration\':\n+ return int(node[\'Iteration_query-len\'])\n+ raise Exception("Unknown XML node type: "+node.tag)\n+ \n+\n+@filter\n+def asframe(frame):\n+ if frame == 1:\n+ return \'Plus\'\n+ elif frame == -1:\n+ return \'Minus\'\n+ raise Exception("frame should be either +1 or -1")\n+\n+def genelink(hit, type=\'genbank\', hsp=None):\n+ if not isinstance(hit, str):\n+ hit = hitid(hit)\n+ link = "http://www.ncbi.nlm.nih.gov/nucleotide/{}?report={}&log$=nuclalign".format(hit, type)\n+ if hsp != None:\n+ link += "&from={}&to={}".format(hsp[\'Hsp_hit-from\'], hsp[\'Hsp_hit-to\'])\n+ return link\n+\n+\n+# javascript escape filter based on Django\'s, from https://github.com/dsissitka/khan-website/blob/master/templatefilters.py#L112-139\n+# I\'ve removed the html escapes, since html escaping is already being performed by the template engine.\n+\n+_base_js_escapes = (\n+ (\'\\\\\', r\'\\u005C\'),\n+ (\'\\\'\', r\'\\u0027\'),\n+ (\'"\', r\'\\u0022\'),\n+ # (\'>\', r\'\\u003E\'),\n+ # (\'<\', r\'\\u003C\'),\n+ # (\'&\', r\'\\u0026\'),\n+ # (\'=\', r\'\\u003D\'),\n+ # (\'-\', r\'\\u002D\'),\n+ # (\';\', r\'\\u003B\'),\n+ # (u\'\\u2028\', r\'\\u2028\'),\n+ # (u\'\\u2029\', r\'\\u2029\')\n+)\n+\n+# Escape every ASCII character with a value less than 32. This is\n+# needed a.o. to prevent html parsers from jumping out of javascript\n+# parsing mode.\n+_js_escapes = (_base_js_escapes +\n+ tuple((\'%c\' % z, \'\\\\u%04X\' % z) for z in range(32)))\n+\n+@filter\n+def js_string_escape(value):\n+ """Escape javascript string literal escapes. Note that this only works\n+ within javascript string literals, not in general javascript\n+ snippets."""\n+\n+ value = str(value)\n+\n+ for bad, good in _js_escapes:\n+ value = value.replace(bad, good)\n+\n+ return value\n+\n+@filter\n+def hits(result):\n+ # sort hits by longest hotspot first\n+ return sorted(result.'..b'ange(query_length):\n+ if table[i] == last:\n+ count += 1\n+ continue\n+ matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n+ last = table[i]\n+ count = 1\n+ matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n+\n+ yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit))\n+\n+ @filter\n+ def queryscale(self, result):\n+ query_length = blastxml_len(result)\n+ skip = math.ceil(query_length / self.max_scale_labels)\n+ percent_multiplier = 100 / query_length\n+ for i in range(1, query_length+1):\n+ if i % skip == 0:\n+ yield dict(label = i, width = skip * percent_multiplier, shorter = False\\)\n+ if query_length % skip != 0:\n+ yield dict(label = query_length,\n+ width = (query_length % skip) * percent_multiplier,\n+ shorter = True)\n+\n+ @filter\n+ def hit_info(self, result):\n+\n+ query_length = blastxml_len(result)\n+\n+ for hit in hits(result):\n+ hsps = hit.Hit_hsps.Hsp\n+\n+ cover = [False] * query_length\n+ for hsp in hsps:\n+ cover[hsp[\'Hsp_query-from\']-1 : int(hsp[\'Hsp_query-to\'])] = repeat(True, blastxml_len(hsp))\n+ cover_count = cover.count(True)\n+\n+ def hsp_val(path):\n+ return (float(hsp[path]) for hsp in hsps)\n+\n+ yield dict(hit = hit,\n+ title = firsttitle(hit),\n+ link_id = hit.Hit_num,\n+ maxscore = "{:.1f}".format(max(hsp_val(\'Hsp_bit-score\'))),\n+ totalscore = "{:.1f}".format(sum(hsp_val(\'Hsp_bit-score\'))),\n+ cover = "{:.0%}".format(cover_count / query_length),\n+ e_value = "{:.4g}".format(min(hsp_val(\'Hsp_evalue\'))),\n+ # FIXME: is this the correct formula vv?\n+ ident = "{:.0%}".format(float(min(hsp.Hsp_identity / blastxml_len(hsp) for hsp in hsps))),\n+ accession = hit.Hit_accession)\n+\n+def main():\n+\n+ parser = argparse.ArgumentParser(description="Convert a BLAST XML result into a nicely readable html page",\n+ usage="{} [-i] INPUT [-o OUTPUT]".format(sys.argv[0]))\n+ input_group = parser.add_mutually_exclusive_group(required=True)\n+ input_group.add_argument(\'positional_arg\', metavar=\'INPUT\', nargs=\'?\', type=argparse.FileType(mode=\'r\'),\n+ help=\'The input Blast XML file, same as -i/--input\')\n+ input_group.add_argument(\'-i\', \'--input\', type=argparse.FileType(mode=\'r\'), \n+ help=\'The input Blast XML file\')\n+ parser.add_argument(\'-o\', \'--output\', type=argparse.FileType(mode=\'w\'), default=sys.stdout,\n+ help=\'The output html file\')\n+ # We just want the file name here, so jinja can open the file\n+ # itself. But it is easier to just use a FileType so argparse can\n+ # handle the errors. This introduces a small race condition when\n+ # jinja later tries to re-open the template file, but we don\'t\n+ # care too much.\n+ parser.add_argument(\'--template\', type=argparse.FileType(mode=\'r\'), default=\'blast2html.html.jinja\',\n+ help=\'The template file to use. Defaults to blast_html.html.jinja\')\n+\n+ args = parser.parse_args()\n+ if args.input == None:\n+ args.input = args.positional_arg\n+ if args.input == None:\n+ parser.error(\'no input specified\')\n+\n+ templatedir, templatename = path.split(args.template.name)\n+ args.template.close()\n+ if not templatedir:\n+ templatedir = \'.\'\n+\n+ b = BlastVisualize(args.input, templatedir, templatename)\n+ b.render(args.output)\n+\n+\n+if __name__ == \'__main__\':\n+ main()\n+\n' |
b |
diff -r 9596fea636bb -r efce16c98650 blast_html.html.jinja --- a/blast_html.html.jinja Thu May 15 10:45:34 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 |
[ |
b'@@ -1,600 +0,0 @@\n-<!DOCTYPE html>\n-<html>\n- <head>\n- <meta charset="UTF-8">\n- <meta name=generator content="blast_html; see ...">\n- \n- <title>Blast output</title>\n- \n- <style>\n- body {\n- color: #333333;\n- font-family: Arial,Sans-Serif;\n- }\n-\n- :link {\n- color: #336699;\n- }\n-\n- .right {\n- float: right;\n- }\n-\n- #content {\n- margin: 0 2em;\n- padding: 0.5em;\n- border: 1px solid #888888;\n- background-color: #d3dff5;\n- }\n-\n- h1, h2, h3, h4, h5, h6 {\n- color: #2A6979;\n- font-family: arial,verdana,sans-serif;\n- letter-spacing: -1px;\n- margin: 1.2em 0 0.3em;\n- }\n-\n- h1 {\n- border-bottom: 1px solid #CCCCCC;\n- font-size: 150%;\n- padding-bottom: 0.1em;\n- }\n-\n- h2 {\n- font-size: 120%;\n- font-weight: bold;\n- }\n-\n- h4.darkHeader {\n- color: #4D4D4D;\n- letter-spacing: 0;\n- font-weight: bold;\n- }\n-\n- #nodata {\n- font-weight: bold;\n- }\n-\n- .index {\n- margin-bottom: 3em;\n- }\n- .index div.indexentry {\n- margin: 1.2em 1.6em;\n- font-weight: bold;\n- font-size: 100%;\n- }\n- \n- .headerdata {\n- font-size: 90%;\n- }\n- .headerdata .param {\n- font-weight: bold;\n- text-align: right;\n- padding: 0 1em;\n- }\n-\n- .grey {\n- background-color: #eeeeee;\n- border: 1px solid #cccccc;\n- padding: 1em;\n- }\n-\n- .white {\n- background-color: white;\n- border: 1px solid #cccccc;\n- padding: 1.5em 2%;\n- }\n-\n- .graphicrow {\n- clear: left;\n- width: 100%;\n- }\n-\n- .graphicitem {\n- float: left;\n- }\n-\n-\n- \n- .graphics .grey {\n- text-align: center;\n- }\n-\n- .graphic {\n- background-color: white;\n- border: 2px solid black;\n- padding: 1.5em;\n- margin: auto;\n- }\n-\n- .centered, .defline, div.legend, div.tablewrapper {\n- margin-left: auto;\n- margin-right: auto;\n- }\n-\n- .defline {\n- background-color: white;\n- border: 1px solid black;\n- margin: .5em auto;\n- padding-left: .2em;\n- padding-right: .2em;\n- max-width: 50em;\n- text-align: left;\n- height: 2.8em;\n- overflow-y: hidden;\n- }\n-\n- div.legend {\n- max-width: 40em;\n- }\n- div.legend div {\n- width: 100%;\n- color: white;\n- font-weight: bold;\n- border-spacing: 0;\n- }\n- div.legend div .graphicitem {\n- width: 20%;\n- padding: 0;\n- margin: 0;\n- border: none;\n- }\n-\n- div.tablewrapper {\n- width: 50%;\n- min-width: 60em;\n- }\n-\n- /* For small widths we give the graphic 100% */\n- @media (max-width: 72.5em) {\n- div.tablewrapper {\n- width: 100%;\n- min-width: 0px;\n- }\n- }\n-\n- .scale {\n- width: 100%;\n- margin: .5em 0;\n- font-weight: bold;\n- }\n- .scale div {\n- color: red;\n- text-align: left;\n- }\n- .scale .graphicrow {\n- margin: .5em 0 .5em 0;\n- color: white;\n- }\n- .scale .graphicitem {\n- position: relative;\n- }\n- .scale .graphicitem div {\n- margin: 0 1px;\n- padding: 0 2px;\n- text-align: right;\n- background-color: red;\n- color: white;\n- }\n- .scale .graphicitem:first-child div {\n- margin-left: 0px;\n- }\n- .scale .graphicitem:last-child div {\n- margin-right: 0px;\n- }\n- .scale .graphicitem .lastlabel {\n- position: absolute;\n- top: 0px;\n- left: 100%;\n- background-color: transparent;\n- color: red;\n- }\n-\n- a.matchresult {\n- display: block;\n- margin: 0;\n- padding: 0;\n- }\n- div.matchrow {\n- margin-top: 4px;\n- }\n- div.matchrow, div.matchitem {\n- height: 4px;\n- }\n-\n- \n- table.descriptiontable {\n- font-'..b'{{hit.link_id}}">\n- {{hit.title}}\n- </a></div></td>\n- <td>{{hit.maxscore}}</td>\n- <td>{{hit.totalscore}}</td>\n- <td>{{hit.cover}}</td>\n- <td>{{hit.e_value}}</td>\n- <td>{{hit.ident}}</td>\n- <td><a href="{{genelink(hit.hit|hitid)}}">{{hit.accession}}</a></td>\n- </tr>\n- {% endfor %}\n- </table>\n-\n- </div></div>\n- </section>\n-\n-\n-\n- <section class=alignments>\n- <h2>Alignments</h2>\n-\n- <div class=grey><div class=white>\n- {% for hit in hits %}\n- <div class=alignment id=hit{{hit.Hit_num}}>\n-\n- <div class=linkheader>\n- <div class=right><a href="#description{{hit.Hit_num}}">Descriptions</a></div>\n- <a class=linkheader href="{{genelink(hit|hitid)}}">GenBank</a>\n- <a class=linkheader href="{{genelink(hit|hitid, \'graph\')}}">Graphics</a>\n- </div>\n-\n- <div class=title>\n- <p class=hittitle>{{hit|firsttitle}}</p>\n- <p class=titleinfo>\n- <span class=b>Sequence ID:</span> <a href="{{genelink(hit|hitid)}}">{{hit|seqid}}</a>\n- <span class=b>Length:</span> {{hit.Hit_len}}\n- <span class=b>Number of Matches:</span> {{hit.Hit_hsps.Hsp|length}}\n- </p>\n- </div>\n-\n- {% if hit|othertitles|length %}\n- <a class=showmoretitles onclick="toggle_visibility(\'moretitles{{hit.Hit_num|js_string_escape}}\'); return false;" href=\'\'>\n- See {{hit|othertitles|length}} more title(s)\n- </a>\n-\n- <div class=moretitles id=moretitles{{hit.Hit_num}} style="display: none">\n- {% for title in hit|othertitles %}\n- <div class=title>\n- <p class=hittitle>{{title.title}}</p>\n- <p class=titleinfo>\n- <span class=b>Sequence ID:</span> <a href="{{genelink(title.hitid)}}">{{title.id}}</a>\n- </p>\n- </div>\n- {% endfor %}\n- </div>\n- {% endif %}\n-\n- {% for hsp in hit.Hit_hsps.Hsp %}\n- <div class=hotspot>\n- <p class=range>\n- <span class=range>Range {{hsp.Hsp_num}}: {{hsp[\'Hsp_hit-from\']}} to {{hsp[\'Hsp_hit-to\']}}</span>\n- <a class=range href="{{genelink(hit|hitid, \'genbank\', hsp)}}">GenBank</a>\n- <a class=range href="{{genelink(hit|hitid, \'graph\', hsp)}}">Graphics</a>\n- </p>\n-\n- <table class=hotspotstable>\n- <tr>\n- <th>Score</th><th>Expect</th><th>Identities</th><th>Gaps</th><th>Strand</th>\n- </tr>\n- <tr>\n- <td>{{hsp[\'Hsp_bit-score\']|fmt(\'.1f\')}} bits({{hsp.Hsp_score}})</td>\n- <td>{{hsp.Hsp_evalue|fmt(\'.1f\')}}</td>\n- <td>{{ hsp.Hsp_identity }}/{{ hsp|len }}({{\n- (hsp.Hsp_identity/hsp|len) |fmt(\'.0%\') }})</td>\n- <td>{{ hsp.Hsp_gaps }}/{{ hsp|len\n- }}({{ (hsp.Hsp_gaps / hsp|len) | fmt(\'.0%\') }})</td>\n- <td>{{ hsp[\'Hsp_query-frame\']|asframe }}/{{ hsp[\'Hsp_hit-frame\']|asframe }}</td>\n- </tr>\n- </table>\n-\n- <pre class=alignmentgraphic>{{hsp|alignment_pre}}</pre>\n- </div>\n- {% endfor %}\n-\n- </div>\n-\n- {% endfor %}\n- </div></div>\n- </section>\n- {% endif %}\n-\n- {% endfor %}\n- {% endif %}\n- </div>\n- </body>\n-</html>\n-\n-{#\n-Local Variables:\n-tab-width: 2\n-indent-tabs-mode: nil\n-End:\n-#}\n' |
b |
diff -r 9596fea636bb -r efce16c98650 blast_html.py --- a/blast_html.py Thu May 15 10:45:34 2014 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 |
[ |
b'@@ -1,304 +0,0 @@\n-#!/usr/bin/env python3\n-\n-# Copyright The Hyve B.V. 2014\n-# License: GPL version 3 or higher\n-\n-import sys\n-import math\n-import warnings\n-from os import path\n-from itertools import repeat\n-import argparse\n-from lxml import objectify\n-import jinja2\n-\n-\n-\n-_filters = {}\n-def filter(func_or_name):\n- "Decorator to register a function as filter in the current jinja environment"\n- if isinstance(func_or_name, str):\n- def inner(func):\n- _filters[func_or_name] = func.__name__\n- return func\n- return inner\n- else:\n- _filters[func_or_name.__name__] = func_or_name.__name__\n- return func_or_name\n-\n-\n-def color_idx(length):\n- if length < 40:\n- return 0\n- elif length < 50:\n- return 1\n- elif length < 80:\n- return 2\n- elif length < 200:\n- return 3\n- return 4\n-\n-@filter\n-def fmt(val, fmt):\n- return format(float(val), fmt)\n-\n-@filter\n-def firsttitle(hit):\n- return hit.Hit_def.text.split(\'>\')[0]\n-\n-@filter\n-def othertitles(hit):\n- """Split a hit.Hit_def that contains multiple titles up, splitting out the hit ids from the titles."""\n- id_titles = hit.Hit_def.text.split(\'>\')\n-\n- titles = []\n- for t in id_titles[1:]:\n- fullid, title = t.split(\' \', 1)\n- hitid, id = fullid.split(\'|\', 2)[1:3]\n- titles.append(dict(id = id,\n- hitid = hitid,\n- fullid = fullid,\n- title = title))\n- return titles\n-\n-@filter\n-def hitid(hit):\n- return hit.Hit_id.text.split(\'|\', 2)[1]\n-\n-@filter\n-def seqid(hit):\n- return hit.Hit_id.text.split(\'|\', 2)[2]\n-\n-@filter\n-def alignment_pre(hsp):\n- return (\n- "Query {:>7s} {} {}\\n".format(hsp[\'Hsp_query-from\'].text, hsp.Hsp_qseq, hsp[\'Hsp_query-to\']) +\n- " {:7s} {}\\n".format(\'\', hsp.Hsp_midline) +\n- "Subject{:>7s} {} {}".format(hsp[\'Hsp_hit-from\'].text, hsp.Hsp_hseq, hsp[\'Hsp_hit-to\'])\n- )\n-\n-@filter(\'len\')\n-def blastxml_len(node):\n- if node.tag == \'Hsp\':\n- return int(node[\'Hsp_align-len\'])\n- elif node.tag == \'Iteration\':\n- return int(node[\'Iteration_query-len\'])\n- raise Exception("Unknown XML node type: "+node.tag)\n- \n-\n-@filter\n-def asframe(frame):\n- if frame == 1:\n- return \'Plus\'\n- elif frame == -1:\n- return \'Minus\'\n- raise Exception("frame should be either +1 or -1")\n-\n-def genelink(hit, type=\'genbank\', hsp=None):\n- if not isinstance(hit, str):\n- hit = hitid(hit)\n- link = "http://www.ncbi.nlm.nih.gov/nucleotide/{}?report={}&log$=nuclalign".format(hit, type)\n- if hsp != None:\n- link += "&from={}&to={}".format(hsp[\'Hsp_hit-from\'], hsp[\'Hsp_hit-to\'])\n- return link\n-\n-\n-# javascript escape filter based on Django\'s, from https://github.com/dsissitka/khan-website/blob/master/templatefilters.py#L112-139\n-# I\'ve removed the html escapes, since html escaping is already being performed by the template engine.\n-\n-_base_js_escapes = (\n- (\'\\\\\', r\'\\u005C\'),\n- (\'\\\'\', r\'\\u0027\'),\n- (\'"\', r\'\\u0022\'),\n- # (\'>\', r\'\\u003E\'),\n- # (\'<\', r\'\\u003C\'),\n- # (\'&\', r\'\\u0026\'),\n- # (\'=\', r\'\\u003D\'),\n- # (\'-\', r\'\\u002D\'),\n- # (\';\', r\'\\u003B\'),\n- # (u\'\\u2028\', r\'\\u2028\'),\n- # (u\'\\u2029\', r\'\\u2029\')\n-)\n-\n-# Escape every ASCII character with a value less than 32. This is\n-# needed a.o. to prevent html parsers from jumping out of javascript\n-# parsing mode.\n-_js_escapes = (_base_js_escapes +\n- tuple((\'%c\' % z, \'\\\\u%04X\' % z) for z in range(32)))\n-\n-@filter\n-def js_string_escape(value):\n- """Escape javascript string literal escapes. Note that this only works\n- within javascript string literals, not in general javascript\n- snippets."""\n-\n- value = str(value)\n-\n- for bad, good in _js_escapes:\n- value = value.replace(bad, good)\n-\n- return value\n-\n-@filter\n-def hits(result):\n- # sort hits by longest hotspot first\n- return sorted(result.'..b'range(query_length):\n- if table[i] == last:\n- count += 1\n- continue\n- matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n- last = table[i]\n- count = 1\n- matches.append((count * percent_multiplier, self.colors[last] if last != 255 else \'transparent\'))\n-\n- yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit))\n-\n- @filter\n- def queryscale(self, result):\n- query_length = blastxml_len(result)\n- skip = math.ceil(query_length / self.max_scale_labels)\n- percent_multiplier = 100 / query_length\n- for i in range(1, query_length+1):\n- if i % skip == 0:\n- yield dict(label = i, width = skip * percent_multiplier, shorter = False)\n- if query_length % skip != 0:\n- yield dict(label = query_length,\n- width = (query_length % skip) * percent_multiplier,\n- shorter = True)\n-\n- @filter\n- def hit_info(self, result):\n-\n- query_length = blastxml_len(result)\n-\n- for hit in hits(result):\n- hsps = hit.Hit_hsps.Hsp\n-\n- cover = [False] * query_length\n- for hsp in hsps:\n- cover[hsp[\'Hsp_query-from\']-1 : int(hsp[\'Hsp_query-to\'])] = repeat(True, blastxml_len(hsp))\n- cover_count = cover.count(True)\n-\n- def hsp_val(path):\n- return (float(hsp[path]) for hsp in hsps)\n-\n- yield dict(hit = hit,\n- title = firsttitle(hit),\n- link_id = hit.Hit_num,\n- maxscore = "{:.1f}".format(max(hsp_val(\'Hsp_bit-score\'))),\n- totalscore = "{:.1f}".format(sum(hsp_val(\'Hsp_bit-score\'))),\n- cover = "{:.0%}".format(cover_count / query_length),\n- e_value = "{:.4g}".format(min(hsp_val(\'Hsp_evalue\'))),\n- # FIXME: is this the correct formula vv?\n- ident = "{:.0%}".format(float(min(hsp.Hsp_identity / blastxml_len(hsp) for hsp in hsps))),\n- accession = hit.Hit_accession)\n-\n-def main():\n-\n- parser = argparse.ArgumentParser(description="Convert a BLAST XML result into a nicely readable html page",\n- usage="{} [-i] INPUT [-o OUTPUT]".format(sys.argv[0]))\n- input_group = parser.add_mutually_exclusive_group(required=True)\n- input_group.add_argument(\'positional_arg\', metavar=\'INPUT\', nargs=\'?\', type=argparse.FileType(mode=\'r\'),\n- help=\'The input Blast XML file, same as -i/--input\')\n- input_group.add_argument(\'-i\', \'--input\', type=argparse.FileType(mode=\'r\'), \n- help=\'The input Blast XML file\')\n- parser.add_argument(\'-o\', \'--output\', type=argparse.FileType(mode=\'w\'), default=sys.stdout,\n- help=\'The output html file\')\n- # We just want the file name here, so jinja can open the file\n- # itself. But it is easier to just use a FileType so argparse can\n- # handle the errors. This introduces a small race condition when\n- # jinja later tries to re-open the template file, but we don\'t\n- # care too much.\n- parser.add_argument(\'--template\', type=argparse.FileType(mode=\'r\'), default=\'blast_html.html.jinja\',\n- help=\'The template file to use. Defaults to blast_html.html.jinja\')\n-\n- args = parser.parse_args()\n- if args.input == None:\n- args.input = args.positional_arg\n- if args.input == None:\n- parser.error(\'no input specified\')\n-\n- templatedir, templatename = path.split(args.template.name)\n- args.template.close()\n- if not templatedir:\n- templatedir = \'.\'\n-\n- b = BlastVisualize(args.input, templatedir, templatename)\n- b.render(args.output)\n-\n-\n-if __name__ == \'__main__\':\n- main()\n-\n' |