バイオ系だけどプログラミング始めました

ImageJ (Fiji)の使い方や Python でのプログラミングなどを、主にバイオ系の研究者・大学院生向けに書いていこうと思います。

ImageJ Fiji + Python で画像解析プログラムを書こう(後編)

目次

後編について

さて、これまで前編と中編では Python の基本について書いてきましたが、いよいよ ImageJ の機能を使いこなして解析の自動化などに関係することを書いていこうと思います。前編と中編の記事はこちら。

satoshithermophilus.hatenablog.com

satoshithermophilus.hatenablog.com

8. モジュールのインポート

大抵のプログラミング言語は基本的な最小限の機能しか備えていません。そこで行うのが「モジュール」のインポートです。モジュールのインポートを行うことでさらに高度なことができるようになったり、あなたが作ったプログラムに含まれる関数などを他のプログラムで使うことができるようになります。また、ImageJ Fiji + Jython でのプログラミングで、ImageI Fiji の画像解析の機能を使うために必要です。

Pythonでは、

import モジュールの名前
や
import モジュールの名前 as 使いたい名前

from モジュール import モジュールに含まれる使いたいサブモジュールの名前

とするとインポートできます。 例えばこんな感じです。

from ij import IJ

IJ.log("Hello World!")

このプログラムは、

  1. ij というモジュールから サブモジュールの IJ を import して、

  2. IJ の中の機能の log() を使って、ImageJ のログウィンドウを開いてそこに “Hello World!” を表示させています。

今までは print() を使って表示せてましたが、テキストウィンドウを開かずにプログラムを起動すると print() で文字を表示させることができないので、自分でプログラムを書く際には最終的にはログウィンドウ実行結果などをに表示させた方が良いと思います。プログラムの作成途中は print() が使い勝手が良いですが。適宜使い分けましょう。

他にもいろいろモジュールはあります。非常に数が多いので全部は列挙できませんが、使用頻度の高い役に立つものはこのブログで紹介する予定です。

上の例では ImageJ のモジュールを import しましたが、Python のモジュールを使う例を以下に書いてみます。

import csv

csvpath = "適当なパス.csv"
f = open(csvpath, "wb")
csvw = csv.writer(f)

resultslist = [[1, 100], [2, 150], [3, 200]]

csvw.writerow(["Number", "value"])
csvw.writerows(resultslist)

f.close()

この適当なパスのところを、適当に書き換えて実行してみてください。分からない人のために、パスについて少し説明しますね。分かるのであれば適当に読み飛ばしてください。

パスというのは、ファイルやフォルダの住所みたいなもので、普段クリックして開いているファイルやフォルダがどこにあるのかを文字で表します。インターネットでいう URL みたいなもんです。例えば Windows だと、ファイルを開くためのエクスプローラーの上の部分をみればよいです。

f:id:shatoshi:20170626164552j:plain

この “C:\Users\ユーザー名\test.csv” と書かれているのがパス(path)になり、Cドライブ直下の、Users というフォルダの、ユーザー名(多分ここはあなたの設定したユーザー名になっています)というフォルダの下の、Desktopフォルダの下の、test.csvというファイルであることを表しています。Windows の場合、バックスラッシュ"\“ か円の記号によりフォルダ名等が区切られます。区切かたりはOSによって若干異なるので注意してください。 そして、、Windows の場合はPythonでパスを扱う場合、バックスラッシュ(円記号)を二重に書くか、スラッシュに書き換える必要があります。例えば、"C:\\Users\\ユーザー名\\Desktop\\test.csv"か、"C:/Users/ユーザー名/Desktop/test.csv"と書く必要があります*1

MacOS についてのパスの確認もそのうち書く予定ですが、Mac の方ではパスをバックスラッシュ二つにしなくてもよいみたいです。ここら辺なんでかよくわからん。

とりあえず、この適当なパスのところを適当に書き換えて実行してみてください。すると、書き換えたパスの場所に Excel などの表計算ソフトで開ける test.csv ファイルができていると思います。これを開くと、

f:id:shatoshi:20170626171405j:plain

こんな感じの表になっているはずです。csv Comma-Separated ValuesというExcelなどの表計算ソフトなどで読み込める形式 https://ja.wikipedia.org/wiki/Comma-Separated_Values のファイルで、カンマ",“と改行で値を区切ったテキストファイルになります。試しにメモ帳で csv ファイルを開いてみるとどんなものか分かると思います。csv は色んなプログラムで扱うことができるし、 Python での扱いも楽なので解析結果は csv で保存するのがよいでしょう。

さて、このプログラムの中身の説明をすると、

  1. import csv で、csv形式のファイルを扱うための Python のモジュールを import します。

  2. “適当なパス.csv" の csvファイルを扱うためのファイルオブジェクトを作成します。オブジェクトは新しく出てくる言葉ですね(オブジェクト指向とは? - IT用語辞典 詳しくない人にとってはちんぷんかんぷんな説明かもしれません。)。オブジェクトについては、もうちょっとあとの「9.画像データを扱う imagePlus」の方で詳しく説明します。軽く説明すると、これまでに出てきた「変数」が単なる数字や文字列の入れ物で、「関数」が変数等の何らかの入力を受け取って仕事をする機能だったのに対し、オブジェクトは、それ自体が関係するいくつかの「変数」(ここでは、ファイルの path と"wb"と書いた読込み形式)と、それに関係する機能(メソッドと呼ばれる関数のようなモノ。ここではファイルを開いて扱う)を持っています*2

  3. csvw という名前で、先ほど作成したファイルオブジェクトにcsv形式で書き込むオブジェクトを作ります。

  4. resultslist というリストに、解析結果っぽいものを代入します。

  5. csvw のメソッドの一つ、「writerow」を使って、[“Number”, “value”]を書き込む。writerowはリストで与えられた値を一行ずつ書き込みます。リストの要素はそれぞれ別なカラムに書き込まれます。

  6. csvw のメソッドの一つ、「writerows」は先ほどのwriterowと違って、与えられたリストを複数の row に書き込むメソッドです。これを使って resultslist の内容を書き込みます。

  7. f.close() でファイルオブジェクトを閉じる。

となります。

9. 画像データを扱う imagePlus

長かった。ここまで来るのに長かった。やっと画像について触れるよ。ほんとここまで読んでくれてお疲れ様です。まだ続くけど。

先ほどオブジェクトというものが出てきましたね。オブジェクトというのは、上に書いたように、それ自体が関係するいくつかの「変数」(ここでは、ファイルの path と"wb"と書いた読込み形式)と、それに関係する機能(メソッドと呼ばれる関数のようなモノ)を持っています。実は Python はあらゆるものがオブジェクトになっていて、例えば list オブジェクトは count() というメソッドを持っていて、「リスト.count(検索したい内容)」で list の中にある要素をカウントすることができます。このように、あるオブジェクトが持っているメソッドは 「オブジェクト.メソッド()」で使うことができます

なんでこんなまどろっこしいものがあるかというと、こうすることで、数値以上の複雑な物を扱えるからなんですね。画像を例にすると、画像データというのはピクセルという小さな点の集まりで、それぞれのピクセルが明るさの値を持っています(詳しくはこちらを読んでください画像データの基本 - バイオ系だけどプログラミング始めました)。このピクセルの値が画像のメインのデータになるわけですが、それ以外にも、画像の高さと横幅、画像の種類(カラー or 8-bit グレースケール or 16-bit or ・・・)etc、と様々な情報が画像データには必要です。そういった様々な値を持ったデータを扱って、またそこから適切にデータを取り出したり入力したりするためにオブジェクトというものが便利なんです。そして、オブジェクトがどういう値を持つのか?どういう関数を持っているのか?というものの定義がクラスになります*3。オブジェクトに関するちゃんとした説明は他の人の説明を読んでもらった方がいいと思います。僕よりもちゃんとしてて分かりやすいと思います。以下のリンクとか読んでみたらいいと思います。

5分で絶対に分かる:5分で絶対に分かるオブジェクト指向 (1/6) - ITmedia エンタープライズ

五分で分かるらしいです。

ImageJ での、画像のオブジェクトは ImagePlus というクラスになります。 以下のリンクを開いてみてください。ここに ImagePlus がどういうクラスなのかが書かれています。

https://imagej.nih.gov/ij/developer/api/ij/ImagePlus.html

Fields が ImagePlus の持つ値とかについての記載、Methods がそのクラスが持っている関数、Constructors が ImagePlus のオブジェクトを作る時の関数になります。いっぱいあるし英語だし、よくわからないかもしれませんが、用語を調べつつプログラムを書きながら

ImageJ のクラスに関する情報は、このページから探すことができますhttps://imagej.nih.gov/ij/developer/api/。このページは、ImageJ のJavadoc という仕様をまとめたもので ImageJ のメインメニューの EMBL > IJ Javadoc から開くことができます。ImageJ の機能を利用してプログラムを書くときはここでクラスの使い方を探して書きましょう。

例えば、適当な画像を ImageJ で開いた状態で、以下のプログラムを実行してみてください。

from ij import IJ

imp = IJ.getImage()
width = imp.getWidth()
height = imp.getHeight()

IJ.log("Width = " + str(width) + ", height = " + str(height))

実行するとこんな感じな結果になると思います。

f:id:shatoshi:20170626225557j:plain

このプログラムは、

  1. IJ にある getImage() という関数でアクティブなウィンドウの画像を ImagePlus オブジェクトとして imp に代入する。

  2. imp というImagePlus オブジェクトから getHeight と getWidth で画像の高さと横幅の値を取得する。

  3. 結果を IJ.log() でログウィンドウに表示する。

というながれで動きます。このようにして、オブジェクトから値をむき出したりします。getHeight メソッドのように、メソッドの名前は何をするのか分かりやすい名前になっています。メソッドやフィールドの数が膨大で圧倒されちゃうかもしれませんが、基本的に何をするメソッドなのか?どういうフィールドなのか?が分かりやすい名前になっているので心配しなくて大丈夫です。

練習問題

この画像の高さと横幅を取得して表示するプログラムを、「適当なパスにある画像を開いて表示」し、「画像の縦と横幅を表示する」ように書き換えましょう。そのために必要な機能は先ほど書いた ImagePlus のドキュメントに書いてあります。頑張って探してみましょう。

https://imagej.nih.gov/ij/developer/api/ij/ImagePlus.html

以下に答えを白文字で書いてます。

Constructor の ImagePlus(適当なパス)を使ってImagePlus オブジェクトを作り、show() メソッドで画像を表示させます。

10. ImageJ の機能を活用する

さて、これでプログラミングの基礎とプログラム上での画像の扱いかたについて一通り伝えました。最後に Jython からの ImageJ の機能の利用法について説明します。ImageJ の機能を利用するには、https://imagej.nih.gov/ij/developer/api/ここを読んで自分の使いたい機能を探すことになるわけですが、どれがImageJ上でポチポチ動かすときの機能に当たるのかちょっとわかりづらいというか探すのが一苦労です。そこで、マクロの記録機能を利用します。メインメニューから、

Plugins > Macros > Record… でマクロの記録画面が表示されます。

f:id:shatoshi:20170626234215j:plain

適当な画像を開いて、Image > Adjust > Threshold で閾値を設定してみましょう(閾値の設定というのが何のことかが分からない場合にはについてはこちらの記事を読んでくださいImageJ で米粒(細胞)を数えて解析しよう。 - バイオ系だけどプログラミング始めました)。すると、こんなのが表示されます。

f:id:shatoshi:20170626234904j:plain

この文をコピペして、 imp の入力を自分の処理したい画像の ImagePlus オブジェクトにして、"Default"の部分を使いたいメソッドにすれば、Jython 上で閾値を決める機能を使うことができます。例えばこんな感じ

from ij import IJ

imp = IJ.getImage()
IJ.setAutoThreshold(imp, "Default dark")

この機能を使えば、ImageJ でポチポチとボタンを押してやっていた作業が自動化できたり、自身で独自の関数を定義してもうちょっと踏み込んだ解析を行うことができます。下に自動化の例を書いているので参考にしてください。

ただ、この方法は ImageJ の GUI を経由して行っているので、結構制限があったりするので(http://wiki.cmci.info/documents/120925gifu)、踏み込んだ解析のためには、Javadoc を読んでクラスやメソッドの使い方をしらべた方が良いです。Ctrl + L (Mac は command + L)で開けるコマンドファインダーからソースを読むこともできます。これも参考になるでしょう。

だいたい基本は書きました。まだ書き足りないこともありますが、おいおい別な記事で書いていきます。プログラムを書く能力は、教わったり本を読むよりも、何か目的をもってその達成のために試行錯誤する過程で一番身につきます。自分の研究で、この解析をもうちょっと踏み込んで解析したい・この部分が大変だから自動化したい、という悩みを勉強だと思ってプログラムを書いて解決してみましょう。最初は、エラーばっかりでるし、調べないと分からないことだらけで、時間ばかりかかるかもしれません*4。もしかしたら、自動化して得られた時間よりもプログラムを書いている時間の方が長くなってしまうかもしれません。しかし、一度プログラムを書けは、似た処理に再利用するのは簡単です。それに、苦労して書いた分んはあなたの糧となり、また別なプログラムを書くときには、もっと早くきれいなコードで書けるでしょう。そうすればプログラミングが楽しくなると思います。

まぁ、とりあえず書いてみたり、僕がブログに書いているプログラムを色々改変して遊んでみるといいと思います。さあ、楽しんでプログラミングをやってみましょう。

以下に、今回紹介した内容でできることのを例を載せています。参考にしてみてください。

11. 例1. 砂嵐のような画像を作る

ImagePlus にランダムな値を入力してランダムなピクセル値を持つ画像を作ってます。

satoshithermophilus.hatenablog.com

12. 例2. 米粒のカウントを複数の 画像に対して自動で行う

この記事でかいた米粒(実際の研究では細胞に対して使ったりする)の解析

satoshithermophilus.hatenablog.com

コレを自動化して、あるフォルダに入っている複数の画像ファイルに対して解析を行い、実行結果を Roi と csv ファイルで出力するプログラムプログラムを書いてみました。

satoshithermophilus.hatenablog.com

この自動解析のプログラムの解説はまた今度書く予定です。

*1:こういった OS に依存した Path の取り扱いの違いに対応したりするために、Python には os.path モジュールがあります。例2でも使ってます。

*2:実は、Pythonは数、文字列、リストなどあらゆるものがオブジェクトなのですがとりあえずここではおいときます

*3:Fate を知っている人には分かりやすいかもしれない。ここでいうクラスというのがセイバーとかアーチャーみたいなクラスにあたり、オブジェクトが、実際に呼ばれたサーバントになります。クラスによってそれぞれ違う機能を持っている。そして、そしてそれぞれのサーバントは真名やステータスや宝具といった性質を持っている・・・みたいな感じです。そして、オブジェクトは、攻撃する・宝具を使う・・・といった機能を持っている。そもそもサーバントは宝具を持っていて、有名な英霊で、パラメータとしてOOをが設定されていて・・・みたいなこともクラスで定義されています。冷静になってみるとこのたとえはあまり良くないかも。

*4:もしも躓いた時にはググれば Python ことは大抵見つかります。ImageJ + Jython は情報がそこまで多くないので苦労するかもしれませんが・・・。