Repository 'blast2html'
hg clone https://toolshed.g2.bx.psu.edu/repos/jankanis/blast2html

Changeset 12:a459c754cdb5 (2014-05-12)
Previous changeset 11:7660519f2dc9 (2014-05-12) Next changeset 13:c2d63adb83db (2014-05-12)
Commit message:
add links, refactor, proper commandline arguments
modified:
visualise.html.jinja
visualise.py
b
diff -r 7660519f2dc9 -r a459c754cdb5 visualise.html.jinja
--- a/visualise.html.jinja Mon May 12 13:55:04 2014 +0200
+++ b/visualise.html.jinja Mon May 12 17:12:24 2014 +0200
[
@@ -11,6 +11,14 @@
       font-family: Arial,Sans-Serif;
       }
 
+      :link {
+      color: #336699;
+      }
+
+      .right {
+      float: right;
+      }
+
       #content {
       margin: 0 2em;
       padding: 0.5em;
@@ -76,10 +84,12 @@
       float: left;
       }
 
+
+      
       .graphic {
       background-color: white;
       border: 2px solid black;
-      padding: .5em;
+      padding: .5em 1.5em;
       align: center;
       margin: auto;
       }
@@ -128,7 +138,7 @@
       }
 
       /* For small widths we give the graphic 100% */
-      @media (max-width: 70em) {
+      @media (max-width: 72.5em) {
       div.tablewrapper {
       width: 100%;
       min-width: 0px;
@@ -174,6 +184,8 @@
       height: 4px;
       }
 
+
+      
       div#descriptions {
       }
       
@@ -212,10 +224,9 @@
       white-space: nowrap;
       text-align: left;
       }
-      table.descriptiontable a:link {
-      color: #336699;
-      }
+
 
+      
       #alignments .white {
       padding: 1.5em 1em;
       }
@@ -228,12 +239,13 @@
       
       div.linkheader {
       padding-top: .2em;
-      text-align: right;
       font-size: 85%;
       color: #14376C;
       }
-      div.linkheader a {
-      color: #14376C;
+      div.linkheader a.linkheader {
+      margin-right: 1em;
+      }
+      div.linkheader .right a {
       text-decoration: none;
       }
 
@@ -244,6 +256,7 @@
       .title .titleinfo {
       font-size: 80%;
       margin-top: 0;
+      margin-bottom: .3em;
       }
       .title .titleinfo .b {
       color: #606060;
@@ -259,13 +272,15 @@
       padding: 0;
       }
       .moretitles .hittitle {
-      margin: .2em 0;
+      margin: .4em 0 .2em 0;
       padding: 0;
       }
       
       a.showmoretitles {
-      font-size: 85%;
+      font-size: 75%;
       color: #336699;
+      font-weight: bold;
+      margin-top: 0;
       }
       a.showmoretitles:hover {
       }
@@ -276,13 +291,18 @@
       margin-bottom: 2.5em;
       }
 
-      .hotspot .range {
+      .hotspot p.range {
       font-size: 70%;
       margin-top: 0;
-      font-weight: bold;
       margin-top: 1em;
       margin-bottom: .2em;
       }
+      .hotspot p.range span.range {
+      font-weight: bold;
+      }
+      .hotspot p.range a.range {
+      margin-left: .5em;
+      }
 
       table.hotspotstable {
       border-top: 1px solid;
@@ -330,14 +350,14 @@
 
         <table class=headerdata>
           {% for param, value in params %}
-            <tr><td class=param>{{param}}</td><td>{{value}}</td></tr>
+            <tr><td class=param>{{param}}:</td><td>{{value}}</td></tr>
           {% endfor %}
         </table>
           
       </div>
-
-      {% if not (blast.BlastOutput_iterations.find('Iteration') and
-                 blast.BlastOutput_iterations.Iteration.Iteration_hits.find('Hit')) %}
+      
+      {% if not (blast.BlastOutput_iterations.findall('Iteration') and
+                 blast.BlastOutput_iterations.Iteration.Iteration_hits.findall('Hit')) %}
       <div id=nodata>
  <h2>No Results</h2>
  <div class=grey>
@@ -360,9 +380,9 @@
             <h4 class=darkHeader>Color key for alignment scores</h4>
      <div class=legend><div class=graphicrow>
  <div class=graphicitem style="background-color: {{colors[0]}}">&lt;40</div>
- <div class=graphicitem style="background-color: {{colors[1]}}">40&endash;50</div>
- <div class=graphicitem style="background-color: {{colors[2]}}">50-80</div>
- <div class=graphicitem style="background-color: {{colors[3]}}">80-200</div>
+ <div class=graphicitem style="background-color: {{colors[1]}}">40–50</div>
+ <div class=graphicitem style="background-color: {{colors[2]}}">50–80</div>
+ <div class=graphicitem style="background-color: {{colors[3]}}">80–200</div>
  <div class=graphicitem style="background-color: {{colors[4]}}">200≤</div>
             </div></div>
      <div style="clear: left"></div>
@@ -433,7 +453,7 @@
                 <td>{{hit.cover}}</td>
                 <td>{{hit.e_value}}</td>
                 <td>{{hit.ident}}</td>
-                <td><a href="http://www.ncbi.nlm.nih.gov/nucleotide/{{hit.hit|hitid}}?report=genbank&log$=nucltop&blast_rank=1">{{hit.accession}}</a></td>
+                <td><a href="{{genelink(hit.hit|hitid)}}">{{hit.accession}}</a></td>
               </tr>
               {% endfor %}
             </table>
@@ -451,13 +471,15 @@
      <div class=alignment id=hit{{hit.Hit_num}}>
        
        <div class=linkheader>
- <a href="#description{{hit.Hit_num}}">Descriptions</a>
+ <div class=right><a href="#description{{hit.Hit_num}}">Descriptions</a></div>
+ <a class=linkheader href="{{genelink(hit|hitid)}}">GenBank</a>
+ <a class=linkheader href="{{genelink(hit|hitid, 'graph')}}">Graphics</a>
        </div>
-
+       
        <div class=title>
  <p class=hittitle>{{hit|firsttitle}}</p>
  <p class=titleinfo>
-   <span class=b>Sequence ID:</span> <a href="http://www.ncbi.nlm.nih.gov/nucleotide/{{hit|hitid}}?report=genbank&log$=nuclalign&blast_rank=1">{{hit|seqid}}</a>
+   <span class=b>Sequence ID:</span> <a href="{{genelink(hit|hitid)}}">{{hit|seqid}}</a>
    <span class=b>Length:</span> {{hit.Hit_len}}
    <span class=b>Number of Matches:</span> {{hit.Hit_hsps.Hsp|length}}
  </p>
@@ -473,7 +495,7 @@
  <div class=title>
    <p class=hittitle>{{title.title}}</p>
    <p class=titleinfo>
-     <span class=b>Sequence ID:</span> {{title.id}}
+     <span class=b>Sequence ID:</span> <a href="{{genelink(title.hitid)}}">{{title.id}}</a>
    </p>
  </div>
  {% endfor %}
@@ -482,7 +504,11 @@
 
        {% for hsp in hit.Hit_hsps.Hsp %}
        <div class=hotspot>
- <p class=range>Range {{hsp.Hsp_num}}: {{hsp['Hsp_hit-from']}} to {{hsp['Hsp_hit-to']}} <a href="http://www.ncbi.nlm.nih.gov/nucleotide/{{hit|hitid}}?report=genbank&log$=nuclalign&blast_rank=1&from={{hsp['Hsp_hit-from']}}&to={{hsp['Hsp_hit-to']}}">GenBank</a></p>
+ <p class=range>
+   <span class=range>Range {{hsp.Hsp_num}}: {{hsp['Hsp_hit-from']}} to {{hsp['Hsp_hit-to']}}</span>
+   <a class=range href="{{genelink(hit|hitid, 'genbank', hsp)}}">GenBank</a>
+   <a class=range href="{{genelink(hit|hitid, 'graph', hsp)}}">Graphics</a>
+ </p>
 
  <table class=hotspotstable>
    <tr>
b
diff -r 7660519f2dc9 -r a459c754cdb5 visualise.py
--- a/visualise.py Mon May 12 13:55:04 2014 +0200
+++ b/visualise.py Mon May 12 17:12:24 2014 +0200
[
b'@@ -7,23 +7,22 @@\n import math\n import warnings\n from itertools import repeat\n+import argparse\n from lxml import objectify\n import jinja2\n \n \n-blast = objectify.parse(\'blast xml example1.xml\').getroot()\n-loader = jinja2.FileSystemLoader(searchpath=\'.\')\n-environment = jinja2.Environment(loader=loader, lstrip_blocks=True, trim_blocks=True, autoescape=True)\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-            environment.filters[func_or_name] = func\n+            _filters[func_or_name] = func\n             return func\n         return inner\n     else:\n-        environment.filters[func_or_name.__name__] = func_or_name\n+        _filters[func_or_name.__name__] = func_or_name\n         return func_or_name\n \n \n@@ -38,10 +37,6 @@\n         return 3\n     return 4\n \n-colors = [\'black\', \'blue\', \'green\', \'magenta\', \'red\']\n-\n-environment.filters[\'color\'] = lambda length: match_colors[color_idx(length)]\n-\n @filter\n def fmt(val, fmt):\n     return format(float(val), fmt)\n@@ -58,8 +53,9 @@\n     titles = []\n     for t in id_titles[1:]:\n         fullid, title = t.split(\' \', 1)\n-        id = fullid.split(\'|\', 2)[2]\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@@ -92,108 +88,158 @@\n         return \'Minus\'\n     raise Exception("frame should be either +1 or -1")\n \n-\n-query_length = int(blast["BlastOutput_query-len"])\n-\n-hits = blast.BlastOutput_iterations.Iteration.Iteration_hits.Hit\n-# sort hits by longest hotspot first\n-ordered_hits = sorted(hits,\n-                      key=lambda h: max(hsplen(hsp) for hsp in h.Hit_hsps.Hsp),\n-                      reverse=True)\n-\n-def match_colors():\n-    """\n-    An iterator that yields lists of length-color pairs. \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 jinja2.Markup(link)\n \n-    percent_multiplier = 100 / query_length\n-    \n-    for hit in hits:\n-        # sort hotspots from short to long, so we can overwrite index colors of\n-        # short matches with those of long ones.\n-        hotspots = sorted(hit.Hit_hsps.Hsp, key=lambda hsp: hsplen(hsp))\n-        table = bytearray([255]) * query_length\n-        for hsp in hotspots:\n-            frm = hsp[\'Hsp_query-from\'] - 1\n-            to = int(hsp[\'Hsp_query-to\'])\n-            table[frm:to] = repeat(color_idx(hsplen(hsp)), to - frm)\n \n-        matches = []\n-        last = table[0]\n-        count = 0\n-        for i in range(query_length):\n-            if table[i] == last:\n-                count += 1\n-                continue\n-            matches.append((count * percent_multiplier, colors[last] if last != 255 else \'none\'))\n-            last = table[i]\n-            count = 1\n-        matches.append((count * percent_multiplier, colors[last] if last != 255 else \'none\'))\n-\n-        yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit))\n \n \n-def queryscale():\n-    max_labels = 10\n-    skip = math.ceil(query_length / max_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)\n-    if query_length % skip != 0:\n-        yield dict(label = query_length, width = (query_length % skip) * percent_multiplier)\n+class BlastVisualize:\n+\n+    colors = (\'black\', \'blue\', \'green\', \'magenta\', \'red\')\n+\n+    max_scale_labels = 10\n+\n+    templatename = \'visualise.html.jinja\'\n+\n+    def __init__(self, input):\n+        '..b')),\n-                   # FIXME: is this the correct formula vv?\n-                   ident = "{:.0%}".format(float(min(hsp.Hsp_identity / hsplen(hsp) for hsp in hsps))),\n-                   accession = hit.Hit_accession)\n+            cover = [False] * self.query_length\n+            for hsp in hsps:\n+                cover[hsp[\'Hsp_query-from\']-1 : int(hsp[\'Hsp_query-to\'])] = repeat(True, hsplen(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 / self.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 / hsplen(hsp) for hsp in hsps))),\n+                       accession = hit.Hit_accession)\n \n \n def main():\n-    template = environment.get_template(\'visualise.html.jinja\')\n \n-    params = ((\'Query ID\', blast["BlastOutput_query-ID"]),\n-              (\'Query definition\', blast["BlastOutput_query-def"]),\n-              (\'Query length\', blast["BlastOutput_query-len"]),\n-              (\'Program\', blast.BlastOutput_version),\n-              (\'Database\', blast.BlastOutput_db),\n-            )\n-\n-    if len(blast.BlastOutput_iterations.Iteration) > 1:\n-        warnings.warn("Multiple \'Iteration\' elements found, showing only the first")\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 \n-    sys.stdout.write(template.render(blast=blast,\n-                                     length=query_length,\n-                                     hits=blast.BlastOutput_iterations.Iteration.Iteration_hits.Hit,\n-                                     colors=colors,\n-                                     match_colors=match_colors(),\n-                                     queryscale=queryscale(),\n-                                     hit_info=hit_info(),\n-                                     params=params))\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-main()\n+    b = BlastVisualize(args.input)\n+    b.render(args.output)\n+\n \n-# http://www.ncbi.nlm.nih.gov/nucleotide/557804451?report=genbank&log$=nuclalign&blast_rank=1&RID=PHWP1JNZ014\n-# http://www.ncbi.nlm.nih.gov/nuccore/557804451?report=graph&rid=PHWP1JNZ014[557804451]&tracks=[key:sequence_track,name:Sequence,display_name:Sequence,id:STD1,category:Sequence,annots:Sequence,ShowLabel:true][key:gene_model_track,CDSProductFeats:false][key:alignment_track,name:other%20alignments,annots:NG%20Alignments%7CRefseq%20Alignments%7CGnomon%20Alignments%7CUnnamed,shown:false]&v=752:2685&appname=ncbiblast&link_loc=fromSubj\n+if __name__ == \'__main__\':\n+    main()\n \n-# http://www.ncbi.nlm.nih.gov/nucleotide/557804451?report=genbank&log$=nucltop&blast_rank=1&RID=PHWP1JNZ014\n'