Hatena::Groupkdri

KazusaAPI開発日誌 このページをアンテナに追加 RSSフィード

2009-01-05Bio::Graphics

Bio::Graphics をつかって blast 検索結果を画像化する

|  Bio::Graphics をつかって blast 検索結果を画像化する - KazusaAPI開発日誌 を含むブックマーク はてなブックマーク -  Bio::Graphics をつかって blast 検索結果を画像化する - KazusaAPI開発日誌  Bio::Graphics をつかって blast 検索結果を画像化する - KazusaAPI開発日誌 のブックマークコメント

no title の Example 4. Parsing and Rendering a Real BLAST File with Bio::SearchIO にのとって blast 検索結果を画像化してみます。そしてそれを Rails アプリのなかで利用してみます。


Bio::Graphics を使ったプログラム

元のサイトのコードに -max_score と -min_score を追加してます。

#!/usr/bin/perl
# This is code example 4 in the Graphics-HOWTO
use strict;
use Bio::Graphics;
use Bio::SearchIO;

my $file = shift or die "Usage: render4.pl <blast file>\n";

my $searchio = Bio::SearchIO->new(-file   => $file,
                                  -format => 'blast') or die "parse failed";

my $result = $searchio->next_result() or die "no result";
my $panel = Bio::Graphics::Panel->new(-length    => $result->query_length,
                                      -width     => 800,
                                      -pad_left  => 10,
                                      -pad_right => 10,
                                      );

my $full_length = Bio::SeqFeature::Generic->new(-start=>1,-end=>$result->query_length,
                                                -display_name=>$result->query_name);
$panel->add_track($full_length,
                  -glyph   => 'arrow',
                  -tick    => 2,
                  -fgcolor => 'black',
                  -double  => 1,
                  -label   => 1,
                 );

my $track = $panel->add_track(-glyph       => 'graded_segments',
                              -label       => 1,
                              -connector   => 'dashed',
                              -bgcolor     => 'blue',
                              -min_score   => 0,
                              -max_score   => 1500,
                              -font2color  => 'red',
                              -sort_order  => 'high_score',
                              -description => sub {
                                 my $feature = shift;
                                 return unless $feature->has_tag('description');
                                 my ($description) = $feature->each_tag_value('description');
                                 my $score = $feature->score;
                                 "$description, score=$score";
                              });

while( my $hit = $result->next_hit ) {
    next unless $hit->significance < 1E-20;
    my $feature = Bio::SeqFeature::Generic->new(-score   => $hit->raw_score,
                                                -display_name => $hit->name,
                                                -tag     => {
                                                             description => $hit->description
                                                            },
                                               );
   while( my $hsp = $hit->next_hsp ) {
       $feature->add_sub_SeqFeature($hsp,'EXPAND');
   }

   $track->add_feature($feature);
}
print $panel->png;

これを blast2png.pl と適当なファイルとして保存し、

$ perl blast2png.pl blast_report.bla > blast_report.png

と実行すると、http://stein.cshl.org/genome_informatics/figs/graphics/fig4.png のような画像が blast_report.png として作成されます(図はイメージです)。

f:id:nakao_mitsuteru:20090108010652p:image

最上段にクエリ配列があり、各行はヒットのポジションを表しています。色の濃さは相同性の度合いを示しています。


Rails アプリから利用する。

blast2png.pl をウェブサービス化してみます。Ruby on Rails を使用します。


設計

つぎのように動作するようにつくります。

  1. 入力データとして、blast レポートを POST で受け付ける。いいかえると、http://localhost/format/pngblast レポートを POST すると画像を含んだページが返る。
  2. 入力データとして、blast レポートの URL を受け付ける。いいかえると、http://example.comblast レポートが有るとして、http://localhost/format/png にその URL を POST すると画像を含んだページが返る。
  3. 作成した画像に再度アクセス可能にする。例:http://localhost/format/png/:id.png
  4. blastレポートのURLをアトリビュートの値としてGETすると image/png として返る。いいかえると、http://example.comblast レポートが有るとして、GET http://localhost/format/png?q=http://example.com で画像が返る。

このような機能の実現には、入力 blast レポートファイルの管理、出力画像の管理が必要です。


実装

script/blast2png.pl

blast2png.pl を script/ におきます。

MVC(モデル、ビュー、コントローラー)のうち、モデルは不要なので、ビューとコントローラー、ルーティングのコードの順に示します。

app/views/format/png.rhtml
<form method='POST'>
BLAST output text or URL
<textarea name='q'></textarea>
<input type='hidden' name='post' value='post' />
<input type='reset' />
<input type='submit' />
</form>

<%= link_to @image_uri, @image_uri %>
<%= image_tag(@image_uri) if @image_uri%>
<hr />
<pre>
<%= @report %>
</pre>

POST 用フォーム生成と画像の表示、画像URLの表示をします。

f:id:nakao_mitsuteru:20090108010654j:image


app/controllers/format_controller.rb
class FormatController < ApplicationController

  # GET  /format/png
  # POST /format/png q, post
  # GET  /format/png?q=http://...
  # GET  /format/png/:bid.png
  def png
    if params[:q] and params[:q] =~ /^http/
      png_generate_by_url
      png_read unless params[:post]
    elsif params[:q] and params[:q] =~ /BLAST/
      png_generate
    elsif params[:bid]
      png_read
    end
  end

  private 
  
  def png_generate
    @report = params[:q]
    @output_image_uri, @image_file_name = exec_blast2png(@report)
  end

  def png_generate_by_url
    @url = params[:q]
    begin
      @report = open(@url).read.gsub(/\<(\w|\/).+?\>/, "")
    rescue
    end
    @output_image_uri, @image_file_name = exec_blast2png(@report)    
  end
  
  def png_read
    image_file = set_image_file_name(params[:bid] || @bid)
    begin 
      image_data = nil
      File.open(image_file) do |f|
        image_data = f.read
      end
      send_data image_data, :type => 'image/png', :disposition => 'inline'
    rescue
      render :text => "404 Not found", :status => '404'
    end
  end
end

表には png メソッドだけだします。パラメーターによって動作を変えています。動作は大きく分けて二つあり、画像生成(png_generate, png_genearte_by_url)と画像読み出し(png_read)です。


app/controllers/application.rb
class ApplicationController < ActionController::Base

  def blast_report_tmp_root
    tmp_report_dir = %W( #{RAILS_ROOT}/tmp/blast_report ).to_s
  end

  def delete_old_files(path, limit = 80000)
    t = Time.now
    Dir.glob("#{path}/*" ).each do |file|
      File.delete file if t - File.ctime(file) > limit
    end
  end
  
  def check_tmp_report_files
    tmp_report_dir = blast_report_tmp_root
    Dir.mkdir(tmp_report_dir) unless File.exists?(tmp_report_dir)
    delete_old_files(tmp_report_dir)
  end

  def set_report_file(report_text)
    file_name = "#{blast_report_tmp_root}/#{set_report_file_name(report_text)}"
    unless File.exist?(file_name)
      begin
        File.open(file_name, 'w') do |f|
          f.write report_text
        end
        return file_name
      rescue
      end
    else
      return file_name
    end
  end

  def set_report_file_name(report_text)
    require 'digest/md5'
    prefix = Digest::MD5.new.hexdigest(report_text)
    return "#{prefix}.blast_report"
  end

  def set_output_image_file(input)
    @bid = File.basename(input, '.blast_report')
    @image_file_name = File.basename(set_image_file_name(@bid))
    @image_uri = "/format/png/#{@image_file_name}"
    return @image_uri, set_image_file_name(@bid)
  end

  def set_image_file_name(bid)
    return "#{blast_report_tmp_root}/#{bid}.png"
  end
  
  def exec_blast2png(report_text)
    perl = '/usr/bin/perl'
    blast2png = %W( #{RAILS_ROOT}/script/blast2png.pl ).to_s
    check_tmp_report_files
    input_file_name = set_report_file(report_text)
    output_file_uri, output_file_name = set_output_image_file(input_file_name)
    command = "#{perl} #{blast2png} #{input_file_name} > #{output_file_name}"
    begin
      o = IO.popen(command)
      Process.wait
      o.close
    rescue
    end
    return output_file_uri, output_file_name
  end

end

入力ファイルの管理、スクリプトの実行のコードです。入力ファイルや出力ファイルを一時ディレクトリ(tmp/blast_report)に保存したり、一時ディレクトリの管理をします。たとえば、おなじレポートファイルは再計算しないような仕組みや古いファイルは自動的に削除する仕組みがあります。

このような機能はもうすこしがんばると一般化できそうです。


app/config/route.rb
map.connect 'format/png/:bid.png', :controller => 'format', :action => 'png'

一度作成された画像に再度アクセスするときのルーティングです。


使い方

三種類の利用を想定しています。

手元の blast レポートのテキストを画像化する

つぎのような blast レポート、blastall の既定の出力形式です、をフォームに入力してサブミットする。

f:id:nakao_mitsuteru:20090108010924j:image

画像を含んだページが帰って来ます。

f:id:nakao_mitsuteru:20090108010653j:image

画像化したいblast レポートの URL を知っているとき

フォームに URL を入力してサブミットします。

たとえば、http://plant1.kazusa.or.jp:3011/blast_output/show/cb:sll1408?db=nr のような blast 検索の結果ページです。ある程度の HTML タグを取り除いて素の blast レポートテキストとして評価しています。うまく表示できないページもあるかもしれません。

画像化したいblast レポートの URL を知っていて、image タグに埋め込みたいとき

URLをパラメーターとしてエスケープすると画像のURLとして扱うことができます。

たとえば、http://plant1.kazusa.or.jp:3011/blast_output/show/cb:sll1408?db=nr の場合、

>> require 'cgi'
>> CGI.escape("http://plant1.kazusa.or.jp:3011/blast_output/show/cb:sll1408?db=nr")
=> "http%3A%2F%2Fplant1.kazusa.or.jp%3A3011%2Fblast_output%2Fshow%2Fcb%3Asll1408%3Fdb%3Dnr"

というようにエスケープできます。これをつかった、http://localhost/format/png?q=http%3A%2F%2Fplant1.kazusa.or.jp%3A3011%2Fblast_output%2Fshow%2Fcb%3Asll1408%3Fdb%3Dnr という URL は Content-Type: image/png の画像として image タグに埋め込むことが可能です。

トラックバック - http://kdri.g.hatena.ne.jp/nakao_mitsuteru/20090105