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

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

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

目次

前回の記事

satoshithermophilus.hatenablog.com

ImageJ で Python を動かそう の続き

前回の記事もそうですが、書いてあるプログラムを自身で打ち込んで(書き写して)実行してみることをお勧めします。結構、打ち間違ってエラーが出たりしますが、どこが間違っているかはエラーメッセージを読めばわかるようになっています。プログラムを書くというのは、機械が理解できる言語を決まった文法にしたがって記述するということになります。言ってみれば外国語を勉強してしゃべるのと同じようなもんです。そんなに気負わずに楽しくやってみてください。もしも、プログラムが動かなかったり、エラーが出たりしたらコメントなどで気軽に相談してください。

3. 文字列

Python では文字列の操作が結構簡単にできます。文字列の扱いができると色々と便利です。例えば、ある名前のファイルだけ解析するとか、実行結果を文字で出力したりとか、解析結果を保存するファイル名を工夫したりとか・・・。普通に文字列を解析することもあるかもしれません。では文字列の操作を説明します。

Python ではダブルクオーテーション""もしくはクオーテーション''で囲むことで文字を表します。Hello World! を print() で表示したときに""で囲っていたことを思い出してください。文字列は「+」で結合することができます。

text1 = "Hello"
text2 = "World!"
text3 = text1 + text2
print(text3)

f:id:shatoshi:20170614153811j:plain

スライス表記と言って、後ろに[]をつけて、その中に数字とコロン(:)を組み合わせて書くことで、文字列のある部分を抜き出したりすることができます。

  • text[a]で文字列の先頭から数えてa番目の文字を抜き出す(この際、a番目というのは、ゼロから数え始めることに注意)。

  • text[a:] で a 番目の文字から最後の文字までを抜き出す。

  • text[:a] で最初の文字から a-1 番目の文字までを抜き出す。

  • text[a:b] で a 番目の文字から b-1 番目の文字までを抜き出す。

  • text[-a] で後ろから a 番目の文字を抜き出す。

例えばこんな感じに打って実行すると、

text = "HelloWorld!"
print(text[0])
print(text[5])
print(text[5:])
print(text[:5])
print(text[1:5])
print(text[-1])

f:id:shatoshi:20170614153833j:plain

てな感じになります。

練習問題 1

文字列 "shatoshi" を上記の方法で "satoshi" にしましょう*1。 この下に白文字で答えを書いているので、選択して答えを確認しましょう。ただし、いろんなやり方があるので、もちろん違う方法でやっていてもかまいません。自分の欲しい機能を自分の考えた方法で実現することは楽しいです。愉しむ気持ちを大切にして色々試してください。

text1 = "shatoshi"
text2 = text1[0] + text1[2:]
print(text2)

4. リスト

Python では「リスト」という何かの要素を順番で保持する形式があります。リストの中には基本的になんでもしまい込めます。数値や文字列、ImageJ の画像データなんかもリストにできます。

ls = [1, 2, 3, 4, 5]
print(ls)
txtls = ["a", "b", "c", "d", "shatoshi"]
print(txtls)

f:id:shatoshi:20170614162318j:plain

リストは文字列と同じようにスライス表記で扱ったり、「+」で連結したりできます。len()でリストの長さを取得することができます(ちなみに文字列も同様にlen()で長さを取得することができます)。

ls = [1, 2, 3, 4, 5]
print(ls[1:4])
print(len(ls))
txtls = ["a", "b", "c", "d", "shatoshi"]
print(txtls[4])
print(ls + txtls)
print(len(txtls))

f:id:shatoshi:20170614164459j:plain

リストも文字列も要素が順番に並んでいるという似たデータの構造になっていて、同じように扱うことができる仕様になっています。このように、似たものはできるだけ同じように操作できるようになっていて、人間にとって優しいところも Python の使いやすいところです。

数字が順番に並んだリストを range() で作製することができます*2。使い方は複数あります。

  • range(a) で 0 から a-1 まで、1 づつ増える a 個の数字からなるリストを作ります。

  • range(a, b) で b から始まって a-1 まで、1 づつ増える a 個の数字からなるリストを作ります。

  • range(a, b, c) で a から始まって a-c まで、c づつ増える a 個の数字からなるリストを作ります。

例えば以下のような感じです。

ls1 = range(10)
print(ls1)
ls2 = range(3, 10)
print(ls2)
ls3 = range(2, 10, 2)
print(ls3)

f:id:shatoshi:20170628174444j:plain

例えば、画像データのリスト、対応するファイル名のリスト、対応する解析結果のリストを作ったりして、プログラムを作る時に役に立つことが多いです。あと、次に書く繰り返しとの相性も良いです。また、ImageJ の機能が出力するデータは、リストやあるいはリストのように扱えるデータが多いのでプログラミングする際に重要になってきます。

5. 繰り返し(for)

繰り返し同じことをするとき、for 文というのを使います。書き方は、

for 変数 in リストもしくはリストのような配列構造をもつモノ:

(スペース)繰り返す処理の内容

になります。例えば以下のような感じです。

for i in range(6):
    print(i**2)

f:id:shatoshi:20170612202929j:plain

これが何をやっているかというと、

  1. [0, 1, 2, 3, 4, 5] というリストを range(6) で作る。

  2. in の後にあるリスト(ここでは[0, 1, 2, 3, 4, 5])のそれぞれの要素を先頭の 0 から順番に変数 i に代入して、「繰り返す処理の内容」を実行する。

  3. 「繰り返す処理の内容」は「変数 i を二乗してそれを出力する」です。

また、上の方にリストもしくはリストのような配列構造を持つモノと書きましたがこの性質を iterable と言います。リストでなくとも iterable なモノを in の後におけば繰り返し処理できます。例えば、文字列なんかも iterable です。以下のように、

for i in "Python":
    print(i)

と書いて実行すると、

f:id:shatoshi:20170614183813j:plain

"Python" を一文字ずつ表示します。

ここで、for・・・の後のコロン(:)を忘れると・・・・・・、

f:id:shatoshi:20170614184642j:plain

エラーが出ます。SyntaxError: ("mismatched input '\n' expecting COLON", ('New_.py', 1, 17, 'for i in "Python"\n'))と表示されましたね。なんかいきなり英語で怒られるとわけわかんないと思うかもしれませんが、何が悪かったのかをちゃんとエラーメッセージにちゃんと表示してくれてます。分かんない単語があればググるなり、辞書を引くなりすれば分かります。これは、

文法のエラーやで:(”間違った入力'\n'(\nは改行)があるで。ここにはコロンが来るはずや”,(New_.pyってファイル(実行中のファイル)の, 一行目の17番目の所や。for i in "Pythonって文やで。気い付けや!))

って意味です。慣れない時には間違えてエラーがいっぱい出ると思いますが、どこが悪いかを指摘してくれるので、分からない単語があれば調べて頑張ってエラーメッセージを読みましょう。

さて、「繰り返し処理」の前にスペース(インデント)を入れていますね。Python はプログラムの文のまとまりをスペースなどによるインデント(字下げ)で表すのが特徴の一つです。 例えば次の二つのプログラムがあったとします。

for i in "Python":
    print(i)
    print("ImageJ")
for i in "Python":
    print(i)
print("ImageJ")

上は "Python" を一文字ずつ処理して、"P"を表示して"ImageJ"を表示、次に"y"を表示して"ImageJ"を表示、次に"t"を・・・・・・という動作をします。

f:id:shatoshi:20170614191618j:plain

では下のプログラムはどうでしょう?

f:id:shatoshi:20170614191736j:plain

インデントが無いだけで、さっきと全然挙動が違いますね。二行目の print(i) は繰り返し実行されていますが三行目の print("ImageJ") は最後に一度しか実行されていません。

Pythonが「繰り返しの処理内容」という文のまとまりをスペースで認識しているために、三行目の print("ImageJ") が for文の処理内容に含まれなくなりこういう実行結果になります。

こういった文のまとまりは他のプログラミング言語だと{}などの括弧で表すことが多くて、インデントを入れるのはプログラムの見た目を整えるためだけに入れます。それも人それぞれ好きなように書いたり・・・。 この文法のおかげで、見た目が文法レベルで整えられて、初心者でも読みやすいプログラムを書くことができます。そう Python ならね。

また、このインデントで作った文のまとまりの中に、また別のインデントによるまとまりを入れることができます。例えば以下のプログラムは、"Python" のそれぞれの一文字を三回づつ表示する(PPPyyyttt・・・)プログラムになります。

for i in "Python":
    for j in range(3):
        print(i)

このインデントはスペース何文字でもOKで、タブでもOKです。ただ、タブを何文字にするかというのは、文字を打つ環境の設定で変わってきます。また、書く人によって異なるインデントのスペースで書いていたら、他の人が書いたプログラムを修正したりするときに支障が出ますし、読みづらいコードになります。基本的に、スペース4つで一つのインデントにしましょうというのが基準になっています。なので、そうするのがいいでしょう。ただ、インデントを入れるときに毎回スペースを4回連打するのもだるいので、設定を以下のように変更しましょう。

f:id:shatoshi:20170614194736j:plain

  • Edit > Tab sizes > 4を選択。

  • Edit key inserts spaces にチェックを入れる。

  • Save Preferences をクリックする。

これで、Tab キーを押すことで4つスペースが入る設定になりました。

このような繰り返し処理は、例えば画像解析の自動化において、フォルダーの中にある画像ファイルに順番に同じ処理をしたり、動画ファイルを一コマづつ処理したり、csvファイルに一行ずつ解析結果を書き込んだり・・・・・・といった具合に使います。また、繰り返し処理はあらゆるプログラムの基本の一つなので、色々自分で書いてみて慣れましょう。

6. 条件分岐(if)

条件分岐は、プログラムを書く上で重要な要素の一つです。条件Aの時αをする、条件Bの時βをする・・・みたいなことをします。これを使うと、例えばあるフォルダの中にある "WT" という文字が入ったファイルだけ処理するみたいなことができます。また、繰り返しと組み合わせることで様々なことができるようになります。

条件分岐の書き方は、

if 条件A:
   実行内容α

elif 条件B:
   実行内容β

elif 条件C:
   実行内容θ

else:
   実行内容Γ

という感じです。if、elseは英語と同じ意味で、もしも~なら、elseはそれ以外なら~、elifはelseとifを合わせた感じです。 条件Aが真の時、「実行内容α」を実行する、それ以外の時で条件Bが真の時、「実行内容β」を実行する・・・全部どれべもない時、「実行内容Γ」を実行する、という具合に動きます。この実行内容の部分は for の時と同じくインデントで文のまとまりを作ります。

例えば、以下のようなプログラムがあったとします。

for i in range(7):
    if i % 3 == 0:
        print(i, "stupid!")
    elif i % 2 == 0:
        print(i**2, "squaring!")
    else:
        print(i)

これは、3のの倍数の時だけ馬鹿になって("数字","stupid!"と表示する)、そして偶数の時だけ賢くなって二乗する("数字の二乗","suaring!"と表示する)、それ以外の時は普通に数字を表示する条件分岐です。これを先ほどの for を使って、0~6 までについて実行しています。3の倍数かどうか、偶数かどうかは % で計算しています。a%bで、aをbで割った時のあまりを計算でき、この余りがゼロかどうかを a%b == 0 で判断しています。また、同じ条件が二つある場合は上に書いた方が優先されます。例えば上のプログラムでは、6は3の倍数でありかつ偶数でもあるのですが、先に書いた3の倍数の時に馬鹿になる方が優先されています。

== のようにイコールを二つ連続で書くときは代入の時のイコールと意味が異なり、「左辺と右辺が同じであるということを判断する」という意味になってきます。高校までの数学では、代入と左辺と右辺が同じという意味でのイコールをあまり意識して区別はしなかったと思いますが、コンピュータはどっちか区別できないので、このように書きます。

条件が真か偽かというのは、Python では「True」と「False」で表します。Python は==の左辺と右辺が等しい時、真:Ture を出力し、等しくない時、偽:False を出力します。以下のような感じです。ちなみに、== でなく !=(not equal)とすると逆の結果になります。

print(10 == 10)
print(10 == 11)
print(10 != 10)
print(10 != 11)

f:id:shatoshi:20170614203634j:plain

f:id:shatoshi:20170614203645j:plain

if という文は if の後の部分が True の時、書かれた「実行内容」を行うものなのですね。

この条件はいくつか組み合わせて複雑な条件にすることもできます。A and B で A も B も両方真のとき Ture、A and B で A か B どちらかが真のとき Ture 、のように書くことができます。

print(True and False)
print(True and True)
print(True or False)
print(True and True)
print(False or False)

f:id:shatoshi:20170614204259j:plain

練習問題2

if の項で最初に書いた、

for i in range(7):
    if i % 3 == 0:
        print(i, "stupid!")
    elif i % 2 == 0:
        print(i**2, "squaring!")
    else:
        print(i)

のプログラムの実行結果は、6 のように 3 の倍数かつ偶数のとき、3の倍数という条件が優先されて馬鹿になりますが、書き換えて 3 の倍数かつ偶数の時に二乗するように書き換えてください。and や or を使う方法と使わない方法があるので両方書いてみましょう。

以下答えです。反転させて見てください。

for i in range(7):
    if i % 3 == 0 and i % 3 != 0:
        print(i, "stupid!")
    elif i % 2 == 0:
        print(i**2, "squaring!")
    else:
        print(i)

もしくは、

for i in range(7):
    if i % 2 == 0:
        print(i**2, "squaring!")
    elif i % 3 == 0:
        print(i, "stupid!")
    else:
        print(i)

7. 関数の定義

例えば、ある式やある処理が何度もそのプログラムに出てくるとき、それを何度も打ったりコピペするのはとても面倒ですし、プログラムが長くなりとても読みづらくなります。そこで関数の定義を行います。ところで、関数っていったい何なのか?

例えば、今までに出てきた print() や range() が関数です*3。関数は、括弧()にある何かの値を受け取って*4ある決まった処理をするというものです。例えば、print()は括弧の中で文字列や値を受け取って、それを表示する関数、range()は受け取った数字をもとに連番の配列を出力する関数になります。関数には、値を返す関数と返さない関数があります。値を返す関数を実行すると、関数を書いた場所に数字などの出力結果が出力されます。例えば、print() は何も値を返しませんが、range() はリストを返します。

では早速関数の定義をやってみましょう。以下のように書きます。

値を返す場合は、

def 関数の名前(引数1, 引数2, ・・・):
    関数の中身(インデントでブロック)
       ・
       ・
       ・
    return

値を返さない場合は、

def 関数の名前(引数1, 引数2, ・・・):
    関数の中身(インデントでブロック)
       ・
       ・
       ・

def は英語の define からきてます。関数の定義も、for や if と同様にコロンとインデントを用いて書きます。

例えば値を返す場合だとこんな感じです。

def squaring(x):
    y = x**2
    return y


for i in range(6):
    print(squaring(i))

f:id:shatoshi:20170725184814j:plain

上記のプログラムは、 1. 「xを引数にして、xの二乗を計算してyに代入する」、最後に「yを返す(出力する)」という動作をする squaring という名前の関数を定義して、 2. 0~6 をsquaring に入力して結果を表示する プログラムになります。

また、例えば、値を返さない関数を定義して、同じ動作をするプログラムは以下のようになります。

def squaring(x):
    y = x**2
    print(y)


for i in range(6):
    squaring(i)

f:id:shatoshi:20170628175200j:plain

返す値というのは、数値だけでなく、文字列やリストなどなんでもかまいません。

関数をうまく定義することで、プログラムが読みやすくなり、書いたプログラムをちょっと改良して似た機能を持つプログラムを作ることが簡単になります。覚えておいてください。

まとめ

今回紹介した「繰り返し」「条件分岐」「関数の定義」を駆使して、プログラムに元々ある簡単な関数や四則演算等を組み合わせることで、より複雑なプログラムを作るというのが、プログラミングの基本です。プログラミングを楽しみましょう。

まとめの練習問題

途中に出てきた3の倍数で馬鹿になり、偶数の時に二乗するプログラムを書き換えて遊んでみましょう。

慣れてきたら、値を返す関数を使ってこのプログラム書き換えて、最初に 0~10 について馬鹿関数を実行て結果を表示、次に 51 から 70 についてするて馬鹿関数を実行して結果を表示するプログラムを書いてみましょう。どの部分を関数で書き換えるべきかよく考えて書いてみましょう。以下に答えを書きますが、これと違う方法であっても全然かまいません。愉しんでやってましょう。楽しむことは大事です。

def stupid(x):
    if i % 3 == 0 and i % 2 != 0:
        return [i, "stupid!"]
    elif i % 2 == 0:
        return [i**2, "squaring!"]
    else:
        return [i, ""]


for i in range(10):
    print(stupid(i))
for i in range(50, 70):
    print(stupid(i))

次の記事

次の記事では、実際に ImageJ の機能を使って画像を処理したりしようと思います。

satoshithermophilus.hatenablog.com

Python の入門にはこちらの本がおすすめです。プログラミング自体が初心者の人に向けて詳しく書かれています。

ImageJ の使い方については「ImageJで始める画像解析」という本が良書です。画像データの基本などの初心者向けの内容から入り、生物系の顕微鏡画像の具体的な定量解析や、有用なプラグインの活用法まで書いています。

*1:僕は、はてな id を satoshi でとったつもりが shatoshi とタイポしてしまって取り返しがつかない感じになってしまいました orz

*2:こう書きましたが、Python3系では、range()はリストを作るのではなく、iterable なオブジェクトを生成します。

*3:print()は実はpython2系では文(ステートメント)なので、厳密にいえば関数ではないのです。ごめんなさい。python3系では関数になっているのですが・・・。違うけど大目に見てください。

*4:何も受け取らない関数もあるし、定義もできるけどね。